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