diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index a58ceaa990226..294621ee07f84 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -779,6 +779,16 @@ public abstract class ActivityManagerInternal {
* @param started {@code true} if the process transits from non-FGS state to FGS state.
*/
void onForegroundServiceStateChanged(String packageName, int uid, int pid, boolean started);
+
+ /**
+ * Call when the notification of the foreground service is updated.
+ *
+ * @param packageName The package name of the process.
+ * @param uid The UID of the process.
+ * @param foregroundId The current foreground service notification ID, a negative value
+ * means this notification is being removed.
+ */
+ void onForegroundServiceNotificationUpdated(String packageName, int uid, int foregroundId);
}
/**
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6c9d407a9e204..b2c016f0dcbc9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5758,4 +5758,10 @@
current drain threshold.
-->
1800
+
+
+ false
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a1417d3631ad1..538a5db46df6a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4743,4 +4743,5 @@
+
diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java
index 6492662e15744..37ad5f9bb57a4 100644
--- a/services/core/java/com/android/server/am/AppBatteryTracker.java
+++ b/services/core/java/com/android/server/am/AppBatteryTracker.java
@@ -1454,16 +1454,18 @@ final class AppBatteryTracker extends BaseAppStateTracker
notifyController = true;
} else {
excessive = true;
- if (brPercentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]) {
+ if (brPercentage >= mBgCurrentDrainBgRestrictedThreshold[thresholdIndex]
+ && curLevel == RESTRICTION_LEVEL_RESTRICTED_BUCKET) {
// 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) {
- if (now > ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET]
- + mBgCurrentDrainWindowMs) {
- ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
- notifyController = true;
- }
+ final long lastResbucket = ts[TIME_STAMP_INDEX_RESTRICTED_BUCKET];
+ final long lastBgRes = ts[TIME_STAMP_INDEX_BG_RESTRICTED];
+ // If it has been a while since restricting the app and since the last
+ // time we notify the controller, notify it again.
+ if ((now >= lastResbucket + mBgCurrentDrainWindowMs) && (lastBgRes == 0
+ || (now >= lastBgRes + mBgCurrentDrainWindowMs))) {
+ ts[TIME_STAMP_INDEX_BG_RESTRICTED] = now;
+ notifyController = true;
}
}
}
diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java
index 9c775b34f9c2b..de554fe3f1149 100644
--- a/services/core/java/com/android/server/am/AppFGSTracker.java
+++ b/services/core/java/com/android/server/am/AppFGSTracker.java
@@ -43,6 +43,7 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -54,6 +55,7 @@ import com.android.server.am.AppFGSTracker.PackageDurations;
import com.android.server.am.BaseAppStateEventsTracker.BaseAppStateEventsPolicy;
import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
import com.android.server.am.BaseAppStateTracker.Injector;
+import com.android.server.notification.NotificationManagerInternal;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
@@ -71,6 +73,9 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker> mFGSNotificationIDs = new UidProcessMap<>();
+
// Unlocked since it's only accessed in single thread.
private final ArrayMap mTmpPkgDurations = new ArrayMap<>();
@@ -100,11 +105,19 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker 0) {
+ ArraySet notificationIDs = mFGSNotificationIDs.get(uid, packageName);
+ if (notificationIDs == null) {
+ notificationIDs = new ArraySet<>();
+ mFGSNotificationIDs.put(uid, packageName, notificationIDs);
+ }
+ notificationIDs.add(notificationId);
+ } else if (notificationId < 0) {
+ final ArraySet notificationIDs = mFGSNotificationIDs.get(uid, packageName);
+ if (notificationIDs != null) {
+ notificationIDs.remove(-notificationId);
+ if (notificationIDs.isEmpty()) {
+ mFGSNotificationIDs.remove(uid, packageName);
+ }
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean hasForegroundServiceNotificationsLocked(String packageName, int uid) {
+ final ArraySet notificationIDs = mFGSNotificationIDs.get(uid, packageName);
+ if (notificationIDs == null || notificationIDs.isEmpty()) {
+ return false;
+ }
+ final NotificationManagerInternal nm = mInjector.getNotificationManagerInternal();
+ final int userId = UserHandle.getUserId(uid);
+ for (int i = notificationIDs.size() - 1; i >= 0; i--) {
+ if (nm.isNotificationShown(packageName, null, notificationIDs.valueAt(i), userId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@GuardedBy("mLock")
private void scheduleDurationCheckLocked(long now) {
// Look for the active FGS with longest running time till now.
@@ -375,6 +430,28 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker>> map =
+ mFGSNotificationIDs.getMap();
+ final ArrayMap> pkgs = map.get(uid);
+ if (pkgs != null) {
+ for (int i = pkgs.size() - 1; i >= 0; i--) {
+ if (hasForegroundServiceNotificationsLocked(pkgs.keyAt(i), uid)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
@Override
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
@@ -382,6 +459,35 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker>> map =
+ mFGSNotificationIDs.getMap();
+ if (map.size() == 0) {
+ pw.print(prefix);
+ pw.println("(none)");
+ }
+ for (int i = 0, size = map.size(); i < size; i++) {
+ final int uid = map.keyAt(i);
+ final String uidString = UserHandle.formatUid(uid);
+ final ArrayMap> pkgs = map.valueAt(i);
+ for (int j = 0, numOfPkgs = pkgs.size(); j < numOfPkgs; j++) {
+ final String pkgName = pkgs.keyAt(j);
+ pw.print(prefix);
+ pw.print(pkgName);
+ pw.print('/');
+ pw.print(uidString);
+ pw.print(" notification=");
+ pw.println(hasForegroundServiceNotificationsLocked(pkgName, uid));
+ }
+ }
+ }
+ }
+
/**
* Tracks the durations with active FGS for a given package.
*/
diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java
index a3aa129a31ea8..6b571935a9b4d 100644
--- a/services/core/java/com/android/server/am/AppRestrictionController.java
+++ b/services/core/java/com/android/server/am/AppRestrictionController.java
@@ -547,17 +547,37 @@ public final class AppRestrictionController {
static final String KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL =
DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "abusive_notification_minimal_interval";
+ /**
+ * The behavior for an app with a FGS and its notification is still showing, when the system
+ * detects it's abusive and should be put into bg restricted level. {@code true} - we'll
+ * show the prompt to user, {@code false} - we'll not show it.
+ */
+ static final String KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED =
+ DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "prompt_fgs_with_noti_to_bg_restricted";
+
static final boolean DEFAULT_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION = false;
static final long DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS = 24 * 60 * 60 * 1000;
+ /**
+ * Default value to {@link #mBgPromptFgsWithNotiToBgRestricted}.
+ */
+ final boolean mDefaultBgPromptFgsWithNotiToBgRestricted;
+
volatile boolean mBgAutoRestrictedBucket;
volatile boolean mRestrictedBucketEnabled;
volatile long mBgNotificationMinIntervalMs;
- ConstantsObserver(Handler handler) {
+ /**
+ * @see #KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED.
+ */
+ volatile boolean mBgPromptFgsWithNotiToBgRestricted;
+
+ ConstantsObserver(Handler handler, Context context) {
super(handler);
+ mDefaultBgPromptFgsWithNotiToBgRestricted = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted);
}
@Override
@@ -573,6 +593,9 @@ public final class AppRestrictionController {
case KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL:
updateBgAbusiveNotificationMinimalInterval();
break;
+ case KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED:
+ updateBgPromptFgsWithNotiToBgRestricted();
+ break;
}
AppRestrictionController.this.onPropertiesChanged(name);
}
@@ -604,6 +627,7 @@ public final class AppRestrictionController {
void updateDeviceConfig() {
updateBgAutoRestrictedBucketChanged();
updateBgAbusiveNotificationMinimalInterval();
+ updateBgPromptFgsWithNotiToBgRestricted();
}
private void updateBgAutoRestrictedBucketChanged() {
@@ -623,6 +647,32 @@ public final class AppRestrictionController {
KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL,
DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS);
}
+
+ private void updateBgPromptFgsWithNotiToBgRestricted() {
+ mBgPromptFgsWithNotiToBgRestricted = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED,
+ mDefaultBgPromptFgsWithNotiToBgRestricted);
+ }
+
+ void dump(PrintWriter pw, String prefix) {
+ pw.print(prefix);
+ pw.println("BACKGROUND RESTRICTION POLICY SETTINGS:");
+ final String indent = " ";
+ prefix = indent + prefix;
+ pw.print(prefix);
+ pw.print(KEY_BG_AUTO_RESTRICTED_BUCKET_ON_BG_RESTRICTION);
+ pw.print('=');
+ pw.println(mBgAutoRestrictedBucket);
+ pw.print(prefix);
+ pw.print(KEY_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL);
+ pw.print('=');
+ pw.println(mBgNotificationMinIntervalMs);
+ pw.print(prefix);
+ pw.print(KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED);
+ pw.print('=');
+ pw.println(mBgPromptFgsWithNotiToBgRestricted);
+ }
}
private final ConstantsObserver mConstantsObserver;
@@ -704,7 +754,7 @@ public final class AppRestrictionController {
mBgHandlerThread.start();
mBgHandler = new BgHandler(mBgHandlerThread.getLooper(), injector);
mBgExecutor = new HandlerExecutor(mBgHandler);
- mConstantsObserver = new ConstantsObserver(mBgHandler);
+ mConstantsObserver = new ConstantsObserver(mBgHandler, mContext);
mNotificationHelper = new NotificationHelper(this);
injector.initAppStateTrackers(this);
}
@@ -1073,6 +1123,20 @@ public final class AppRestrictionController {
return mInjector.getAppFGSTracker().hasForegroundServices(uid);
}
+ /**
+ * @return If the given package/uid has a foreground service notification or not.
+ */
+ boolean hasForegroundServiceNotifications(String packageName, int uid) {
+ return mInjector.getAppFGSTracker().hasForegroundServiceNotifications(packageName, uid);
+ }
+
+ /**
+ * @return If the given uid has a foreground service notification or not.
+ */
+ boolean hasForegroundServiceNotifications(int uid) {
+ return mInjector.getAppFGSTracker().hasForegroundServiceNotifications(uid);
+ }
+
/**
* @return The to-be-exempted battery usage of the given UID in the given duration; it could
* be considered as "exempted" due to various use cases, i.e. media playback.
@@ -1098,11 +1162,14 @@ public final class AppRestrictionController {
void dump(PrintWriter pw, String prefix) {
pw.print(prefix);
- pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS");
+ pw.println("APP BACKGROUND RESTRICTIONS");
prefix = " " + prefix;
+ pw.print(prefix);
+ pw.println("BACKGROUND RESTRICTION LEVEL SETTINGS");
synchronized (mLock) {
- mRestrictionSettings.dumpLocked(pw, prefix);
+ mRestrictionSettings.dumpLocked(pw, " " + prefix);
}
+ mConstantsObserver.dump(pw, " " + prefix);
for (int i = 0, size = mAppStateTrackers.size(); i < size; i++) {
pw.println();
mAppStateTrackers.get(i).dump(pw, prefix);
@@ -1366,8 +1433,21 @@ public final class AppRestrictionController {
intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, null,
UserHandle.of(UserHandle.getUserId(uid)));
Notification.Action[] actions = null;
- if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER
- && mBgController.hasForegroundServices(packageName, uid)) {
+ final boolean hasForegroundServices =
+ mBgController.hasForegroundServices(packageName, uid);
+ final boolean hasForegroundServiceNotifications =
+ mBgController.hasForegroundServiceNotifications(packageName, uid);
+ if (!mBgController.mConstantsObserver.mBgPromptFgsWithNotiToBgRestricted) {
+ // We're not going to prompt the user if the FGS is active and its notification
+ // is still showing (not dismissed/silenced/denied).
+ if (hasForegroundServices && hasForegroundServiceNotifications) {
+ if (DEBUG_BG_RESTRICTION_CONTROLLER) {
+ Slog.i(TAG, "Not requesting bg-restriction due to FGS with notification");
+ }
+ return;
+ }
+ }
+ if (ENABLE_SHOW_FOREGROUND_SERVICE_MANAGER && hasForegroundServices) {
final Intent trampoline = new Intent(ACTION_FGS_MANAGER_TRAMPOLINE);
trampoline.setPackage("android");
trampoline.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
diff --git a/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java b/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java
index 3e1bcae767196..c6900b279c45f 100644
--- a/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java
+++ b/services/core/java/com/android/server/am/BaseAppStateEventsTracker.java
@@ -184,9 +184,13 @@ abstract class BaseAppStateEventsTracker
}
}
}
+ dumpOthers(pw, prefix);
policy.dump(pw, prefix);
}
+ void dumpOthers(PrintWriter pw, String prefix) {
+ }
+
@GuardedBy("mLock")
void dumpEventHeaderLocked(PrintWriter pw, String prefix, String packageName, int uid, U events,
T policy) {
diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java
index 2846f6c15165e..60d9167586a1f 100644
--- a/services/core/java/com/android/server/am/BaseAppStateTracker.java
+++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java
@@ -36,6 +36,7 @@ import android.util.Slog;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -183,6 +184,7 @@ public abstract class BaseAppStateTracker {
AppOpsManager mAppOpsManager;
MediaSessionManager mMediaSessionManager;
RoleManager mRoleManager;
+ NotificationManagerInternal mNotificationManagerInternal;
void setPolicy(T policy) {
mAppStatePolicy = policy;
@@ -201,6 +203,8 @@ public abstract class BaseAppStateTracker {
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mMediaSessionManager = context.getSystemService(MediaSessionManager.class);
mRoleManager = context.getSystemService(RoleManager.class);
+ mNotificationManagerInternal = LocalServices.getService(
+ NotificationManagerInternal.class);
getPolicy().onSystemReady();
}
@@ -259,5 +263,9 @@ public abstract class BaseAppStateTracker {
RoleManager getRoleManager() {
return mRoleManager;
}
+
+ NotificationManagerInternal getNotificationManagerInternal() {
+ return mNotificationManagerInternal;
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index da78e2d7504ef..ba0d19452880a 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1093,6 +1093,10 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
userId);
foregroundNoti = localForegroundNoti; // save it for amending next time
+
+ signalForegroundServiceNotification(packageName, appInfo.uid,
+ localForegroundId);
+
} catch (RuntimeException e) {
Slog.w(TAG, "Error showing notification for service", e);
// If it gave us a garbage notification, it doesn't
@@ -1126,10 +1130,21 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN
} catch (RuntimeException e) {
Slog.w(TAG, "Error canceling notification for service", e);
}
+ signalForegroundServiceNotification(packageName, appInfo.uid, -localForegroundId);
}
});
}
+ private void signalForegroundServiceNotification(String packageName, int uid,
+ int foregroundId) {
+ synchronized (ams) {
+ for (int i = ams.mForegroundServiceStateListeners.size() - 1; i >= 0; i--) {
+ ams.mForegroundServiceStateListeners.get(i).onForegroundServiceNotificationUpdated(
+ packageName, appInfo.uid, foregroundId);
+ }
+ }
+ }
+
public void stripForegroundServiceFlagFromNotification() {
final int localForegroundId = foregroundId;
final int localUserId = userId;
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 816dbdbcc5a1b..e401d0ea09fd7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java
@@ -127,6 +127,7 @@ import com.android.server.am.AppRestrictionController.NotificationHelper;
import com.android.server.am.AppRestrictionController.UidBatteryUsageProvider;
import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent;
import com.android.server.apphibernation.AppHibernationManagerInternal;
+import com.android.server.notification.NotificationManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.android.server.usage.AppStandbyInternal;
@@ -215,6 +216,7 @@ public final class BackgroundRestrictionTest {
@Mock private PackageManager mPackageManager;
@Mock private PackageManagerInternal mPackageManagerInternal;
@Mock private NotificationManager mNotificationManager;
+ @Mock private NotificationManagerInternal mNotificationManagerInternal;
@Mock private PermissionManagerServiceInternal mPermissionManagerServiceInternal;
@Mock private MediaSessionManager mMediaSessionManager;
@Mock private RoleManager mRoleManager;
@@ -536,11 +538,14 @@ public final class BackgroundRestrictionTest {
final float bgRestrictedThreshold = 4.0f;
final float bgRestrictedThresholdMah =
BATTERY_FULL_CHARGE_MAH * bgRestrictedThreshold / 100.0f;
+ final int testPid = 1234;
+ final int notificationId = 1000;
DeviceConfigSession bgCurrentDrainMonitor = null;
DeviceConfigSession bgCurrentDrainWindow = null;
DeviceConfigSession bgCurrentDrainRestrictedBucketThreshold = null;
DeviceConfigSession bgCurrentDrainBgRestrictedThreshold = null;
+ DeviceConfigSession bgPromptFgsWithNotiToBgRestricted = null;
mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
@@ -587,10 +592,24 @@ public final class BackgroundRestrictionTest {
isLowRamDeviceStatic() ? 1 : 0]);
bgCurrentDrainBgRestrictedThreshold.set(bgRestrictedThreshold);
+ bgPromptFgsWithNotiToBgRestricted = new DeviceConfigSession<>(
+ DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ ConstantsObserver.KEY_BG_PROMPT_FGS_WITH_NOTIFICATION_TO_BG_RESTRICTED,
+ DeviceConfig::getBoolean,
+ mContext.getResources().getBoolean(
+ R.bool.config_bg_prompt_fgs_with_noti_to_bg_restricted));
+ bgPromptFgsWithNotiToBgRestricted.set(true);
+
mCurrentTimeMillis = 10_000L;
doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp();
doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp();
doReturn(statsList).when(mBatteryStatsInternal).getBatteryUsageStats(anyObject());
+ doReturn(true).when(mNotificationManagerInternal).isNotificationShown(
+ testPkgName, null, notificationId, testUser);
+ mAppFGSTracker.onForegroundServiceStateChanged(testPkgName, testUid,
+ testPid, true);
+ mAppFGSTracker.onForegroundServiceNotificationUpdated(
+ testPkgName, testUid, notificationId);
runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
new double[]{restrictBucketThresholdMah - 1, 0},
@@ -721,6 +740,85 @@ public final class BackgroundRestrictionTest {
Thread.sleep(windowMs);
clearInvocations(mInjector.getAppStandbyInternal());
clearInvocations(mBgRestrictionController);
+
+ // We're not going to prompt the user if the abusive app has a FGS with notification.
+ bgPromptFgsWithNotiToBgRestricted.set(false);
+
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ new double[]{bgRestrictedThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ doReturn(mCurrentTimeMillis + windowMs)
+ .when(stats).getStatsEndTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ // We won't change restriction level automatically because it needs
+ // user consent.
+ try {
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+ fail("There shouldn't be level change event like this");
+ } catch (Exception e) {
+ // Expected.
+ }
+ verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
+ eq(testPkgName),
+ eq(STANDBY_BUCKET_RARE),
+ eq(testUser),
+ anyInt(), anyInt());
+ // We should have requested to goto background restricted level.
+ verify(mBgRestrictionController, times(1)).handleRequestBgRestricted(
+ eq(testPkgName),
+ eq(testUid));
+ // However, we won't have the prompt to user posted because the policy
+ // is not to show that for FGS with notification.
+ checkNotificationShown(new String[] {testPkgName}, never(), false);
+ });
+
+ // Pretend we have the notification dismissed.
+ mAppFGSTracker.onForegroundServiceNotificationUpdated(
+ testPkgName, testUid, -notificationId);
+ clearInvocations(mInjector.getAppStandbyInternal());
+ clearInvocations(mBgRestrictionController);
+
+ runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
+ new double[]{bgRestrictedThresholdMah + 1, 0},
+ new double[]{0, restrictBucketThresholdMah - 1}, zeros,
+ () -> {
+ doReturn(mCurrentTimeMillis).when(stats).getStatsStartTimestamp();
+ doReturn(mCurrentTimeMillis + windowMs)
+ .when(stats).getStatsEndTimestamp();
+ mCurrentTimeMillis += windowMs + 1;
+ // We won't change restriction level automatically because it needs
+ // user consent.
+ try {
+ listener.verify(timeout, testUid, testPkgName,
+ RESTRICTION_LEVEL_BACKGROUND_RESTRICTED);
+ fail("There shouldn't be level change event like this");
+ } catch (Exception e) {
+ // Expected.
+ }
+ verify(mInjector.getAppStandbyInternal(), never()).setAppStandbyBucket(
+ eq(testPkgName),
+ eq(STANDBY_BUCKET_RARE),
+ eq(testUser),
+ anyInt(), anyInt());
+ // We should have requested to goto background restricted level.
+ verify(mBgRestrictionController, times(1)).handleRequestBgRestricted(
+ eq(testPkgName),
+ eq(testUid));
+ // Verify we have the notification posted now because its FGS is invisible.
+ checkNotificationShown(new String[] {testPkgName}, atLeast(1), true);
+ });
+
+ // Pretend notification is back on.
+ mAppFGSTracker.onForegroundServiceNotificationUpdated(
+ testPkgName, testUid, notificationId);
+ // Now we'll prompt the user even it has a FGS with notification.
+ bgPromptFgsWithNotiToBgRestricted.set(true);
+ clearInvocations(mInjector.getAppStandbyInternal());
+ clearInvocations(mBgRestrictionController);
+
runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
new double[]{bgRestrictedThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros,
@@ -785,6 +883,7 @@ public final class BackgroundRestrictionTest {
closeIfNotNull(bgCurrentDrainWindow);
closeIfNotNull(bgCurrentDrainRestrictedBucketThreshold);
closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
+ closeIfNotNull(bgPromptFgsWithNotiToBgRestricted);
}
}
@@ -2460,6 +2559,11 @@ public final class BackgroundRestrictionTest {
return BackgroundRestrictionTest.this.mMediaSessionManager;
}
+ @Override
+ NotificationManagerInternal getNotificationManagerInternal() {
+ return BackgroundRestrictionTest.this.mNotificationManagerInternal;
+ }
+
@Override
long getServiceStartForegroundTimeout() {
return 1_000; // ms