diff options
8 files changed, 327 insertions, 18 deletions
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index 3b6f8f6187e1..b79c0bed4564 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -62,6 +62,7 @@ public class SystemNotificationChannels { public static String DO_NOT_DISTURB = "DO_NOT_DISTURB"; public static String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION"; public static String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY"; + public static String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS"; public static void createAll(Context context) { final NotificationManager nm = context.getSystemService(NotificationManager.class); @@ -209,6 +210,12 @@ public class SystemNotificationChannels { NotificationManager.IMPORTANCE_LOW); channelsList.add(accessibilitySecurityPolicyChannel); + final NotificationChannel abusiveBackgroundAppsChannel = new NotificationChannel( + ABUSIVE_BACKGROUND_APPS, + context.getString(R.string.notification_channel_abusive_bg_apps), + NotificationManager.IMPORTANCE_LOW); + channelsList.add(abusiveBackgroundAppsChannel); + nm.createNotificationChannels(channelsList); } diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 1a5d8b7ca8fd..fe5bafd491bb 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6199,4 +6199,13 @@ ul.</string> <string name="ui_translation_accessibility_translated_text"><xliff:g id="message" example="Hello">%1$s</xliff:g> Translated.</string> <!-- Accessibility message announced to notify the user when the system has finished translating the content displayed on the screen to a different language after the user requested translation. [CHAR LIMIT=NONE] --> <string name="ui_translation_accessibility_translation_finished">Message translated from <xliff:g id="from_language" example="English">%1$s</xliff:g> to <xliff:g id="to_language" example="French">%2$s</xliff:g>.</string> + + <!-- Title for the notification channel notifying user of abusive background apps. [CHAR LIMIT=NONE] --> + <string name="notification_channel_abusive_bg_apps">Background Activity</string> + <!-- Title of notification indicating abusive background apps. [CHAR LIMIT=NONE] --> + <string name="notification_title_abusive_bg_apps">Background Activity</string> + <!-- Content of notification indicating abusive background apps. [CHAR LIMIT=NONE] --> + <string name="notification_content_abusive_bg_apps"> + <xliff:g id="app" example="Gmail">%1$s</xliff:g> is running in the background and draining battery. Tap to review. + </string> </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index bcc3a6df3180..3ceaa2aaf9fc 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4669,4 +4669,8 @@ <java-symbol type="string" name="config_deviceManagerUpdater" /> <java-symbol type="string" name="config_deviceSpecificDeviceStatePolicyProvider" /> + + <java-symbol type="string" name="notification_channel_abusive_bg_apps"/> + <java-symbol type="string" name="notification_title_abusive_bg_apps"/> + <java-symbol type="string" name="notification_content_abusive_bg_apps"/> </resources> diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 196c6aaa7fcb..e89dda90a2dd 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -362,5 +362,11 @@ message SystemMessage { // Notify the user that some accessibility service has view and control permissions. // package: android NOTE_A11Y_VIEW_AND_CONTROL_ACCESS = 1005; + + // Notify the user an abusive background app has been detected. + // Package: android + // Note: this is a base ID, multiple notifications will be posted for each + // abusive apps, with notification ID based off this ID. + NOTE_ABUSIVE_BG_APPS_BASE = 0xc1b2508; // 203105544 } } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index c062365944d7..8eba1a2101b1 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -336,6 +336,8 @@ final class ActivityManagerShellCommand extends ShellCommand { return runGetIsolatedProcesses(pw); case "set-stop-user-on-switch": return runSetStopUserOnSwitch(pw); + case "set-bg-abusive-uids": + return runSetBgAbusiveUids(pw); default: return handleDefaultCommands(cmd); } @@ -3215,6 +3217,43 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + // TODO(b/203105544) STOPSHIP - For debugging only, to be removed before shipping. + private int runSetBgAbusiveUids(PrintWriter pw) throws RemoteException { + final String arg = getNextArg(); + final AppBatteryTracker batteryTracker = + mInternal.mAppRestrictionController.getAppStateTracker(AppBatteryTracker.class); + if (batteryTracker == null) { + getErrPrintWriter().println("Unable to get bg battery tracker"); + return -1; + } + if (arg == null) { + batteryTracker.mDebugUidPercentages.clear(); + return 0; + } + String[] pairs = arg.split(","); + int[] uids = new int[pairs.length]; + double[] values = new double[pairs.length]; + try { + for (int i = 0; i < pairs.length; i++) { + String[] pair = pairs[i].split("="); + if (pair.length != 2) { + getErrPrintWriter().println("Malformed input"); + return -1; + } + uids[i] = Integer.parseInt(pair[0]); + values[i] = Double.parseDouble(pair[1]); + } + } catch (NumberFormatException e) { + getErrPrintWriter().println("Malformed input"); + return -1; + } + batteryTracker.mDebugUidPercentages.clear(); + for (int i = 0; i < pairs.length; i++) { + batteryTracker.mDebugUidPercentages.put(uids[i], values[i]); + } + return 0; + } + private Resources getResources(PrintWriter pw) throws RemoteException { // system resources does not contain all the device configuration, construct it manually. Configuration config = mInterface.getConfiguration(); @@ -3551,6 +3590,8 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Sets whether the current user (and its profiles) should be stopped" + " when switching to a different user."); pw.println(" Without arguments, it resets to the value defined by platform."); + pw.println(" set-bg-abusive-uids [uid=percentage][,uid=percentage...]"); + pw.println(" Force setting the battery usage of the given UID."); pw.println(); Intent.printIntentArgsHelp(pw, ""); } diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 49a22d6060ea..24ec0869aaac 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -69,7 +69,7 @@ import java.util.List; final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> { static final String TAG = TAG_WITH_CLASS_NAME ? "AppBatteryTracker" : TAG_AM; - private static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false; + static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false; // As we don't support realtime per-UID battery usage stats yet, we're polling the stats // in a regular time basis. @@ -103,6 +103,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> { private BatteryUsageStatsQuery mBatteryUsageStatsQuery; + // For debug only. + final SparseArray<Double> mDebugUidPercentages = new SparseArray<>(); + AppBatteryTracker(Context context, AppRestrictionController controller) { this(context, controller, null, null); } @@ -112,7 +115,9 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> { Object outerContext) { super(context, controller, injector, outerContext); if (injector == null) { - mBatteryUsageStatsPollingIntervalMs = BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_LONG; + mBatteryUsageStatsPollingIntervalMs = DEBUG_BACKGROUND_BATTERY_TRACKER + ? BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG + : BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_LONG; } else { mBatteryUsageStatsPollingIntervalMs = BATTERY_USAGE_STATS_POLLING_INTERVAL_MS_DEBUG; } @@ -186,6 +191,11 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> { } bgPolicy.handleUidBatteryConsumption(uid, percentage); } + // For debugging only. + for (int i = 0, size = mDebugUidPercentages.size(); i < size; i++) { + bgPolicy.handleUidBatteryConsumption(mDebugUidPercentages.keyAt(i), + mDebugUidPercentages.valueAt(i)); + } } finally { scheduleBatteryUsageStatsUpdateIfNecessary(); } @@ -422,7 +432,8 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> { mBgCurrentDrainWindowMs = DeviceConfig.getLong( DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_BG_CURRENT_DRAIN_WINDOW, - DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS); + mBgCurrentDrainWindowMs != DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS + ? mBgCurrentDrainWindowMs : DEFAULT_BG_CURRENT_DRAIN_WINDOW_MS); } @Override @@ -490,16 +501,19 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> { // it's actually back to normal, but we don't untrack it until // explicit user interactions. notifyController = true; - } else if (percentage >= mBgCurrentDrainBgRestrictedThreshold) { - // If we're in the restricted standby bucket but still seeing high - // current drains, tell the controller again. - if (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET - && ts[TIME_STAMP_INDEX_BG_RESTRICTED] == 0) { - final long now = SystemClock.elapsedRealtime(); - if (now > ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] - + mBgCurrentDrainWindowMs) { - ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; - notifyController = excessive = true; + } else { + excessive = true; + if (percentage >= mBgCurrentDrainBgRestrictedThreshold) { + // If we're in the restricted standby bucket but still seeing high + // current drains, tell the controller again. + if (curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET + && ts[TIME_STAMP_INDEX_BG_RESTRICTED] == 0) { + final long now = SystemClock.elapsedRealtime(); + if (now > ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET] + + mBgCurrentDrainWindowMs) { + ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now; + notifyController = true; + } } } } diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index aa24a3452194..f1d48d4e02c3 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -47,7 +47,9 @@ import static android.app.usage.UsageStatsManager.reasonToString; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS; +import static android.os.Process.SYSTEM_UID; +import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -61,6 +63,9 @@ import android.app.ActivityThread; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IUidObserver; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageStatsManager; import android.content.BroadcastReceiver; @@ -68,9 +73,11 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; +import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; @@ -82,6 +89,7 @@ import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.DeviceConfig.OnPropertiesChangedListener; import android.provider.DeviceConfig.Properties; +import android.provider.Settings; import android.provider.Settings.Global; import android.util.Slog; import android.util.SparseArrayMap; @@ -89,6 +97,7 @@ import android.util.TimeUtils; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.util.ArrayUtils; import com.android.internal.util.function.TriConsumer; import com.android.server.AppStateTracker; @@ -147,6 +156,7 @@ public final class AppRestrictionController { private final Object mLock = new Object(); private final Injector mInjector; + private final NotificationHelper mNotificationHelper; /** * The restriction levels that each package is on, the levels here are defined in @@ -165,6 +175,9 @@ public final class AppRestrictionController { private @ElapsedRealtimeLong long mLevelChangeTimeElapsed; private int mReason; + @ElapsedRealtimeLong long mLastNotificationShownTimeElapsed; + int mNotificationId; + PkgSettings(String packageName, int uid) { mPackageName = packageName; mUid = uid; @@ -207,8 +220,12 @@ public final class AppRestrictionController { pw.print('/'); pw.print(ActivityManager.restrictionLevelToName(mLastRestrictionLevel)); } - pw.print(' '); + pw.print(" levelChange="); TimeUtils.formatDuration(mLevelChangeTimeElapsed - nowElapsed, pw); + if (mLastNotificationShownTimeElapsed > 0) { + pw.print(" lastNoti="); + TimeUtils.formatDuration(mLastNotificationShownTimeElapsed - nowElapsed, pw); + } } String getPackageName() { @@ -240,7 +257,7 @@ public final class AppRestrictionController { @RestrictionLevel int update(String packageName, int uid, @RestrictionLevel int level, int reason, int subReason) { synchronized (mLock) { - PkgSettings settings = mRestrictionLevels.get(uid, packageName); + PkgSettings settings = getRestrictionSettingsLocked(uid, packageName); if (settings == null) { settings = new PkgSettings(packageName, uid); mRestrictionLevels.add(uid, packageName, settings); @@ -284,7 +301,7 @@ public final class AppRestrictionController { @RestrictionLevel int getRestrictionLevel(int uid, String packageName) { synchronized (mLock) { - final PkgSettings settings = mRestrictionLevels.get(uid, packageName); + final PkgSettings settings = getRestrictionSettingsLocked(uid, packageName); return settings == null ? getRestrictionLevel(uid) : settings.getCurrentRestrictionLevel(); } @@ -325,6 +342,11 @@ public final class AppRestrictionController { } } + @GuardedBy("mLock") + PkgSettings getRestrictionSettingsLocked(int uid, String packageName) { + return mRestrictionLevels.get(uid, packageName); + } + void removeUser(@UserIdInt int userId) { synchronized (mLock) { for (int i = mRestrictionLevels.numMaps() - 1; i >= 0; i--) { @@ -373,14 +395,24 @@ public final class AppRestrictionController { * when it's background-restricted. */ static final String KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = - DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "auto_restricted_bucket_on_bg_restricted"; + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "auto_restricted_bucket_on_bg_restricted"; + + /** + * The minimal interval in ms before posting a notification again on abusive behaviors + * of a certain package. + */ + static final String KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "abusive_notification_minimal_interval"; static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = true; + static final long DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS = 24 * 60 * 60 * 1000; volatile boolean mBgAutoRestrictedBucket; volatile boolean mRestrictedBucketEnabled; + volatile long mBgNotificationMinIntervalMs; + ConstantsObserver(Handler handler) { super(handler); } @@ -395,6 +427,9 @@ public final class AppRestrictionController { case KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION: updateBgAutoRestrictedBucketChanged(); break; + case KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL: + updateBgAbusiveNotificationMinimalInterval(); + break; } AppRestrictionController.this.onPropertiesChanged(name); } @@ -425,6 +460,7 @@ public final class AppRestrictionController { void updateDeviceConfig() { updateBgAutoRestrictedBucketChanged(); + updateBgAbusiveNotificationMinimalInterval(); } private void updateBgAutoRestrictedBucketChanged() { @@ -437,6 +473,13 @@ public final class AppRestrictionController { dispatchAutoRestrictedBucketFeatureFlagChanged(mBgAutoRestrictedBucket); } } + + private void updateBgAbusiveNotificationMinimalInterval() { + mBgNotificationMinIntervalMs = DeviceConfig.getLong( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL, + DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS); + } } private final ConstantsObserver mConstantsObserver; @@ -515,6 +558,7 @@ public final class AppRestrictionController { mBgHandlerThread.start(); mBgHandler = new BgHandler(mBgHandlerThread.getLooper(), injector); mConstantsObserver = new ConstantsObserver(mBgHandler); + mNotificationHelper = new NotificationHelper(this); injector.initAppStateTrackers(this); } @@ -793,6 +837,8 @@ public final class AppRestrictionController { applyRestrictionLevel(pkgName, uid, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, curBucket, true, REASON_MAIN_FORCED_BY_USER, REASON_SUB_FORCED_USER_FLAG_INTERACTION); + mBgHandler.obtainMessage(BgHandler.MSG_CANCEL_REQUEST_BG_RESTRICTED, uid, 0, pkgName) + .sendToTarget(); } else { // Moved out of the background-restricted state, we'd need to check if it should // stay in the restricted standby bucket. @@ -857,7 +903,135 @@ public final class AppRestrictionController { Slog.i(TAG, "Requesting background restricted " + packageName + " " + UserHandle.formatUid(uid)); } - // TODO: b/200326767 - show the request notification. + mNotificationHelper.postRequestBgRestrictedIfNecessary(packageName, uid); + } + + void handleCancelRequestBgRestricted(String packageName, int uid) { + if (DEBUG_BG_RESTRICTION_CONTROLLER) { + Slog.i(TAG, "Cancelling requesting background restricted " + packageName + " " + + UserHandle.formatUid(uid)); + } + mNotificationHelper.cancelRequestBgRestrictedIfNecessary(packageName, uid); + } + + static class NotificationHelper { + static final String PACKAGE_SCHEME = "package"; + static final String GROUP_KEY = "com.android.app.abusive_bg_apps"; + + static final int SUMMARY_NOTIFICATION_ID = SystemMessage.NOTE_ABUSIVE_BG_APPS_BASE; + + private final AppRestrictionController mBgController; + private final NotificationManager mNotificationManager; + private final Injector mInjector; + private final Object mLock; + private final Context mContext; + + @GuardedBy("mLock") + private int mNotificationIDStepper = SUMMARY_NOTIFICATION_ID + 1; + + NotificationHelper(AppRestrictionController controller) { + mBgController = controller; + mInjector = controller.mInjector; + mNotificationManager = mInjector.getNotificationManager(); + mLock = controller.mLock; + mContext = mInjector.getContext(); + } + + void postRequestBgRestrictedIfNecessary(String packageName, int uid) { + int notificationId; + synchronized (mLock) { + final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings + .getRestrictionSettingsLocked(uid, packageName); + + final long now = SystemClock.elapsedRealtime(); + if (settings.mLastNotificationShownTimeElapsed != 0 + && (settings.mLastNotificationShownTimeElapsed + + mBgController.mConstantsObserver.mBgNotificationMinIntervalMs > now)) { + if (DEBUG_BG_RESTRICTION_CONTROLLER) { + Slog.i(TAG, "Not showing notification as last notification was shown " + + TimeUtils.formatDuration( + now - settings.mLastNotificationShownTimeElapsed) + + " ago"); + } + return; + } + if (DEBUG_BG_RESTRICTION_CONTROLLER) { + Slog.i(TAG, "Showing notification for " + packageName + + "/" + UserHandle.formatUid(uid) + + ", now=" + now + + ", lastShown=" + settings.mLastNotificationShownTimeElapsed); + } + settings.mLastNotificationShownTimeElapsed = now; + if (settings.mNotificationId == 0) { + settings.mNotificationId = mNotificationIDStepper++; + } + notificationId = settings.mNotificationId; + } + + final UserHandle targetUser = UserHandle.of(UserHandle.getUserId(uid)); + + postSummaryNotification(targetUser); + + final PackageManagerInternal pm = mInjector.getPackageManagerInternal(); + final ApplicationInfo ai = pm.getApplicationInfo(packageName, STOCK_PM_FLAGS, + SYSTEM_UID, UserHandle.getUserId(uid)); + final String title = mContext.getString( + com.android.internal.R.string.notification_title_abusive_bg_apps); + final String message = mContext.getString( + com.android.internal.R.string.notification_content_abusive_bg_apps, + ai != null ? mInjector.getPackageManager() + .getText(packageName, ai.labelRes, ai) : packageName); + + final Intent intent = new Intent(Settings.ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL); + intent.setData(Uri.fromParts(PACKAGE_SCHEME, packageName, null)); + final PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0, + intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, null, + targetUser); + + final Notification.Builder notificationBuilder = new Notification.Builder(mContext, + ABUSIVE_BACKGROUND_APPS) + .setAutoCancel(true) + .setGroup(GROUP_KEY) + .setWhen(System.currentTimeMillis()) + .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning) + .setColor(mContext.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(message) + .setContentIntent(pendingIntent); + if (ai != null) { + notificationBuilder.setLargeIcon(Icon.createWithResource(packageName, ai.icon)); + } + + final Notification notification = notificationBuilder.build(); + // Remember the package name for testing. + notification.extras.putString(Intent.EXTRA_PACKAGE_NAME, packageName); + + mNotificationManager.notifyAsUser(null, notificationId, notification, targetUser); + } + + private void postSummaryNotification(@NonNull UserHandle targetUser) { + final Notification summary = new Notification.Builder(mContext, + ABUSIVE_BACKGROUND_APPS) + .setGroup(GROUP_KEY) + .setGroupSummary(true) + .setStyle(new Notification.BigTextStyle()) + .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning) + .setColor(mContext.getColor( + com.android.internal.R.color.system_notification_accent_color)) + .build(); + mNotificationManager.notifyAsUser(null, SUMMARY_NOTIFICATION_ID, summary, targetUser); + } + + void cancelRequestBgRestrictedIfNecessary(String packageName, int uid) { + synchronized (mLock) { + final RestrictionSettings.PkgSettings settings = mBgController.mRestrictionSettings + .getRestrictionSettingsLocked(uid, packageName); + if (settings.mNotificationId > 0) { + mNotificationManager.cancel(settings.mNotificationId); + } + } + } } void handleUidInactive(int uid, boolean disabled) { @@ -924,6 +1098,18 @@ public final class AppRestrictionController { mAppStateTrackers.add(tracker); } + /** + * @return The tracker instance of the given class. + */ + <T extends BaseAppStateTracker> T getAppStateTracker(Class<T> trackerClass) { + for (BaseAppStateTracker tracker : mAppStateTrackers) { + if (trackerClass.isAssignableFrom(tracker.getClass())) { + return (T) tracker; + } + } + return null; + } + static class BgHandler extends Handler { static final int MSG_BACKGROUND_RESTRICTION_CHANGED = 0; static final int MSG_APP_RESTRICTION_LEVEL_CHANGED = 1; @@ -932,6 +1118,7 @@ public final class AppRestrictionController { static final int MSG_REQUEST_BG_RESTRICTED = 4; static final int MSG_UID_INACTIVE = 5; static final int MSG_UID_ACTIVE = 6; + static final int MSG_CANCEL_REQUEST_BG_RESTRICTED = 7; private final Injector mInjector; @@ -966,6 +1153,9 @@ public final class AppRestrictionController { case MSG_UID_ACTIVE: { c.handleUidActive(msg.arg1); } break; + case MSG_CANCEL_REQUEST_BG_RESTRICTED: { + c.handleCancelRequestBgRestricted((String) msg.obj, msg.arg1); + } break; } } } @@ -980,6 +1170,7 @@ public final class AppRestrictionController { private IActivityManager mIActivityManager; private UserManagerInternal mUserManagerInternal; private PackageManagerInternal mPackageManagerInternal; + private NotificationManager mNotificationManager; Injector(Context context) { mContext = context; @@ -1048,6 +1239,13 @@ public final class AppRestrictionController { PackageManager getPackageManager() { return getContext().getPackageManager(); } + + NotificationManager getNotificationManager() { + if (mNotificationManager == null) { + mNotificationManager = getContext().getSystemService(NotificationManager.class); + } + return mNotificationManager; + } } private void registerForSystemBroadcasts() { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java index 1d031e19d6a8..5e9232298df0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -36,13 +36,16 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS; import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_BG; import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FG; import static com.android.server.am.AppBatteryTracker.BATT_DIMEN_FGS; import static com.android.server.am.AppRestrictionController.STOCK_PM_FLAGS; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; @@ -64,8 +67,11 @@ import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IUidObserver; +import android.app.Notification; +import android.app.NotificationManager; import android.app.usage.AppStandbyInfo; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.os.BatteryManagerInternal; @@ -85,6 +91,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.AppStateTracker; import com.android.server.DeviceIdleInternal; import com.android.server.am.AppBatteryTracker.AppBatteryPolicy; +import com.android.server.am.AppRestrictionController.NotificationHelper; import com.android.server.apphibernation.AppHibernationManagerInternal; import com.android.server.pm.UserManagerInternal; import com.android.server.usage.AppStandbyInternal; @@ -168,6 +175,7 @@ public final class BackgroundRestrictionTest { @Mock private UserManagerInternal mUserManagerInternal; @Mock private PackageManager mPackageManager; @Mock private PackageManagerInternal mPackageManagerInternal; + @Mock private NotificationManager mNotificationManager; private long mCurrentTimeMillis; @@ -619,6 +627,8 @@ public final class BackgroundRestrictionTest { verify(mBgRestrictionController, times(1)).handleRequestBgRestricted( eq(testPkgName), eq(testUid)); + // Verify we have the notification posted. + checkNotification(testPkgName); }); // Turn ON the FAS for real. @@ -658,6 +668,21 @@ public final class BackgroundRestrictionTest { } } + private void checkNotification(String packageName) throws Exception { + final NotificationManager nm = mInjector.getNotificationManager(); + final ArgumentCaptor<Integer> notificationIdCaptor = + ArgumentCaptor.forClass(Integer.class); + final ArgumentCaptor<Notification> notificationCaptor = + ArgumentCaptor.forClass(Notification.class); + verify(mInjector.getNotificationManager(), atLeast(1)).notifyAsUser(any(), + notificationIdCaptor.capture(), notificationCaptor.capture(), any()); + final Notification n = notificationCaptor.getValue(); + assertTrue(NotificationHelper.SUMMARY_NOTIFICATION_ID < notificationIdCaptor.getValue()); + assertEquals(NotificationHelper.GROUP_KEY, n.getGroup()); + assertEquals(ABUSIVE_BACKGROUND_APPS, n.getChannelId()); + assertEquals(packageName, n.extras.getString(Intent.EXTRA_PACKAGE_NAME)); + } + private void closeIfNotNull(DeviceConfigSession<?> config) throws Exception { if (config != null) { config.close(); @@ -812,6 +837,11 @@ public final class BackgroundRestrictionTest { PackageManager getPackageManager() { return mPackageManager; } + + @Override + NotificationManager getNotificationManager() { + return mNotificationManager; + } } private class TestBaseTrackerInjector<T extends BaseAppStatePolicy> |