Post notification on abusive background current drain

Tapping on the notification will bring up the individual abusive
app's battery settings page. It won't be re-posted after being
dismissed until next day, in order to avoid from spamming the user.

Bug: 200326767
Bug: 203105544
Test: FrameworksMockingServicesTests:BackgroundRestrictionTest
Test: Manual - adb shell am set-bg-abusive-uids & verify notification
Change-Id: I0b30014d748863c66c3845b5f310948a9493e302
This commit is contained in:
Jing Ji
2021-12-07 14:51:43 -08:00
parent b5f28a5ee6
commit 0c6603973a
8 changed files with 327 additions and 18 deletions

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
}
}

View File

@@ -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, "");
}

View File

@@ -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;
}
}
}
}

View File

@@ -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() {

View File

@@ -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>