Merge "Add support to exempt the battery usage with visible FGS notifications" into tm-dev

This commit is contained in:
Jing Ji
2022-03-19 07:11:33 +00:00
committed by Android (Google) Code Review
5 changed files with 257 additions and 62 deletions

View File

@@ -5796,7 +5796,7 @@
<!-- The types of state where we'll exempt its battery usage during that state.
The state here must be one or a combination of STATE_TYPE_* in BaseAppStateTracker.
-->
<integer name="config_bg_current_drain_exempted_types">9</integer>
<integer name="config_bg_current_drain_exempted_types">25</integer>
<!-- The behavior when an app has the permission ACCESS_BACKGROUND_LOCATION granted,
whether or not the system will use a higher threshold towards its background battery usage

View File

@@ -32,18 +32,22 @@ import static com.android.server.am.BaseAppStateTracker.ONE_HOUR;
import android.annotation.NonNull;
import android.app.ActivityManagerInternal.ForegroundServiceStateListener;
import android.app.IProcessObserver;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ServiceInfo.ForegroundServiceType;
import android.os.Handler;
import android.os.Message;
import android.os.PowerExemptionManager.ReasonCode;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -53,7 +57,6 @@ 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;
@@ -72,11 +75,14 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
private final MyHandler mHandler;
@GuardedBy("mLock")
private final UidProcessMap<ArraySet<Integer>> mFGSNotificationIDs = new UidProcessMap<>();
private final UidProcessMap<SparseBooleanArray> mFGSNotificationIDs = new UidProcessMap<>();
// Unlocked since it's only accessed in single thread.
private final ArrayMap<PackageDurations, Long> mTmpPkgDurations = new ArrayMap<>();
@VisibleForTesting
final NotificationListener mNotificationListener = new NotificationListener();
final IProcessObserver.Stub mProcessObserver = new IProcessObserver.Stub() {
@Override
public void onForegroundActivitiesChanged(int pid, int uid, boolean fg) {
@@ -116,6 +122,8 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
static final int MSG_FOREGROUND_SERVICES_CHANGED = 2;
static final int MSG_FOREGROUND_SERVICES_NOTIFICATION_UPDATED = 3;
static final int MSG_CHECK_LONG_RUNNING_FGS = 4;
static final int MSG_NOTIFICATION_POSTED = 5;
static final int MSG_NOTIFICATION_REMOVED = 6;
private final AppFGSTracker mTracker;
@@ -146,6 +154,12 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
case MSG_CHECK_LONG_RUNNING_FGS:
mTracker.checkLongRunningFgs();
break;
case MSG_NOTIFICATION_POSTED:
mTracker.handleNotificationPosted((String) msg.obj, msg.arg1, msg.arg2);
break;
case MSG_NOTIFICATION_REMOVED:
mTracker.handleNotificationRemoved((String) msg.obj, msg.arg1, msg.arg2);
break;
}
}
}
@@ -223,19 +237,36 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
private void handleForegroundServiceNotificationUpdated(String packageName, int uid,
int notificationId) {
synchronized (mLock) {
SparseBooleanArray notificationIDs = mFGSNotificationIDs.get(uid, packageName);
if (notificationId > 0) {
ArraySet<Integer> notificationIDs = mFGSNotificationIDs.get(uid, packageName);
if (notificationIDs == null) {
notificationIDs = new ArraySet<>();
notificationIDs = new SparseBooleanArray();
mFGSNotificationIDs.put(uid, packageName, notificationIDs);
}
notificationIDs.add(notificationId);
notificationIDs.put(notificationId, false);
} 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);
final int indexOfKey = notificationIDs.indexOfKey(-notificationId);
if (indexOfKey >= 0) {
final boolean wasVisible = notificationIDs.valueAt(indexOfKey);
notificationIDs.removeAt(indexOfKey);
if (notificationIDs.size() == 0) {
mFGSNotificationIDs.remove(uid, packageName);
}
// Walk through the list of FGS notification IDs and see if there are any
// visible ones.
for (int i = notificationIDs.size() - 1; i >= 0; i--) {
if (notificationIDs.valueAt(i)) {
// Still visible, nothing to do.
return;
}
}
if (wasVisible) {
// That was the last visible notification, notify the listeners.
notifyListenersOnStateChange(uid, packageName, false,
SystemClock.elapsedRealtime(),
STATE_TYPE_FGS_WITH_NOTIFICATION);
}
}
}
}
@@ -244,20 +275,74 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
@GuardedBy("mLock")
private boolean hasForegroundServiceNotificationsLocked(String packageName, int uid) {
final ArraySet<Integer> notificationIDs = mFGSNotificationIDs.get(uid, packageName);
if (notificationIDs == null || notificationIDs.isEmpty()) {
final SparseBooleanArray notificationIDs = mFGSNotificationIDs.get(uid, packageName);
if (notificationIDs == null || notificationIDs.size() == 0) {
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)) {
if (notificationIDs.valueAt(i)) {
return true;
}
}
return false;
}
private void handleNotificationPosted(String pkgName, int uid, int notificationId) {
synchronized (mLock) {
final SparseBooleanArray notificationIDs = mFGSNotificationIDs.get(uid, pkgName);
final int indexOfKey;
if (notificationIDs == null
|| (indexOfKey = notificationIDs.indexOfKey(notificationId)) < 0) {
return;
}
if (notificationIDs.valueAt(indexOfKey)) {
// It's already visible.
return;
}
boolean anyVisible = false;
// Walk through the list of FGS notification IDs and see if there are any visible ones.
for (int i = notificationIDs.size() - 1; i >= 0; i--) {
if (notificationIDs.valueAt(i)) {
anyVisible = true;
break;
}
}
notificationIDs.setValueAt(indexOfKey, true);
if (!anyVisible) {
// We didn't have any visible FGS notifications but now we have one,
// let the listeners know.
notifyListenersOnStateChange(uid, pkgName, true, SystemClock.elapsedRealtime(),
STATE_TYPE_FGS_WITH_NOTIFICATION);
}
}
}
private void handleNotificationRemoved(String pkgName, int uid, int notificationId) {
synchronized (mLock) {
final SparseBooleanArray notificationIDs = mFGSNotificationIDs.get(uid, pkgName);
final int indexOfKey;
if (notificationIDs == null
|| (indexOfKey = notificationIDs.indexOfKey(notificationId)) < 0) {
return;
}
if (!notificationIDs.valueAt(indexOfKey)) {
// It's already invisible.
return;
}
notificationIDs.setValueAt(indexOfKey, false);
// Walk through the list of FGS notification IDs and see if there are any visible ones.
for (int i = notificationIDs.size() - 1; i >= 0; i--) {
if (notificationIDs.valueAt(i)) {
// Still visible, nothing to do.
return;
}
}
// Nothing is visible now, let the listeners know.
notifyListenersOnStateChange(uid, pkgName, false, SystemClock.elapsedRealtime(),
STATE_TYPE_FGS_WITH_NOTIFICATION);
}
}
@GuardedBy("mLock")
private void scheduleDurationCheckLocked(long now) {
// Look for the active FGS with longest running time till now.
@@ -374,7 +459,19 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
synchronized (mLock) {
scheduleDurationCheckLocked(SystemClock.elapsedRealtime());
}
try {
mNotificationListener.registerAsSystemService(mContext,
new ComponentName(mContext, NotificationListener.class),
UserHandle.USER_ALL);
} catch (RemoteException e) {
// Intra-process call, should never happen.
}
} else {
try {
mNotificationListener.unregisterAsSystemService();
} catch (RemoteException e) {
// Intra-process call, should never happen.
}
mHandler.removeMessages(MyHandler.MSG_CHECK_LONG_RUNNING_FGS);
synchronized (mLock) {
mPkgEvents.clear();
@@ -436,9 +533,9 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
boolean hasForegroundServiceNotifications(int uid) {
synchronized (mLock) {
final SparseArray<ArrayMap<String, ArraySet<Integer>>> map =
final SparseArray<ArrayMap<String, SparseBooleanArray>> map =
mFGSNotificationIDs.getMap();
final ArrayMap<String, ArraySet<Integer>> pkgs = map.get(uid);
final ArrayMap<String, SparseBooleanArray> pkgs = map.get(uid);
if (pkgs != null) {
for (int i = pkgs.size() - 1; i >= 0; i--) {
if (hasForegroundServiceNotificationsLocked(pkgs.keyAt(i), uid)) {
@@ -463,7 +560,7 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
pw.println("APPS WITH ACTIVE FOREGROUND SERVICES:");
prefix = " " + prefix;
synchronized (mLock) {
final SparseArray<ArrayMap<String, ArraySet<Integer>>> map =
final SparseArray<ArrayMap<String, SparseBooleanArray>> map =
mFGSNotificationIDs.getMap();
if (map.size() == 0) {
pw.print(prefix);
@@ -472,7 +569,7 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
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);
final ArrayMap<String, SparseBooleanArray> pkgs = map.valueAt(i);
for (int j = 0, numOfPkgs = pkgs.size(); j < numOfPkgs; j++) {
final String pkgName = pkgs.keyAt(j);
pw.print(prefix);
@@ -622,6 +719,28 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker<AppFGSPolicy, Pac
}
}
@VisibleForTesting
class NotificationListener extends NotificationListenerService {
@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap map) {
if (DEBUG_BACKGROUND_FGS_TRACKER) {
Slog.i(TAG, "Notification posted: " + sbn);
}
mHandler.obtainMessage(MyHandler.MSG_NOTIFICATION_POSTED,
sbn.getUid(), sbn.getId(), sbn.getPackageName()).sendToTarget();
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
int reason) {
if (DEBUG_BACKGROUND_FGS_TRACKER) {
Slog.i(TAG, "Notification removed: " + sbn);
}
mHandler.obtainMessage(MyHandler.MSG_NOTIFICATION_REMOVED,
sbn.getUid(), sbn.getId(), sbn.getPackageName()).sendToTarget();
}
}
static final class AppFGSPolicy extends BaseAppStateEventsPolicy<AppFGSTracker> {
/**
* Whether or not we should enable the monitoring on abusive FGS.

View File

@@ -64,13 +64,15 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
static final int STATE_TYPE_MEDIA_SESSION = 1;
static final int STATE_TYPE_FGS_MEDIA_PLAYBACK = 1 << 1;
static final int STATE_TYPE_FGS_LOCATION = 1 << 2;
static final int STATE_TYPE_PERMISSION = 1 << 3;
static final int STATE_TYPE_NUM = 4;
static final int STATE_TYPE_FGS_WITH_NOTIFICATION = 1 << 3;
static final int STATE_TYPE_PERMISSION = 1 << 4;
static final int STATE_TYPE_NUM = 5;
static final int STATE_TYPE_INDEX_MEDIA_SESSION = 0;
static final int STATE_TYPE_INDEX_FGS_MEDIA_PLAYBACK = 1;
static final int STATE_TYPE_INDEX_FGS_LOCATION = 2;
static final int STATE_TYPE_INDEX_PERMISSION = 3;
static final int STATE_TYPE_INDEX_FGS_WITH_NOTIFICATION = 3;
static final int STATE_TYPE_INDEX_PERMISSION = 4;
protected final AppRestrictionController mAppRestrictionController;
protected final Injector<T> mInjector;
@@ -129,6 +131,9 @@ public abstract class BaseAppStateTracker<T extends BaseAppStatePolicy> {
case STATE_TYPE_FGS_LOCATION:
sb.append("FGS_LOCATION");
break;
case STATE_TYPE_FGS_WITH_NOTIFICATION:
sb.append("FGS_NOTIFICATION");
break;
case STATE_TYPE_PERMISSION:
sb.append("PERMISSION");
break;

View File

@@ -31,6 +31,7 @@
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission
android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<!-- needed by MasterClearReceiverTest to display a system dialog -->
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>

View File

@@ -59,6 +59,7 @@ import static com.android.server.am.AppPermissionTracker.AppPermissionPolicy;
import static com.android.server.am.AppRestrictionController.STOCK_PM_FLAGS;
import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_FGS_LOCATION;
import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_FGS_MEDIA_PLAYBACK;
import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_FGS_WITH_NOTIFICATION;
import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_MEDIA_SESSION;
import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_PERMISSION;
@@ -113,6 +114,7 @@ import android.os.UidBatteryConsumer;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.service.notification.StatusBarNotification;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Pair;
@@ -566,6 +568,7 @@ public final class BackgroundRestrictionTest {
DeviceConfigSession<Float> bgCurrentDrainBgRestrictedThreshold = null;
DeviceConfigSession<Boolean> bgPromptFgsWithNotiToBgRestricted = null;
DeviceConfigSession<Long> bgNotificationMinInterval = null;
DeviceConfigSession<Integer> bgBatteryExemptionTypes = null;
mBgRestrictionController.addAppBackgroundRestrictionListener(listener);
@@ -627,16 +630,25 @@ public final class BackgroundRestrictionTest {
ConstantsObserver.DEFAULT_BG_ABUSIVE_NOTIFICATION_MINIMAL_INTERVAL_MS);
bgNotificationMinInterval.set(windowMs);
bgBatteryExemptionTypes = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES,
DeviceConfig::getInt,
mContext.getResources().getInteger(
R.integer.config_bg_current_drain_exempted_types));
bgBatteryExemptionTypes.set(0);
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);
mAppFGSTracker.mNotificationListener.onNotificationPosted(new StatusBarNotification(
testPkgName, null, notificationId, null, testUid, testPid,
new Notification(), UserHandle.of(testUser), null, mCurrentTimeMillis), null);
runTestBgCurrentDrainMonitorOnce(listener, stats, uids,
new double[]{restrictBucketThresholdMah - 1, 0},
@@ -915,6 +927,7 @@ public final class BackgroundRestrictionTest {
closeIfNotNull(bgCurrentDrainBgRestrictedThreshold);
closeIfNotNull(bgPromptFgsWithNotiToBgRestricted);
closeIfNotNull(bgNotificationMinInterval);
closeIfNotNull(bgBatteryExemptionTypes);
}
}
@@ -1250,14 +1263,15 @@ public final class BackgroundRestrictionTest {
List<Pair<List<MediaController>, Long>> mediaControllers, List<Long> topStateChanges,
VerificationMode mode) throws Exception {
runExemptionTestOnce(
packageName, uid, pid, serviceType, sleepMs, true, perm, mediaControllers,
packageName, uid, pid, serviceType, sleepMs, true, false, perm, mediaControllers,
topStateChanges, true, true,
() -> checkNotificationShown(new String[] {packageName}, mode, false)
);
}
private void runExemptionTestOnce(String packageName, int uid, int pid,
int serviceType, long sleepMs, boolean stopAfterSleep, String perm,
int serviceType, long sleepMs, boolean stopAfterSleep,
boolean withNotification, String perm,
List<Pair<List<MediaController>, Long>> mediaControllers,
List<Long> topStateChanges, boolean resetFGSTracker, boolean resetController,
RunnableWithException r) throws Exception {
@@ -1302,7 +1316,20 @@ public final class BackgroundRestrictionTest {
FOREGROUND_SERVICE_TYPE_NONE);
}
}
if (withNotification) {
final int notificationId = 1000;
mAppFGSTracker.onForegroundServiceNotificationUpdated(
packageName, uid, notificationId);
final StatusBarNotification noti = new StatusBarNotification(
packageName, null, notificationId, null, uid, pid,
new Notification(), UserHandle.of(UserHandle.getUserId(uid)),
null, mCurrentTimeMillis);
mAppFGSTracker.mNotificationListener.onNotificationPosted(noti, null);
Thread.sleep(sleepMs);
if (stopAfterSleep) {
mAppFGSTracker.mNotificationListener.onNotificationRemoved(noti, null, 0);
}
}
if (perm != null) {
doReturn(PERMISSION_GRANTED)
.when(mPermissionManagerServiceInternal)
@@ -1513,7 +1540,8 @@ public final class BackgroundRestrictionTest {
mContext.getResources().getInteger(
R.integer.config_bg_current_drain_exempted_types));
bgBatteryExemptionTypes.set(STATE_TYPE_MEDIA_SESSION | STATE_TYPE_FGS_MEDIA_PLAYBACK
| STATE_TYPE_FGS_LOCATION | STATE_TYPE_PERMISSION);
| STATE_TYPE_FGS_LOCATION | STATE_TYPE_PERMISSION
| STATE_TYPE_FGS_WITH_NOTIFICATION);
bgPermissionMonitorEnabled = new DeviceConfigSession<>(
DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
@@ -1546,7 +1574,7 @@ public final class BackgroundRestrictionTest {
// Run with a media playback service which starts/stops immediately, we should
// goto the restricted bucket.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true, false,
null, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1556,7 +1584,7 @@ public final class BackgroundRestrictionTest {
// Run with a media playback service with extended time. We should be back to normal.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
null, null, null, listener, stats, uids,
false, null, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false,
@@ -1585,7 +1613,7 @@ public final class BackgroundRestrictionTest {
// Run with a media playback service with extended time, with higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
null, null, null, listener, stats, uids,
false, null, null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah - 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
@@ -1594,7 +1622,7 @@ public final class BackgroundRestrictionTest {
// Run with a media playback service with extended time, with even higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
null, null, null, listener, stats, uids,
false, null, null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
@@ -1607,8 +1635,8 @@ public final class BackgroundRestrictionTest {
// Run with a media session with extended time, with higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false,
null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah - 1, 0},
@@ -1618,8 +1646,8 @@ public final class BackgroundRestrictionTest {
// Run with a media session with extended time, with even higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false,
null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah + 1, 0},
@@ -1635,8 +1663,8 @@ public final class BackgroundRestrictionTest {
// Run with a media session with extended time, with moderate current drain,
// but it ran on the top when the location service is active.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false,
null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
List.of(0L, timeout * 2), listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
@@ -1651,7 +1679,7 @@ public final class BackgroundRestrictionTest {
// Run with a location service with extended time, with higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false,
null, null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah - 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1660,7 +1688,7 @@ public final class BackgroundRestrictionTest {
// Run with a location service with extended time, with even higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false,
null, null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1675,7 +1703,7 @@ public final class BackgroundRestrictionTest {
// Run with a location service with extended time, with moderate current drain,
// but it ran on the top when the location service is active.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false,
null, null, List.of(0L, timeout * 2), listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1692,7 +1720,7 @@ public final class BackgroundRestrictionTest {
// Run with bg location permission, with moderate current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, 0, false,
FOREGROUND_SERVICE_TYPE_NONE, 0, false, false,
ACCESS_BACKGROUND_LOCATION, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah - 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1701,7 +1729,7 @@ public final class BackgroundRestrictionTest {
// Run with bg location permission, with a bit higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, 0, false,
FOREGROUND_SERVICE_TYPE_NONE, 0, false, false,
ACCESS_BACKGROUND_LOCATION, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1718,7 +1746,7 @@ public final class BackgroundRestrictionTest {
// Run with bg location permission, with higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, 0, false,
FOREGROUND_SERVICE_TYPE_NONE, 0, false, false,
ACCESS_BACKGROUND_LOCATION , null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah - 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1727,7 +1755,7 @@ public final class BackgroundRestrictionTest {
// Run with bg location permission, with even higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, 0, false,
FOREGROUND_SERVICE_TYPE_NONE, 0, false, false,
ACCESS_BACKGROUND_LOCATION , null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1749,7 +1777,7 @@ public final class BackgroundRestrictionTest {
// Run with a media playback service which starts/stops immediately, we should
// goto the restricted bucket.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, 0, true, false,
null, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1759,7 +1787,7 @@ public final class BackgroundRestrictionTest {
// Run with a media playback service with extended time. We should be back to normal.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
null, null, null, listener, stats, uids,
false, null, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false,
@@ -1791,7 +1819,7 @@ public final class BackgroundRestrictionTest {
// Run with a media playback service with extended time, with higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
null, null, null, listener, stats, uids,
false, null, null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah - 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
@@ -1802,7 +1830,7 @@ public final class BackgroundRestrictionTest {
// playback.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK, bgMediaPlaybackMinDuration * 2, false,
null, null, null, listener, stats, uids,
false, null, null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah + 100, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false,
@@ -1817,7 +1845,7 @@ public final class BackgroundRestrictionTest {
// Run with coarse location permission, with high current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, 0, false,
FOREGROUND_SERVICE_TYPE_NONE, 0, false, false,
ACCESS_COARSE_LOCATION, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1831,7 +1859,7 @@ public final class BackgroundRestrictionTest {
// Run with fine location permission, with high current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, 0, false,
FOREGROUND_SERVICE_TYPE_NONE, 0, false, false,
ACCESS_FINE_LOCATION, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1845,8 +1873,8 @@ public final class BackgroundRestrictionTest {
// Run with a media session with extended time, with higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false,
null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah - 1, 0},
@@ -1858,8 +1886,8 @@ public final class BackgroundRestrictionTest {
// it still should stay in the current restriction level as we exempt the media
// session.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, null,
List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false, false,
null, List.of(Pair.create(createMediaControllers(new String[] {testPkgName1},
new int[] {testUid1}), bgMediaPlaybackMinDuration * 2)),
null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah + 100, 0},
@@ -1867,9 +1895,51 @@ public final class BackgroundRestrictionTest {
true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
null, windowMs, initialBg, initialFgs, initialFg, initialCached);
// Set the policy to exempt fgs with notifications.
bgBatteryExemptionTypes.set(STATE_TYPE_FGS_WITH_NOTIFICATION);
// Start over.
resetBgRestrictionController();
setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros);
mAppBatteryPolicy.reset();
// Run with a FGS with notification posted/removed immediately, we should
// goto the restricted bucket.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, 0, true, true,
null, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros,
false, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true,
null, windowMs, null, null, null);
// Run with a service with notification for extended time. We should be back to normal.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_NONE, bgMediaPlaybackMinDuration * 2, false,
true, null, null, null, listener, stats, uids,
new double[]{restrictBucketThresholdMah + 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros,
true, RESTRICTION_LEVEL_ADAPTIVE_BUCKET, timeout, false,
() -> {
// A user interaction will bring it back to normal.
mIdleStateListener.onUserInteractionStarted(testPkgName1,
UserHandle.getUserId(testUid1));
waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
// It should have been back to normal.
listener.verify(timeout, testUid1, testPkgName1,
RESTRICTION_LEVEL_ADAPTIVE_BUCKET);
verify(mInjector.getAppStandbyInternal(), times(1)).maybeUnrestrictApp(
eq(testPkgName1),
eq(UserHandle.getUserId(testUid1)),
eq(REASON_MAIN_FORCED_BY_SYSTEM),
eq(REASON_SUB_FORCED_SYSTEM_FLAG_ABUSE),
eq(REASON_MAIN_USAGE),
eq(REASON_SUB_USAGE_USER_INTERACTION));
}, windowMs, null, null, null);
// Set the policy to exempt all.
bgBatteryExemptionTypes.set(STATE_TYPE_MEDIA_SESSION | STATE_TYPE_FGS_MEDIA_PLAYBACK
| STATE_TYPE_FGS_LOCATION | STATE_TYPE_PERMISSION);
| STATE_TYPE_FGS_LOCATION | STATE_TYPE_PERMISSION
| STATE_TYPE_FGS_WITH_NOTIFICATION);
// Start over.
resetBgRestrictionController();
@@ -1878,7 +1948,7 @@ public final class BackgroundRestrictionTest {
// Run with a location service with extended time, with higher current drain.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false,
null, null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah - 1, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1888,7 +1958,7 @@ public final class BackgroundRestrictionTest {
// Run with a location service with extended time, with even higher current drain.
// it still should stay in the current restriction level as we exempt the location.
runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false,
FOREGROUND_SERVICE_TYPE_LOCATION, bgMediaPlaybackMinDuration * 2, false, false,
null, null, null, listener, stats, uids,
new double[]{restrictBucketHighThresholdMah + 100, 0},
new double[]{0, restrictBucketThresholdMah - 1}, zeros, zeros,
@@ -1913,8 +1983,8 @@ public final class BackgroundRestrictionTest {
}
private void runTestBgCurrentDrainExemptionOnce(String packageName, int uid, int pid,
int serviceType, long sleepMs, boolean stopAfterSleep, String perm,
List<Pair<List<MediaController>, Long>> mediaControllers,
int serviceType, long sleepMs, boolean stopAfterSleep, boolean withNotification,
String perm, List<Pair<List<MediaController>, Long>> mediaControllers,
List<Long> topStateChanges, TestAppRestrictionLevelListener listener,
BatteryUsageStats stats, int[] uids, double[] bg, double[] fgs, double[] fg,
double[] cached, boolean expectingTimeout, int expectingLevel, long timeout,
@@ -1941,7 +2011,7 @@ public final class BackgroundRestrictionTest {
}
waitForIdleHandler(mBgRestrictionController.getBackgroundHandler());
runExemptionTestOnce(
packageName, uid, pid, serviceType, sleepMs, stopAfterSleep,
packageName, uid, pid, serviceType, sleepMs, stopAfterSleep, withNotification,
perm, mediaControllers, topStateChanges, resetFGSTracker, false,
() -> {
clearInvocations(mInjector.getAppStandbyInternal());