From 4838c7cac9641f2b88997bdef6e011f2e98cb74c Mon Sep 17 00:00:00 2001 From: Jing Ji Date: Mon, 7 Feb 2022 20:19:11 -0800 Subject: [PATCH] Exclude the bg battery usage for apps with access to fine location If an app has the permission ACCESS_FINE_LOCATION granted, its background battery usage during that wil be excluded from its overall background battery usage in AppBatteryTracker. Also turning OFF the exemption for apps with ACCESS_BACKGROUND_LOCATION only, as this doesn't mean it has access to locations. Bug: 200326767 Bug: 203105544 Test: atest FrameworksMockingServicesTests:BackgroundRestrictionTest Change-Id: Ibbd6dbb555c34c1839be6d21c62de00012832de6 --- core/res/res/values/config.xml | 11 + core/res/res/values/symbols.xml | 2 + .../server/am/AppBatteryExemptionTracker.java | 48 ++- .../android/server/am/AppBatteryTracker.java | 72 +++- .../com/android/server/am/AppFGSTracker.java | 16 +- .../server/am/AppMediaSessionTracker.java | 14 +- .../server/am/AppPermissionTracker.java | 327 ++++++++++++++++++ .../server/am/AppRestrictionController.java | 12 +- .../am/BaseAppStateDurationsTracker.java | 28 -- .../server/am/BaseAppStateTracker.java | 85 +++++ .../server/am/BackgroundRestrictionTest.java | 180 +++++++++- 11 files changed, 711 insertions(+), 84 deletions(-) create mode 100644 services/core/java/com/android/server/am/AppPermissionTracker.java diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b2c016f0dcbc9..b7e349cc4813f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5764,4 +5764,15 @@ show the prompt to user, False - we'll not show it. --> false + + + 9 + + + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 538a5db46df6a..8cac7bf556ae4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4744,4 +4744,6 @@ + + diff --git a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java index 3c780aaf25586..d1cf0049d1468 100644 --- a/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryExemptionTracker.java @@ -20,7 +20,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.am.AppBatteryTracker.BATTERY_USAGE_NONE; import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX; -import static com.android.server.am.BaseAppStateDurationsTracker.EVENT_NUM; +import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_NUM; import android.annotation.NonNull; import android.annotation.Nullable; @@ -36,9 +36,9 @@ import com.android.server.am.AppBatteryExemptionTracker.UidBatteryStates; import com.android.server.am.AppBatteryTracker.AppBatteryPolicy; import com.android.server.am.AppBatteryTracker.BatteryUsage; import com.android.server.am.AppBatteryTracker.ImmutableBatteryUsage; -import com.android.server.am.BaseAppStateDurationsTracker.EventListener; import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent; import com.android.server.am.BaseAppStateTracker.Injector; +import com.android.server.am.BaseAppStateTracker.StateListener; import java.io.PrintWriter; import java.lang.reflect.Constructor; @@ -57,13 +57,13 @@ import java.util.LinkedList; */ final class AppBatteryExemptionTracker extends BaseAppStateDurationsTracker - implements BaseAppStateEvents.Factory, EventListener { + implements BaseAppStateEvents.Factory, StateListener { private static final String TAG = TAG_WITH_CLASS_NAME ? "AppBatteryExemptionTracker" : TAG_AM; private static final boolean DEBUG_BACKGROUND_BATTERY_EXEMPTION_TRACKER = false; // As it's a UID-based tracker, anywhere which requires a package name, use this default name. - private static final String DEFAULT_NAME = ""; + static final String DEFAULT_NAME = ""; AppBatteryExemptionTracker(Context context, AppRestrictionController controller) { this(context, controller, null, null); @@ -80,9 +80,7 @@ final class AppBatteryExemptionTracker void onSystemReady() { super.onSystemReady(); mAppRestrictionController.forEachTracker(tracker -> { - if (tracker instanceof BaseAppStateDurationsTracker) { - ((BaseAppStateDurationsTracker) tracker).registerEventListener(this); - } + tracker.registerStateListener(this); }); } @@ -97,19 +95,20 @@ final class AppBatteryExemptionTracker } @Override - public void onNewEvent(int uid, String packageName, boolean start, long now, int eventType) { + public void onStateChange(int uid, String packageName, boolean start, long now, int stateType) { if (!mInjector.getPolicy().isEnabled()) { return; } final ImmutableBatteryUsage batteryUsage = mAppRestrictionController .getUidBatteryUsage(uid); + final int stateTypeIndex = stateTypeToIndex(stateType); synchronized (mLock) { UidBatteryStates pkg = mPkgEvents.get(uid, DEFAULT_NAME); if (pkg == null) { pkg = createAppStateEvents(uid, DEFAULT_NAME); mPkgEvents.put(uid, DEFAULT_NAME, pkg); } - pkg.addEvent(start, now, batteryUsage, eventType); + pkg.addEvent(start, now, batteryUsage, stateTypeIndex); } } @@ -125,7 +124,8 @@ final class AppBatteryExemptionTracker * @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. */ - ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now) { + ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now, + int types) { if (!mInjector.getPolicy().isEnabled()) { return BATTERY_USAGE_NONE; } @@ -135,7 +135,7 @@ final class AppBatteryExemptionTracker if (pkg == null) { return BATTERY_USAGE_NONE; } - result = pkg.getBatteryUsageSince(since, now); + result = pkg.getBatteryUsageSince(since, now, types); } if (!result.second.isEmpty()) { // We have an open event (just start, no stop), get the battery usage till now. @@ -149,7 +149,7 @@ final class AppBatteryExemptionTracker static final class UidBatteryStates extends BaseAppStateDurations { UidBatteryStates(int uid, @NonNull String tag, @NonNull MaxTrackingDurationConfig maxTrackingDurationConfig) { - super(uid, DEFAULT_NAME, EVENT_NUM, tag, maxTrackingDurationConfig); + super(uid, DEFAULT_NAME, STATE_TYPE_NUM, tag, maxTrackingDurationConfig); } UidBatteryStates(@NonNull UidBatteryStates other) { @@ -160,7 +160,7 @@ final class AppBatteryExemptionTracker * @param start {@code true} if it's a start event. * @param now The timestamp when this event occurred. * @param batteryUsage The background current drain since the system boots. - * @param eventType One of EVENT_TYPE_* defined in the class BaseAppStateDurationsTracker. + * @param eventType One of STATE_TYPE_INDEX_* defined in the class BaseAppStateTracker. */ void addEvent(boolean start, long now, ImmutableBatteryUsage batteryUsage, int eventType) { if (start) { @@ -184,17 +184,6 @@ final class AppBatteryExemptionTracker return mEvents[eventType] != null ? mEvents[eventType].peekLast() : null; } - /** - * @return The pair of bg battery usage of given duration; the first value in the pair - * is the aggregated battery usage of all event pairs in this duration; while - * the second value is the battery usage since the system boots, if there is - * an open event(just start, no stop) at the end of the duration. - */ - Pair getBatteryUsageSince(long since, - long now, int eventType) { - return getBatteryUsageSince(since, now, mEvents[eventType]); - } - private Pair getBatteryUsageSince(long since, long now, LinkedList events) { if (events == null || events.size() == 0) { @@ -217,13 +206,18 @@ final class AppBatteryExemptionTracker } /** - * @return The aggregated battery usage amongst all the event types we're tracking. + * @return The pair of bg battery usage of given duration; the first value in the pair + * is the aggregated battery usage of selected events in this duration; while + * the second value is the battery usage since the system boots, if there is + * an open event(just start, no stop) at the end of the duration. */ Pair getBatteryUsageSince(long since, - long now) { + long now, int types) { LinkedList result = new LinkedList<>(); for (int i = 0; i < mEvents.length; i++) { - result = add(result, mEvents[i]); + if ((types & stateIndexToType(i)) != 0) { + result = add(result, mEvents[i]); + } } return getBatteryUsageSince(since, now, result); } diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 37ad5f9bb57a4..655e309820918 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -325,7 +325,8 @@ final class AppBatteryTracker extends BaseAppStateTracker final int uid = uidConsumers.keyAt(i); final ImmutableBatteryUsage actualUsage = uidConsumers.valueAt(i); final ImmutableBatteryUsage exemptedUsage = mAppRestrictionController - .getUidBatteryExemptedUsageSince(uid, since, now); + .getUidBatteryExemptedUsageSince(uid, since, now, + bgPolicy.mBgCurrentDrainExemptedTypes); // It's possible the exemptedUsage could be larger than actualUsage, // as the former one is an approximate value. final BatteryUsage bgUsage = actualUsage.mutate() @@ -656,7 +657,8 @@ final class AppBatteryTracker extends BaseAppStateTracker final BatteryUsage bgUsage = uidConsumers.valueAt(i) .calcPercentage(uid, bgPolicy); final BatteryUsage exemptedUsage = mAppRestrictionController - .getUidBatteryExemptedUsageSince(uid, since, now) + .getUidBatteryExemptedUsageSince(uid, since, now, + bgPolicy.mBgCurrentDrainExemptedTypes) .calcPercentage(uid, bgPolicy); final BatteryUsage reportedUsage = new BatteryUsage(bgUsage) .subtract(exemptedUsage) @@ -1026,6 +1028,21 @@ final class AppBatteryTracker extends BaseAppStateTracker static final String KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS = DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_power_components"; + /** + * The types of state where we'll exempt its battery usage when it's in that state. + * The state here must be one or a combination of STATE_TYPE_* in BaseAppStateTracker. + */ + static final String KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_exempted_types"; + + /** + * 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 because of it. + */ + static final String KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "current_drain_high_threshold_by_bg_location"; + /** * Default value to the {@link #INDEX_REGULAR_CURRENT_DRAIN_THRESHOLD} of * the {@link #mBgCurrentDrainRestrictedBucketThreshold}. @@ -1088,6 +1105,16 @@ final class AppBatteryTracker extends BaseAppStateTracker final int mDefaultBgCurrentDrainPowerComponent; + /** + * Default value to {@link #mBgCurrentDrainExmptedTypes}. + **/ + final int mDefaultBgCurrentDrainExemptedTypes; + + /** + * Default value to {@link #mBgCurrentDrainHighThresholdByBgLocation}. + */ + final boolean mDefaultBgCurrentDrainHighThresholdByBgLocation; + /** * The index to {@link #mBgCurrentDrainRestrictedBucketThreshold} * and {@link #mBgCurrentDrainBgRestrictedThreshold}. @@ -1145,6 +1172,16 @@ final class AppBatteryTracker extends BaseAppStateTracker volatile Dimensions[] mBatteryDimensions; + /** + * @see #KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES. + */ + volatile int mBgCurrentDrainExemptedTypes; + + /** + * @see #KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION. + */ + volatile boolean mBgCurrentDrainHighThresholdByBgLocation; + /** * The capacity of the battery when fully charged in mAh. */ @@ -1201,6 +1238,10 @@ final class AppBatteryTracker extends BaseAppStateTracker R.integer.config_bg_current_drain_types_to_bg_restricted); mDefaultBgCurrentDrainPowerComponent = resources.getInteger( R.integer.config_bg_current_drain_power_components); + mDefaultBgCurrentDrainExemptedTypes = resources.getInteger( + R.integer.config_bg_current_drain_exempted_types); + mDefaultBgCurrentDrainHighThresholdByBgLocation = resources.getBoolean( + R.bool.config_bg_current_drain_high_threshold_by_bg_location); mBgCurrentDrainRestrictedBucketThreshold[0] = mDefaultBgCurrentDrainRestrictedBucket; mBgCurrentDrainRestrictedBucketThreshold[1] = @@ -1230,6 +1271,7 @@ final class AppBatteryTracker extends BaseAppStateTracker switch (name) { case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_RESTRICTED_BUCKET: case KEY_BG_CURRENT_DRAIN_THRESHOLD_TO_BG_RESTRICTED: + case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION: case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_RESTRICTED_BUCKET: case KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_TO_BG_RESTRICTED: case KEY_BG_CURRENT_DRAIN_TYPES_TO_RESTRICTED_BUCKET: @@ -1249,6 +1291,9 @@ final class AppBatteryTracker extends BaseAppStateTracker case KEY_BG_CURRENT_DRAIN_EVENT_DURATION_BASED_THRESHOLD_ENABLED: updateCurrentDrainEventDurationBasedThresholdEnabled(); break; + case KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES: + updateCurrentDrainExemptedTypes(); + break; default: super.onPropertiesChanged(name); break; @@ -1305,6 +1350,10 @@ final class AppBatteryTracker extends BaseAppStateTracker mBatteryDimensions[i] = new Dimensions(mBgCurrentDrainPowerComponents, i); } } + mBgCurrentDrainHighThresholdByBgLocation = + DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION, + mDefaultBgCurrentDrainHighThresholdByBgLocation); } private void updateCurrentDrainWindow() { @@ -1335,6 +1384,13 @@ final class AppBatteryTracker extends BaseAppStateTracker mDefaultBgCurrentDrainEventDurationBasedThresholdEnabled); } + private void updateCurrentDrainExemptedTypes() { + mBgCurrentDrainExemptedTypes = DeviceConfig.getInt( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES, + mDefaultBgCurrentDrainExemptedTypes); + } + @Override public void onSystemReady() { mBatteryFullChargeMah = @@ -1345,6 +1401,7 @@ final class AppBatteryTracker extends BaseAppStateTracker updateCurrentDrainMediaPlaybackMinDuration(); updateCurrentDrainLocationMinDuration(); updateCurrentDrainEventDurationBasedThresholdEnabled(); + updateCurrentDrainExemptedTypes(); } @Override @@ -1507,6 +1564,9 @@ final class AppBatteryTracker extends BaseAppStateTracker } private boolean hasLocation(int uid, long now, long window) { + if (!mBgCurrentDrainHighThresholdByBgLocation) { + return false; + } final AppRestrictionController controller = mTracker.mAppRestrictionController; if (mInjector.getPermissionManagerServiceInternal().checkUidPermission( uid, ACCESS_BACKGROUND_LOCATION) == PERMISSION_GRANTED) { @@ -1620,6 +1680,14 @@ final class AppBatteryTracker extends BaseAppStateTracker pw.print(KEY_BG_CURRENT_DRAIN_POWER_COMPONENTS); pw.print('='); pw.println(mBgCurrentDrainPowerComponents); + pw.print(prefix); + pw.print(KEY_BG_CURRENT_DRAIN_EXEMPTED_TYPES); + pw.print('='); + pw.println(BaseAppStateTracker.stateTypesToString(mBgCurrentDrainExemptedTypes)); + pw.print(prefix); + pw.print(KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION); + pw.print('='); + pw.println(mBgCurrentDrainHighThresholdByBgLocation); pw.print(prefix); pw.println("Excessive current drain detected:"); diff --git a/services/core/java/com/android/server/am/AppFGSTracker.java b/services/core/java/com/android/server/am/AppFGSTracker.java index de554fe3f1149..b92b856828dd3 100644 --- a/services/core/java/com/android/server/am/AppFGSTracker.java +++ b/services/core/java/com/android/server/am/AppFGSTracker.java @@ -543,7 +543,7 @@ final class AppFGSTracker extends BaseAppStateDurationsTracker + implements OnPermissionsChangedListener { + static final String TAG = TAG_WITH_CLASS_NAME ? "AppPermissionTracker" : TAG_AM; + + static final boolean DEBUG_PERMISSION_TRACKER = false; + + private final MyHandler mHandler; + + @GuardedBy("mLock") + private SparseArray> mUidGrantedPermissionsInMonitor = new SparseArray<>(); + + AppPermissionTracker(Context context, AppRestrictionController controller) { + this(context, controller, null, null); + } + + AppPermissionTracker(Context context, AppRestrictionController controller, + Constructor> injector, Object outerContext) { + super(context, controller, injector, outerContext); + mHandler = new MyHandler(this); + mInjector.setPolicy(new AppPermissionPolicy(mInjector, this)); + } + + @Override + public void onPermissionsChanged(int uid) { + mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_CHANGED, uid, 0).sendToTarget(); + } + + private void handlePermissionsInit() { + final int[] allUsers = mInjector.getUserManagerInternal().getUserIds(); + final PackageManagerInternal pmi = mInjector.getPackageManagerInternal(); + final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal(); + final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + for (int userId : allUsers) { + final List apps = pmi.getInstalledApplications(0, userId, SYSTEM_UID); + if (apps == null) { + continue; + } + synchronized (mLock) { + final SparseArray> uidPerms = mUidGrantedPermissionsInMonitor; + final long now = SystemClock.elapsedRealtime(); + for (int i = 0, size = apps.size(); i < size; i++) { + final ApplicationInfo ai = apps.get(i); + for (String permission : permissions) { + if (pm.checkUidPermission(ai.uid, permission) != PERMISSION_GRANTED) { + continue; + } + ArraySet grantedPermissions = uidPerms.get(ai.uid); + if (grantedPermissions == null) { + grantedPermissions = new ArraySet(); + uidPerms.put(ai.uid, grantedPermissions); + } + grantedPermissions.add(permission); + notifyListenersOnStateChange(ai.uid, DEFAULT_NAME, true, now, + STATE_TYPE_PERMISSION); + } + } + } + } + } + + private void handlePermissionsDestroy() { + synchronized (mLock) { + final SparseArray> uidPerms = mUidGrantedPermissionsInMonitor; + final long now = SystemClock.elapsedRealtime(); + for (int i = 0, size = uidPerms.size(); i < size; i++) { + final int uid = uidPerms.keyAt(i); + final ArraySet grantedPermissions = uidPerms.valueAt(i); + for (int j = 0, numOfPerms = grantedPermissions.size(); j < numOfPerms; j++) { + notifyListenersOnStateChange(uid, DEFAULT_NAME, false, now, + STATE_TYPE_PERMISSION); + } + } + uidPerms.clear(); + } + } + + private void handlePermissionsChanged(int uid) { + final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + if (permissions != null && permissions.length > 0) { + synchronized (mLock) { + handlePermissionsChangedLocked(uid); + } + } + } + + @GuardedBy("mLock") + private void handlePermissionsChangedLocked(int uid) { + final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal(); + final int index = mUidGrantedPermissionsInMonitor.indexOfKey(uid); + ArraySet grantedPermissions = index >= 0 + ? mUidGrantedPermissionsInMonitor.valueAt(index) : null; + final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + final long now = SystemClock.elapsedRealtime(); + for (String permission: permissions) { + boolean granted = pm.checkUidPermission(uid, permission) == PERMISSION_GRANTED; + if (DEBUG_PERMISSION_TRACKER) { + Slog.i(TAG, UserHandle.formatUid(uid) + " " + permission + "=" + granted); + } + boolean changed = false; + if (granted) { + if (grantedPermissions == null) { + grantedPermissions = new ArraySet<>(); + mUidGrantedPermissionsInMonitor.put(uid, grantedPermissions); + } + changed = grantedPermissions.add(permission); + } else if (grantedPermissions != null) { + changed = grantedPermissions.remove(permission); + if (grantedPermissions.isEmpty()) { + mUidGrantedPermissionsInMonitor.removeAt(index); + } + } + if (changed) { + notifyListenersOnStateChange(uid, DEFAULT_NAME, granted, now, + STATE_TYPE_PERMISSION); + } + } + } + + private static class MyHandler extends Handler { + static final int MSG_PERMISSIONS_INIT = 0; + static final int MSG_PERMISSIONS_DESTROY = 1; + static final int MSG_PERMISSIONS_CHANGED = 2; + + private @NonNull AppPermissionTracker mTracker; + + MyHandler(@NonNull AppPermissionTracker tracker) { + super(tracker.mBgHandler.getLooper()); + mTracker = tracker; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PERMISSIONS_INIT: + mTracker.handlePermissionsInit(); + break; + case MSG_PERMISSIONS_DESTROY: + mTracker.handlePermissionsDestroy(); + break; + case MSG_PERMISSIONS_CHANGED: + mTracker.handlePermissionsChanged(msg.arg1); + break; + } + } + } + + private void onPermissionTrackerEnabled(boolean enabled) { + final PermissionManager pm = mInjector.getPermissionManager(); + if (enabled) { + pm.addOnPermissionsChangeListener(this); + mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_INIT).sendToTarget(); + } else { + pm.removeOnPermissionsChangeListener(this); + mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_DESTROY).sendToTarget(); + } + } + + @Override + void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.println("APP PERMISSIONS TRACKER:"); + final String[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); + final String prefixMore = " " + prefix; + final String prefixMoreMore = " " + prefixMore; + for (String permission : permissions) { + pw.print(prefixMore); + pw.print(permission); + pw.println(':'); + synchronized (mLock) { + final SparseArray> uidPerms = mUidGrantedPermissionsInMonitor; + pw.print(prefixMoreMore); + pw.print('['); + boolean needDelimiter = false; + for (int i = 0, size = uidPerms.size(); i < size; i++) { + if (uidPerms.valueAt(i).contains(permission)) { + if (needDelimiter) { + pw.print(','); + } + needDelimiter = true; + pw.print(UserHandle.formatUid(uidPerms.keyAt(i))); + } + } + pw.println(']'); + } + } + super.dump(pw, prefix); + } + + static final class AppPermissionPolicy extends BaseAppStatePolicy { + /** + * Whether or not we should enable the monitoring on app permissions. + */ + static final String KEY_BG_PERMISSION_MONITOR_ENABLED = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "permission_monitor_enabled"; + + /** + * The names of the permissions we're monitoring its changes. + */ + static final String KEY_BG_PERMISSIONS_IN_MONITOR = + DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "permission_in_monitor"; + + /** + * Default value to {@link #mTrackerEnabled}. + */ + static final boolean DEFAULT_BG_PERMISSION_MONITOR_ENABLED = true; + + /** + * Default value to {@link #mBgPermissionsInMonitor}. + */ + static final String[] DEFAULT_BG_PERMISSIONS_IN_MONITOR = new String[] { + ACCESS_FINE_LOCATION, + }; + + /** + * @see #KEY_BG_PERMISSIONS_IN_MONITOR. + */ + volatile String[] mBgPermissionsInMonitor = DEFAULT_BG_PERMISSIONS_IN_MONITOR; + + AppPermissionPolicy(@NonNull Injector injector, @NonNull AppPermissionTracker tracker) { + super(injector, tracker, KEY_BG_PERMISSION_MONITOR_ENABLED, + DEFAULT_BG_PERMISSION_MONITOR_ENABLED); + } + + @Override + public void onSystemReady() { + super.onSystemReady(); + updateBgPermissionsInMonitor(); + } + + @Override + public void onPropertiesChanged(String name) { + switch (name) { + case KEY_BG_PERMISSIONS_IN_MONITOR: + updateBgPermissionsInMonitor(); + break; + default: + super.onPropertiesChanged(name); + break; + } + } + + String[] getBgPermissionsInMonitor() { + return mBgPermissionsInMonitor; + } + + private void updateBgPermissionsInMonitor() { + final String config = DeviceConfig.getString( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + KEY_BG_PERMISSIONS_IN_MONITOR, + null); + mBgPermissionsInMonitor = config != null + ? config.split(",") : DEFAULT_BG_PERMISSIONS_IN_MONITOR; + } + + @Override + public void onTrackerEnabled(boolean enabled) { + mTracker.onPermissionTrackerEnabled(enabled); + } + + @Override + void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.println("APP PERMISSION TRACKER POLICY SETTINGS:"); + prefix = " " + prefix; + super.dump(pw, prefix); + pw.print(prefix); + pw.print(KEY_BG_PERMISSIONS_IN_MONITOR); + pw.print('='); + pw.println(Arrays.toString(mBgPermissionsInMonitor)); + } + } +} diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 6b571935a9b4d..376709c42ddec 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -1141,9 +1141,10 @@ public final class AppRestrictionController { * @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. */ - ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now) { + ImmutableBatteryUsage getUidBatteryExemptedUsageSince(int uid, long since, long now, + int types) { return mInjector.getAppBatteryExemptionTracker() - .getUidBatteryExemptedUsageSince(uid, since, now); + .getUidBatteryExemptedUsageSince(uid, since, now, types); } /** @@ -1944,6 +1945,7 @@ public final class AppRestrictionController { private AppBatteryExemptionTracker mAppBatteryExemptionTracker; private AppFGSTracker mAppFGSTracker; private AppMediaSessionTracker mAppMediaSessionTracker; + private AppPermissionTracker mAppPermissionTracker; private TelephonyManager mTelephonyManager; Injector(Context context) { @@ -1960,10 +1962,12 @@ public final class AppRestrictionController { mAppBatteryExemptionTracker = new AppBatteryExemptionTracker(mContext, controller); mAppFGSTracker = new AppFGSTracker(mContext, controller); mAppMediaSessionTracker = new AppMediaSessionTracker(mContext, controller); + mAppPermissionTracker = new AppPermissionTracker(mContext, controller); controller.mAppStateTrackers.add(mAppBatteryTracker); controller.mAppStateTrackers.add(mAppBatteryExemptionTracker); controller.mAppStateTrackers.add(mAppFGSTracker); controller.mAppStateTrackers.add(mAppMediaSessionTracker); + controller.mAppStateTrackers.add(mAppPermissionTracker); controller.mAppStateTrackers.add(new AppBroadcastEventsTracker(mContext, controller)); controller.mAppStateTrackers.add(new AppBindServiceEventsTracker(mContext, controller)); } @@ -2071,6 +2075,10 @@ public final class AppRestrictionController { return mAppBatteryExemptionTracker; } + AppPermissionTracker getAppPermissionTracker() { + return mAppPermissionTracker; + } + String getPackageName(int pid) { final ActivityManagerService am = getActivityManagerService(); final ProcessRecord app; diff --git a/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java b/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java index cc89e847023dc..80a219d446682 100644 --- a/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java +++ b/services/core/java/com/android/server/am/BaseAppStateDurationsTracker.java @@ -19,7 +19,6 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; -import android.annotation.NonNull; import android.content.Context; import android.os.SystemClock; import android.util.SparseArray; @@ -32,7 +31,6 @@ import com.android.server.am.BaseAppStateTimeEvents.BaseTimeEvent; import java.io.PrintWriter; import java.lang.reflect.Constructor; -import java.util.ArrayList; import java.util.LinkedList; /** @@ -43,20 +41,9 @@ abstract class BaseAppStateDurationsTracker extends BaseAppStateEventsTracker { static final boolean DEBUG_BASE_APP_STATE_DURATION_TRACKER = false; - static final int EVENT_TYPE_MEDIA_SESSION = 0; - static final int EVENT_TYPE_FGS_MEDIA_PLAYBACK = 1; - static final int EVENT_TYPE_FGS_LOCATION = 2; - static final int EVENT_NUM = 3; - - final ArrayList mEventListeners = new ArrayList<>(); - @GuardedBy("mLock") final SparseArray mUidStateDurations = new SparseArray<>(); - interface EventListener { - void onNewEvent(int uid, String packageName, boolean start, long now, int eventType); - } - BaseAppStateDurationsTracker(Context context, AppRestrictionController controller, Constructor> injector, Object outerContext) { super(context, controller, injector, outerContext); @@ -104,21 +91,6 @@ abstract class BaseAppStateDurationsTracker mUidStateDurations.remove(uid); } - void registerEventListener(@NonNull EventListener listener) { - synchronized (mLock) { - mEventListeners.add(listener); - } - } - - void notifyListenersOnEvent(int uid, String packageName, - boolean start, long now, int eventType) { - synchronized (mLock) { - for (int i = 0, size = mEventListeners.size(); i < size; i++) { - mEventListeners.get(i).onNewEvent(uid, packageName, start, now, eventType); - } - } - } - long getTotalDurations(String packageName, int uid, long now, int index, boolean bgOnly) { synchronized (mLock) { final U durations = mPkgEvents.get(uid, packageName); diff --git a/services/core/java/com/android/server/am/BaseAppStateTracker.java b/services/core/java/com/android/server/am/BaseAppStateTracker.java index 60d9167586a1f..482d69751d700 100644 --- a/services/core/java/com/android/server/am/BaseAppStateTracker.java +++ b/services/core/java/com/android/server/am/BaseAppStateTracker.java @@ -28,10 +28,12 @@ import android.app.AppOpsManager; import android.app.role.RoleManager; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.media.session.MediaSessionManager; import android.os.BatteryManagerInternal; import android.os.BatteryStatsInternal; import android.os.Handler; +import android.permission.PermissionManager; import android.util.Slog; import com.android.server.DeviceIdleInternal; @@ -42,6 +44,7 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import java.io.PrintWriter; import java.lang.reflect.Constructor; +import java.util.ArrayList; /** * Base class to track certain state of the app, could be used to determine the restriction level. @@ -55,11 +58,27 @@ public abstract class BaseAppStateTracker { static final long ONE_HOUR = 60 * ONE_MINUTE; static final long ONE_DAY = 24 * ONE_HOUR; + 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_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; + protected final AppRestrictionController mAppRestrictionController; protected final Injector mInjector; protected final Context mContext; protected final Handler mBgHandler; protected final Object mLock; + protected final ArrayList mStateListeners = new ArrayList<>(); + + interface StateListener { + void onStateChange(int uid, String packageName, boolean start, long now, int stateType); + } BaseAppStateTracker(Context context, AppRestrictionController controller, @Nullable Constructor> injector, Object outerContext) { @@ -80,6 +99,60 @@ public abstract class BaseAppStateTracker { } } + static int stateTypeToIndex(int stateType) { + return Integer.numberOfTrailingZeros(stateType); + } + + static int stateIndexToType(int stateTypeIndex) { + return 1 << stateTypeIndex; + } + + static String stateTypesToString(int stateTypes) { + final StringBuilder sb = new StringBuilder("["); + boolean needDelimiter = false; + for (int stateType = Integer.highestOneBit(stateTypes); stateType != 0; + stateType = Integer.highestOneBit(stateTypes)) { + if (needDelimiter) { + sb.append('|'); + } + needDelimiter = true; + switch (stateType) { + case STATE_TYPE_MEDIA_SESSION: + sb.append("MEDIA_SESSION"); + break; + case STATE_TYPE_FGS_MEDIA_PLAYBACK: + sb.append("FGS_MEDIA_PLAYBACK"); + break; + case STATE_TYPE_FGS_LOCATION: + sb.append("FGS_LOCATION"); + break; + case STATE_TYPE_PERMISSION: + sb.append("PERMISSION"); + break; + default: + return "[UNKNOWN(" + Integer.toHexString(stateTypes) + ")]"; + } + stateTypes &= ~stateType; + } + sb.append("]"); + return sb.toString(); + } + + void registerStateListener(@NonNull StateListener listener) { + synchronized (mLock) { + mStateListeners.add(listener); + } + } + + void notifyListenersOnStateChange(int uid, String packageName, + boolean start, long now, int stateType) { + synchronized (mLock) { + for (int i = 0, size = mStateListeners.size(); i < size; i++) { + mStateListeners.get(i).onStateChange(uid, packageName, start, now, stateType); + } + } + } + /** * Return the policy holder of this tracker. */ @@ -180,6 +253,8 @@ public abstract class BaseAppStateTracker { DeviceIdleInternal mDeviceIdleInternal; UserManagerInternal mUserManagerInternal; PackageManager mPackageManager; + PackageManagerInternal mPackageManagerInternal; + PermissionManager mPermissionManager; PermissionManagerServiceInternal mPermissionManagerServiceInternal; AppOpsManager mAppOpsManager; MediaSessionManager mMediaSessionManager; @@ -196,12 +271,14 @@ public abstract class BaseAppStateTracker { mBatteryStatsInternal = LocalServices.getService(BatteryStatsInternal.class); mDeviceIdleInternal = LocalServices.getService(DeviceIdleInternal.class); mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); + mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mPermissionManagerServiceInternal = LocalServices.getService( PermissionManagerServiceInternal.class); final Context context = mAppStatePolicy.mTracker.mContext; mPackageManager = context.getPackageManager(); mAppOpsManager = context.getSystemService(AppOpsManager.class); mMediaSessionManager = context.getSystemService(MediaSessionManager.class); + mPermissionManager = context.getSystemService(PermissionManager.class); mRoleManager = context.getSystemService(RoleManager.class); mNotificationManagerInternal = LocalServices.getService( NotificationManagerInternal.class); @@ -244,6 +321,14 @@ public abstract class BaseAppStateTracker { return mPackageManager; } + PackageManagerInternal getPackageManagerInternal() { + return mPackageManagerInternal; + } + + PermissionManager getPermissionManager() { + return mPermissionManager; + } + PermissionManagerServiceInternal getPermissionManagerServiceInternal() { return mPermissionManagerServiceInternal; } 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 e401d0ea09fd7..936940fcde6e8 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BackgroundRestrictionTest.java @@ -17,6 +17,8 @@ package com.android.server.am; import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; @@ -52,7 +54,12 @@ import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND; import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATTERY_USAGE_INDEX_FOREGROUND_SERVICE; import static com.android.server.am.AppBatteryTracker.BatteryUsage.BATT_DIMENS; +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_MEDIA_SESSION; +import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_PERMISSION; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -103,6 +110,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UidBatteryConsumer; import android.os.UserHandle; +import android.permission.PermissionManager; import android.provider.DeviceConfig; import android.telephony.TelephonyManager; import android.util.Log; @@ -152,6 +160,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.BiConsumer; +import java.util.stream.Collectors; /** * Tests for {@link AppRestrictionController}. @@ -217,6 +226,7 @@ public final class BackgroundRestrictionTest { @Mock private PackageManagerInternal mPackageManagerInternal; @Mock private NotificationManager mNotificationManager; @Mock private NotificationManagerInternal mNotificationManagerInternal; + @Mock private PermissionManager mPermissionManager; @Mock private PermissionManagerServiceInternal mPermissionManagerServiceInternal; @Mock private MediaSessionManager mMediaSessionManager; @Mock private RoleManager mRoleManager; @@ -252,6 +262,7 @@ public final class BackgroundRestrictionTest { private AppBindServiceEventsTracker mAppBindServiceEventsTracker; private AppFGSTracker mAppFGSTracker; private AppMediaSessionTracker mAppMediaSessionTracker; + private AppPermissionTracker mAppPermissionTracker; @Before public void setUp() throws Exception { @@ -291,12 +302,16 @@ public final class BackgroundRestrictionTest { doReturn(AppOpsManager.MODE_IGNORED) .when(mAppOpsManager) .checkOpNoThrow(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN, uid, packageName); - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION); - doReturn(PERMISSION_DENIED) - .when(mPermissionManagerServiceInternal) - .checkPermission(packageName, ACCESS_BACKGROUND_LOCATION, userId); + final String[] permissions = new String[] {ACCESS_BACKGROUND_LOCATION, + ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION}; + for (String permission : permissions) { + doReturn(PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkUidPermission(uid, permission); + doReturn(PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkPermission(packageName, permission, userId); + } } doReturn(appStandbyInfoList).when(mAppStandbyInternal).getAppStandbyBuckets(userId); } @@ -1278,7 +1293,8 @@ public final class BackgroundRestrictionTest { .checkPermission(packageName, perm, UserHandle.getUserId(uid)); doReturn(PERMISSION_GRANTED) .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION); + .checkUidPermission(uid, perm); + mInjector.getAppPermissionTracker().onPermissionsChanged(uid); } if (mediaControllers != null) { @@ -1303,7 +1319,8 @@ public final class BackgroundRestrictionTest { .checkPermission(packageName, perm, UserHandle.getUserId(uid)); doReturn(PERMISSION_DENIED) .when(mPermissionManagerServiceInternal) - .checkUidPermission(uid, ACCESS_BACKGROUND_LOCATION); + .checkUidPermission(uid, perm); + mInjector.getAppPermissionTracker().onPermissionsChanged(uid); } if (topStateThread != null) { topStateThread.join(); @@ -1371,6 +1388,10 @@ public final class BackgroundRestrictionTest { DeviceConfigSession bgLocationMinDurationThreshold = null; DeviceConfigSession bgCurrentDrainEventDurationBasedThresholdEnabled = null; DeviceConfigSession bgBatteryExemptionEnabled = null; + DeviceConfigSession bgBatteryExemptionTypes = null; + DeviceConfigSession bgPermissionMonitorEnabled = null; + DeviceConfigSession bgPermissionsInMonitor = null; + DeviceConfigSession bgCurrentDrainHighThresholdByBgLocation = null; mBgRestrictionController.addAppBackgroundRestrictionListener(listener); @@ -1469,6 +1490,38 @@ public final class BackgroundRestrictionTest { AppBatteryExemptionPolicy.DEFAULT_BG_BATTERY_EXEMPTION_ENABLED); bgBatteryExemptionEnabled.set(false); + 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(STATE_TYPE_MEDIA_SESSION | STATE_TYPE_FGS_MEDIA_PLAYBACK + | STATE_TYPE_FGS_LOCATION | STATE_TYPE_PERMISSION); + + bgPermissionMonitorEnabled = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppPermissionPolicy.KEY_BG_PERMISSION_MONITOR_ENABLED, + DeviceConfig::getBoolean, + AppPermissionPolicy.DEFAULT_BG_PERMISSION_MONITOR_ENABLED); + bgPermissionMonitorEnabled.set(true); + + bgPermissionsInMonitor = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppPermissionPolicy.KEY_BG_PERMISSION_MONITOR_ENABLED, + DeviceConfig::getString, + Arrays.stream(AppPermissionPolicy.DEFAULT_BG_PERMISSIONS_IN_MONITOR) + .collect(Collectors.joining(","))); + bgPermissionsInMonitor.set(ACCESS_FINE_LOCATION); + + bgCurrentDrainHighThresholdByBgLocation = new DeviceConfigSession<>( + DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, + AppBatteryPolicy.KEY_BG_CURRENT_DRAIN_HIGH_THRESHOLD_BY_BG_LOCATION, + DeviceConfig::getBoolean, + mContext.getResources().getBoolean( + R.bool.config_bg_current_drain_high_threshold_by_bg_location)); + bgCurrentDrainHighThresholdByBgLocation.set(true); + mCurrentTimeMillis = 10_000L; doReturn(mCurrentTimeMillis - windowMs).when(stats).getStatsStartTimestamp(); doReturn(mCurrentTimeMillis).when(stats).getStatsEndTimestamp(); @@ -1618,15 +1671,44 @@ public final class BackgroundRestrictionTest { setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); mAppBatteryPolicy.reset(); - // Run with bg location permission, with higher current drain. + // Turn off the higher threshold for bg location access. + bgCurrentDrainHighThresholdByBgLocation.set(false); + + // Run with bg location permission, with moderate current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, ACCESS_BACKGROUND_LOCATION, null, null, listener, stats, uids, - new double[]{restrictBucketHighThresholdMah - 1, 0}, + new double[]{restrictBucketThresholdMah - 1, 0}, new double[]{0, restrictBucketThresholdMah - 1}, zeros, true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, null, windowMs, null, null, null); + // Run with bg location permission, with a bit higher current drain. + runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, + FOREGROUND_SERVICE_TYPE_NONE, 0, false, + ACCESS_BACKGROUND_LOCATION, 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); + + // Start over. + resetBgRestrictionController(); + setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); + mAppBatteryPolicy.reset(); + + // Turn on the higher threshold for bg location access. + bgCurrentDrainHighThresholdByBgLocation.set(true); + + // Run with bg location permission, with higher current drain. + runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, + FOREGROUND_SERVICE_TYPE_NONE, 0, false, + ACCESS_BACKGROUND_LOCATION , null, null, listener, stats, uids, + new double[]{restrictBucketHighThresholdMah - 1, 0}, + new double[]{0, restrictBucketThresholdMah - 1}, zeros, + true , RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, + null, windowMs, null, null, null); + // Run with bg location permission, with even higher current drain. runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, FOREGROUND_SERVICE_TYPE_NONE, 0, false, @@ -1709,6 +1791,36 @@ public final class BackgroundRestrictionTest { true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, false, null, windowMs, initialBg, initialFgs, initialFg); + // Set the policy to exempt media session and permission. + bgBatteryExemptionTypes.set(STATE_TYPE_MEDIA_SESSION | STATE_TYPE_PERMISSION); + // Start over. + resetBgRestrictionController(); + setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); + mAppBatteryPolicy.reset(); + + // Run with coarse location permission, with high current drain. + runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, + FOREGROUND_SERVICE_TYPE_NONE, 0, false, + ACCESS_COARSE_LOCATION, 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, initialBg, initialFgs, initialFg); + + // Start over. + resetBgRestrictionController(); + setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); + mAppBatteryPolicy.reset(); + + // Run with fine location permission, with high current drain. + runTestBgCurrentDrainExemptionOnce(testPkgName1, testUid1, testPid1, + FOREGROUND_SERVICE_TYPE_NONE, 0, false, + ACCESS_FINE_LOCATION, null, null, listener, stats, uids, + new double[]{restrictBucketThresholdMah + 1, 0}, + new double[]{0, restrictBucketThresholdMah - 1}, zeros, + true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, + null, windowMs, initialBg, initialFgs, initialFg); + // Start over. resetBgRestrictionController(); setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); @@ -1738,6 +1850,10 @@ public final class BackgroundRestrictionTest { true, RESTRICTION_LEVEL_RESTRICTED_BUCKET, timeout, true, null, windowMs, initialBg, initialFgs, initialFg); + // Set the policy to exempt all. + bgBatteryExemptionTypes.set(STATE_TYPE_MEDIA_SESSION | STATE_TYPE_FGS_MEDIA_PLAYBACK + | STATE_TYPE_FGS_LOCATION | STATE_TYPE_PERMISSION); + // Start over. resetBgRestrictionController(); setUidBatteryConsumptions(stats, uids, zeros, zeros, zeros); @@ -1772,6 +1888,10 @@ public final class BackgroundRestrictionTest { closeIfNotNull(bgLocationMinDurationThreshold); closeIfNotNull(bgCurrentDrainEventDurationBasedThresholdEnabled); closeIfNotNull(bgBatteryExemptionEnabled); + closeIfNotNull(bgBatteryExemptionTypes); + closeIfNotNull(bgPermissionMonitorEnabled); + closeIfNotNull(bgPermissionsInMonitor); + closeIfNotNull(bgCurrentDrainHighThresholdByBgLocation); } } @@ -1792,6 +1912,15 @@ public final class BackgroundRestrictionTest { mAppBatteryExemptionTracker.reset(); mAppBatteryPolicy.reset(); } + if (perm != null) { + doReturn(PERMISSION_GRANTED) + .when(mPermissionManagerServiceInternal) + .checkPermission(packageName, perm, UserHandle.getUserId(uid)); + doReturn(PERMISSION_GRANTED) + .when(mPermissionManagerServiceInternal) + .checkUidPermission(uid, perm); + mInjector.getAppPermissionTracker().onPermissionsChanged(uid); + } runExemptionTestOnce( packageName, uid, pid, serviceType, sleepMs, stopAfterSleep, perm, mediaControllers, topStateChanges, resetFGSTracker, false, @@ -1841,6 +1970,15 @@ public final class BackgroundRestrictionTest { ); } ); + if (perm != null) { + doReturn(PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkPermission(packageName, perm, UserHandle.getUserId(uid)); + doReturn(PERMISSION_DENIED) + .when(mPermissionManagerServiceInternal) + .checkUidPermission(uid, perm); + mInjector.getAppPermissionTracker().onPermissionsChanged(uid); + } } @Test @@ -2406,6 +2544,11 @@ public final class BackgroundRestrictionTest { BackgroundRestrictionTest.class), BackgroundRestrictionTest.this); controller.addAppStateTracker(mAppBindServiceEventsTracker); + mAppPermissionTracker = new AppPermissionTracker(mContext, controller, + TestAppPermissionTrackerInjector.class.getDeclaredConstructor( + BackgroundRestrictionTest.class), + BackgroundRestrictionTest.this); + controller.addAppStateTracker(mAppPermissionTracker); } catch (NoSuchMethodException e) { // Won't happen. } @@ -2500,6 +2643,11 @@ public final class BackgroundRestrictionTest { AppBatteryExemptionTracker getAppBatteryExemptionTracker() { return mAppBatteryExemptionTracker; } + + @Override + AppPermissionTracker getAppPermissionTracker() { + return mAppPermissionTracker; + } } private class TestBaseTrackerInjector @@ -2564,6 +2712,14 @@ public final class BackgroundRestrictionTest { return BackgroundRestrictionTest.this.mNotificationManagerInternal; } + PackageManagerInternal getPackageManagerInternal() { + return BackgroundRestrictionTest.this.mPackageManagerInternal; + } + + PermissionManager getPermissionManager() { + return BackgroundRestrictionTest.this.mPermissionManager; + } + @Override long getServiceStartForegroundTimeout() { return 1_000; // ms @@ -2594,6 +2750,10 @@ public final class BackgroundRestrictionTest { extends TestBaseTrackerInjector { } + private class TestAppPermissionTrackerInjector + extends TestBaseTrackerInjector { + } + private class TestAppBroadcastEventsTrackerInjector extends TestBaseTrackerInjector { @Override