Don't prompt on bg abuse if the app has a FGS with notification

Add a new config to control whether or not to prompt the user when
the system detects a background abusive app and it has a foreground
service with notification shown. By default it's OFF, meaning if
the background abusive app has a foreground service and its
notification is still showing, no prompt to the user in this case.

Bug: 200326767
Bug: 203105544
Test: atest FrameworksMockingServicesTests:BackgroundRestrictionTest
Change-Id: Ic611090e3152117a2379d902fc32416e11c3e197
This commit is contained in:
Jing Ji
2022-02-07 00:56:27 -08:00
parent ed6be44189
commit 8ddcda8bec
10 changed files with 351 additions and 15 deletions

View File

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

View File

@@ -5758,4 +5758,10 @@
current drain threshold.
-->
<integer name="config_bg_current_drain_location_min_duration">1800</integer>
<!-- 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. True - we'll
show the prompt to user, False - we'll not show it.
-->
<bool name="config_bg_prompt_fgs_with_noti_to_bg_restricted">false</bool>
</resources>

View File

@@ -4743,4 +4743,5 @@
<java-symbol type="array" name="config_bg_current_drain_high_threshold_to_bg_restricted" />
<java-symbol type="integer" name="config_bg_current_drain_media_playback_min_duration" />
<java-symbol type="integer" name="config_bg_current_drain_location_min_duration" />
<java-symbol type="bool" name="config_bg_prompt_fgs_with_noti_to_bg_restricted" />
</resources>

View File

@@ -1454,16 +1454,18 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy>
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;
}
}
}

View File

@@ -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<AppFGSPolicy, Pac
private final MyHandler mHandler;
@GuardedBy("mLock")
private final UidProcessMap<ArraySet<Integer>> mFGSNotificationIDs = new UidProcessMap<>();
// Unlocked since it's only accessed in single thread.
private final ArrayMap<PackageDurations, Long> mTmpPkgDurations = new ArrayMap<>();
@@ -100,11 +105,19 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
: MyHandler.MSG_FOREGROUND_SERVICES_STOPPED, pid, uid, packageName).sendToTarget();
}
@Override
public void onForegroundServiceNotificationUpdated(String packageName, int uid,
int foregroundId) {
mHandler.obtainMessage(MyHandler.MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED,
uid, foregroundId, packageName).sendToTarget();
}
private static class MyHandler extends Handler {
static final int MSG_FOREGROUND_SERVICES_STARTED = 0;
static final int MSG_FOREGROUND_SERVICES_STOPPED = 1;
static final int MSG_FOREGROUND_SERVICES_CHANGED = 2;
static final int MSG_CHECK_LONG_RUNNING_FGS = 3;
static final int MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED = 3;
static final int MSG_CHECK_LONG_RUNNING_FGS = 4;
private final AppFGSTracker mTracker;
@@ -128,6 +141,10 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
mTracker.handleForegroundServicesChanged(
(String) msg.obj, msg.arg1, msg.arg2);
break;
case MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED:
mTracker.handleForegroundServiceNotificationUpdated(
(String) msg.obj, msg.arg1, msg.arg2);
break;
case MSG_CHECK_LONG_RUNNING_FGS:
mTracker.checkLongRunningFgs();
break;
@@ -205,6 +222,44 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
}
}
private void handleForegroundServiceNotificationUpdated(String packageName, int uid,
int notificationId) {
synchronized (mLock) {
if (notificationId > 0) {
ArraySet<Integer> 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<Integer> 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<Integer> 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<AppFGSPolicy, Pac
}
}
boolean hasForegroundServiceNotifications(String packageName, int uid) {
synchronized (mLock) {
return hasForegroundServiceNotificationsLocked(packageName, uid);
}
}
boolean hasForegroundServiceNotifications(int uid) {
synchronized (mLock) {
final SparseArray<ArrayMap<String, ArraySet<Integer>>> map =
mFGSNotificationIDs.getMap();
final ArrayMap<String, ArraySet<Integer>> 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<AppFGSPolicy, Pac
super.dump(pw, " " + prefix);
}
@Override
void dumpOthers(PrintWriter pw, String prefix) {
pw.print(prefix);
pw.println("APPS WITH ACTIVE FOREGROUND SERVICES:");
prefix = " " + prefix;
synchronized (mLock) {
final SparseArray<ArrayMap<String, ArraySet<Integer>>> 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<String, ArraySet<Integer>> 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.
*/

View File

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

View File

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

View File

@@ -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<T extends BaseAppStatePolicy> {
AppOpsManager mAppOpsManager;
MediaSessionManager mMediaSessionManager;
RoleManager mRoleManager;
NotificationManagerInternal mNotificationManagerInternal;
void setPolicy(T policy) {
mAppStatePolicy = policy;
@@ -201,6 +203,8 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
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<T extends BaseAppStatePolicy> {
RoleManager getRoleManager() {
return mRoleManager;
}
NotificationManagerInternal getNotificationManagerInternal() {
return mNotificationManagerInternal;
}
}
}

View File

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

View File

@@ -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<Boolean> bgCurrentDrainMonitor = null;
DeviceConfigSession<Long> bgCurrentDrainWindow = null;
DeviceConfigSession<Float> bgCurrentDrainRestrictedBucketThreshold = null;
DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
DeviceConfigSession<Boolean> 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