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:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, "");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user