diff options
| author | 2017-11-29 02:33:39 +0000 | |
|---|---|---|
| committer | 2017-11-29 02:33:39 +0000 | |
| commit | 2dedff18cc62e7a30a5e38b47ed83e8ece856416 (patch) | |
| tree | c25d9cb91067b97c4a9ab4fb9a17cbe77650ef85 | |
| parent | 7e35c55f9d51a933aa65b08f814a86916fff6139 (diff) | |
| parent | 2206af39a28e8ef9d242015f791dc8abb6c3b3cc (diff) | |
Merge "Extreme battery saver: AlarmManager"
8 files changed, 1783 insertions, 293 deletions
diff --git a/core/proto/android/server/alarmmanagerservice.proto b/core/proto/android/server/alarmmanagerservice.proto index d2cd19072128..d72443711edb 100644 --- a/core/proto/android/server/alarmmanagerservice.proto +++ b/core/proto/android/server/alarmmanagerservice.proto @@ -20,6 +20,7 @@ import "frameworks/base/core/proto/android/app/alarmmanager.proto"; import "frameworks/base/core/proto/android/app/pendingintent.proto"; import "frameworks/base/core/proto/android/internal/locallog.proto"; import "frameworks/base/core/proto/android/os/worksource.proto"; +import "frameworks/base/core/proto/android/server/forceappstandbytracker.proto"; package com.android.server; @@ -32,10 +33,9 @@ message AlarmManagerServiceProto { optional int64 last_time_change_realtime = 4; // Current settings optional ConstantsProto settings = 5; - // UIDs currently in the foreground. - repeated int32 foreground_uids = 6; - // Packages forced into app standby. - repeated string forced_app_standby_packages = 7; + + // Dump from ForceAppStandbyTracker. + optional ForceAppStandbyTrackerProto force_app_standby_tracker = 6; optional bool is_interactive = 8; // Only valid if is_interactive is false. diff --git a/core/proto/android/server/forceappstandbytracker.proto b/core/proto/android/server/forceappstandbytracker.proto new file mode 100644 index 000000000000..8753bf768321 --- /dev/null +++ b/core/proto/android/server/forceappstandbytracker.proto @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +package com.android.server; + +option java_multiple_files = true; + +// Dump from ForceAppStandbyTracker. +message ForceAppStandbyTrackerProto { + // Whether all apps are forced standby or not. + optional bool force_all_apps_standby = 1; + + // UIDs currently in the foreground. + repeated int32 foreground_uids = 2; + + // App ids that are in power-save whitelist. + repeated int32 power_save_whitelist_app_ids = 3; + + // App ids that are in temporary power-save whitelist. + repeated int32 temp_power_save_whitelist_app_ids = 4; + + message RunAnyInBackgroundRestrictedPackages { + optional int32 uid = 1; + optional string package_name = 2; + } + + // Packages that are disallowed OP_RUN_ANY_IN_BACKGROUND. + repeated RunAnyInBackgroundRestrictedPackages run_any_in_background_restricted_packages = 5; +} diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index ab252a43814a..ca1524959358 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -46,7 +46,6 @@ import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; @@ -56,7 +55,6 @@ import android.provider.Settings; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Log; import android.util.Slog; @@ -82,6 +80,7 @@ import java.util.Locale; import java.util.Random; import java.util.TimeZone; import java.util.TreeSet; +import java.util.function.Predicate; import static android.app.AlarmManager.RTC_WAKEUP; import static android.app.AlarmManager.RTC; @@ -89,11 +88,17 @@ import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; import static android.app.AlarmManager.ELAPSED_REALTIME; import com.android.internal.annotations.GuardedBy; -import com.android.internal.app.IAppOpsCallback; -import com.android.internal.app.IAppOpsService; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.LocalLog; +import com.android.server.ForceAppStandbyTracker.Listener; +/** + * Alarm manager implementaion. + * + * Unit test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java + */ class AlarmManagerService extends SystemService { private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP; private static final int RTC_MASK = 1 << RTC; @@ -132,13 +137,10 @@ class AlarmManagerService extends SystemService { final LocalLog mLog = new LocalLog(TAG); AppOpsManager mAppOps; - IAppOpsService mAppOpsService; DeviceIdleController.LocalService mLocalDeviceIdleController; final Object mLock = new Object(); - ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>(); - SparseBooleanArray mForegroundUids = new SparseBooleanArray(); // List of alarms per uid deferred due to user applied background restrictions on the source app SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>(); long mNativeData; @@ -185,12 +187,6 @@ class AlarmManagerService extends SystemService { int mSystemUiUid; /** - * The current set of user whitelisted apps for device idle mode, meaning these are allowed - * to freely schedule alarms. - */ - int[] mDeviceIdleUserWhitelist = new int[0]; - - /** * For each uid, this is the last time we dispatched an "allow while idle" alarm, * used to determine the earliest we can dispatch the next such alarm. Times are in the * 'elapsed' timebase. @@ -224,6 +220,8 @@ class AlarmManagerService extends SystemService { private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray = new SparseArray<>(); + private final ForceAppStandbyTracker mForceAppStandbyTracker; + /** * All times are in milliseconds. These constants are kept synchronized with the system * global Settings. Any access to this class or its fields should be done while @@ -758,6 +756,9 @@ class AlarmManagerService extends SystemService { public AlarmManagerService(Context context) { super(context); mConstants = new Constants(mHandler); + + mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context); + mForceAppStandbyTracker.addListener(mForceAppStandbyListener); } static long convertToElapsed(long when, int type) { @@ -895,17 +896,48 @@ class AlarmManagerService extends SystemService { deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime()); } - void sendPendingBackgroundAlarmsForAppIdLocked(int appId) { + /** + * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not + * restricted. + * + * This is only called when the global "force all apps-standby" flag changes or when the + * power save whitelist changes, so it's okay to be slow. + */ + void sendAllUnrestrictedPendingBackgroundAlarmsLocked() { final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>(); - for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) { - final int uid = mPendingBackgroundAlarms.keyAt(i); - final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i); - if (UserHandle.getAppId(uid) == appId) { - alarmsToDeliver.addAll(alarmsForUid); - mPendingBackgroundAlarms.removeAt(i); + + findAllUnrestrictedPendingBackgroundAlarmsLockedInner( + mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted); + + if (alarmsToDeliver.size() > 0) { + deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime()); + } + } + + @VisibleForTesting + static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner( + SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms, + Predicate<Alarm> isBackgroundRestricted) { + + for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) { + final int uid = pendingAlarms.keyAt(uidIndex); + final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex); + + for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) { + final Alarm alarm = alarmsForUid.get(alarmIndex); + + if (isBackgroundRestricted.test(alarm)) { + continue; + } + + unrestrictedAlarms.add(alarm); + alarmsForUid.remove(alarmIndex); + } + + if (alarmsForUid.size() == 0) { + pendingAlarms.removeAt(uidIndex); } } - deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime()); } private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) { @@ -1235,10 +1267,8 @@ class AlarmManagerService extends SystemService { } catch (RemoteException e) { // ignored; both services live in system_server } - mAppOpsService = IAppOpsService.Stub.asInterface( - ServiceManager.getService(Context.APP_OPS_SERVICE)); publishBinderService(Context.ALARM_SERVICE, mService); - publishLocalService(LocalService.class, new LocalService()); + mForceAppStandbyTracker.start(); } @Override @@ -1248,13 +1278,6 @@ class AlarmManagerService extends SystemService { mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mLocalDeviceIdleController = LocalServices.getService(DeviceIdleController.LocalService.class); - try { - mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null, - new AppOpsWatcher()); - } catch (RemoteException rexc) { - // Shouldn't happen as they are in the same process. - Slog.e(TAG, "AppOps service not reachable", rexc); - } } } @@ -1583,8 +1606,7 @@ class AlarmManagerService extends SystemService { // timing restrictions. } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID || callingUid == mSystemUiUid - || Arrays.binarySearch(mDeviceIdleUserWhitelist, - UserHandle.getAppId(callingUid)) >= 0)) { + || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) { flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE; } @@ -1661,24 +1683,14 @@ class AlarmManagerService extends SystemService { } }; - public final class LocalService { - public void setDeviceIdleUserWhitelist(int[] appids) { - setDeviceIdleUserWhitelistImpl(appids); - } - } - void dumpImpl(PrintWriter pw) { synchronized (mLock) { pw.println("Current Alarm Manager state:"); mConstants.dump(pw); pw.println(); - pw.print(" Foreground uids: ["); - for (int i = 0; i < mForegroundUids.size(); i++) { - if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " "); - } - pw.println("]"); - pw.println(" Forced app standby packages: " + mForcedAppStandbyPackages); + mForceAppStandbyTracker.dump(pw, " "); + final long nowRTC = System.currentTimeMillis(); final long nowELAPSED = SystemClock.elapsedRealtime(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @@ -1718,7 +1730,6 @@ class AlarmManagerService extends SystemService { pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw); pw.println(); pw.print(" Num time change events: "); pw.println(mNumTimeChanged); - pw.println(" mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist)); pw.println(); pw.println(" Next alarm clock information: "); @@ -1991,15 +2002,8 @@ class AlarmManagerService extends SystemService { mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS); - final int foregroundUidsSize = mForegroundUids.size(); - for (int i = 0; i < foregroundUidsSize; i++) { - if (mForegroundUids.valueAt(i)) { - proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i)); - } - } - for (String pkg : mForcedAppStandbyPackages) { - proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg); - } + mForceAppStandbyTracker.dumpProto(proto, + AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER); proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive); if (!mInteractive) { @@ -2023,9 +2027,6 @@ class AlarmManagerService extends SystemService { proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS, nowElapsed - mLastWakeupSet); proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged); - for (int i : mDeviceIdleUserWhitelist) { - proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i); - } final TreeSet<Integer> users = new TreeSet<>(); final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size(); @@ -2267,28 +2268,6 @@ class AlarmManagerService extends SystemService { } } - void setDeviceIdleUserWhitelistImpl(int[] appids) { - synchronized (mLock) { - // appids are sorted, just send pending alarms for any new appids added to the whitelist - int i = 0, j = 0; - while (i < appids.length) { - while (j < mDeviceIdleUserWhitelist.length - && mDeviceIdleUserWhitelist[j] < appids[i]) { - j++; - } - if (j < mDeviceIdleUserWhitelist.length - && appids[i] != mDeviceIdleUserWhitelist[j]) { - if (DEBUG_BG_LIMIT) { - Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]); - } - sendPendingBackgroundAlarmsForAppIdLocked(appids[j]); - } - i++; - } - mDeviceIdleUserWhitelist = appids; - } - } - AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) { synchronized (mLock) { return mNextAlarmClockForUser.get(userId); @@ -2711,9 +2690,7 @@ class AlarmManagerService extends SystemService { final String sourcePackage = (alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName; final int sourceUid = alarm.creatorUid; - return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid) - && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid)) - < 0; + return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage); } private native long init(); @@ -2860,7 +2837,8 @@ class AlarmManagerService extends SystemService { } } - private static class Alarm { + @VisibleForTesting + static class Alarm { public final int type; public final long origWhen; public final boolean wakeup; @@ -3483,17 +3461,10 @@ class AlarmManagerService extends SystemService { if (disabled) { removeForStoppedLocked(uid); } - mForegroundUids.delete(uid); } } @Override public void onUidActive(int uid) { - synchronized (mLock) { - if (!mForegroundUids.get(uid)) { - mForegroundUids.put(uid, true); - sendPendingBackgroundAlarmsLocked(uid, null); - } - } } @Override public void onUidIdle(int uid, boolean disabled) { @@ -3501,7 +3472,6 @@ class AlarmManagerService extends SystemService { if (disabled) { removeForStoppedLocked(uid); } - mForegroundUids.delete(uid); } } @@ -3509,27 +3479,29 @@ class AlarmManagerService extends SystemService { } }; - private final class AppOpsWatcher extends IAppOpsCallback.Stub { + + private final Listener mForceAppStandbyListener = new Listener() { @Override - public void opChanged(int op, int uid, String packageName) throws RemoteException { + public void unblockAllUnrestrictedAlarms() { synchronized (mLock) { - final int mode = mAppOpsService.checkOperation(op, uid, packageName); - if (DEBUG_BG_LIMIT) { - Slog.d(TAG, - "Appop changed for " + uid + ", " + packageName + " to " + mode); - } - final boolean changed; - if (mode != AppOpsManager.MODE_ALLOWED) { - changed = mForcedAppStandbyPackages.add(packageName); - } else { - changed = mForcedAppStandbyPackages.remove(packageName); - } - if (changed && mode == AppOpsManager.MODE_ALLOWED) { - sendPendingBackgroundAlarmsLocked(uid, packageName); - } + sendAllUnrestrictedPendingBackgroundAlarmsLocked(); } } - } + + @Override + public void unblockAlarmsForUid(int uid) { + synchronized (mLock) { + sendPendingBackgroundAlarmsLocked(uid, null); + } + } + + @Override + public void unblockAlarmsForUidPackage(int uid, String packageName) { + synchronized (mLock) { + sendPendingBackgroundAlarmsLocked(uid, packageName); + } + } + }; private final BroadcastStats getStatsLocked(PendingIntent pi) { String pkg = pi.getCreatorPackage(); diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 0921a00b242f..d7aeb8cec641 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -119,7 +119,6 @@ public class DeviceIdleController extends SystemService private PowerManagerInternal mLocalPowerManager; private PowerManager mPowerManager; private ConnectivityService mConnectivityService; - private AlarmManagerService.LocalService mLocalAlarmManager; private INetworkPolicyManager mNetworkPolicyManager; private SensorManager mSensorManager; private Sensor mMotionSensor; @@ -1435,7 +1434,6 @@ public class DeviceIdleController extends SystemService mGoingIdleWakeLock.setReferenceCounted(true); mConnectivityService = (ConnectivityService)ServiceManager.getService( Context.CONNECTIVITY_SERVICE); - mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class); mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface( ServiceManager.getService(Context.NETWORK_POLICY_SERVICE)); mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class); @@ -1500,8 +1498,8 @@ public class DeviceIdleController extends SystemService mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); - mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray); + passWhiteListToForceAppStandbyTrackerLocked(); updateInteractivityLocked(); } updateConnectivityState(null); @@ -2477,13 +2475,7 @@ public class DeviceIdleController extends SystemService } mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray); } - if (mLocalAlarmManager != null) { - if (DEBUG) { - Slog.d(TAG, "Setting alarm whitelist to " - + Arrays.toString(mPowerSaveWhitelistUserAppIdArray)); - } - mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray); - } + passWhiteListToForceAppStandbyTrackerLocked(); } private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) { @@ -2509,6 +2501,7 @@ public class DeviceIdleController extends SystemService } mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray); } + passWhiteListToForceAppStandbyTrackerLocked(); } private void reportPowerSaveWhitelistChangedLocked() { @@ -2523,6 +2516,12 @@ public class DeviceIdleController extends SystemService getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM); } + private void passWhiteListToForceAppStandbyTrackerLocked() { + ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds( + mPowerSaveWhitelistAllAppIdArray, + mTempWhitelistAppIdArray); + } + void readConfigFileLocked() { if (DEBUG) Slog.d(TAG, "Reading config from " + mConfigFile.getBaseFile()); mPowerSaveWhitelistUserApps.clear(); diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java index 5dd3ee05d66c..61d3833d3197 100644 --- a/services/core/java/com/android/server/ForceAppStandbyTracker.java +++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java @@ -16,13 +16,18 @@ package com.android.server; import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.AppOpsManager.PackageOps; +import android.app.IActivityManager; import android.app.IUidObserver; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.os.PowerManager.ServiceType; import android.os.PowerManagerInternal; import android.os.RemoteException; @@ -30,21 +35,36 @@ import android.os.ServiceManager; import android.os.UserHandle; import android.util.ArraySet; import android.util.Pair; -import android.util.Slog; import android.util.SparseBooleanArray; +import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.Preconditions; +import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages; +import java.io.PrintWriter; +import java.util.Arrays; import java.util.List; /** - * Class to track OP_RUN_ANY_IN_BACKGROUND, UID foreground state and "force all app standby". + * Class to keep track of the information related to "force app standby", which includes: + * - OP_RUN_ANY_IN_BACKGROUND for each package + * - UID foreground state + * - User+system power save whitelist + * - Temporary power save whitelist + * - Global "force all apps standby" mode enforced by battery saver. * - * TODO Clean up cache when a user is deleted. - * TODO Add unit tests. b/68769804. + * TODO: In general, we can reduce the number of callbacks by checking all signals before sending + * each callback. For example, even when an UID comes into the foreground, if it wasn't + * originally restricted, then there's no need to send an event. + * Doing this would be error-prone, so we punt it for now, but we should revisit it later. + * + * Test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java */ public class ForceAppStandbyTracker { private static final String TAG = "ForceAppStandbyTracker"; @@ -55,22 +75,32 @@ public class ForceAppStandbyTracker { private final Object mLock = new Object(); private final Context mContext; + @VisibleForTesting + static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND; + + IActivityManager mIActivityManager; AppOpsManager mAppOpsManager; IAppOpsService mAppOpsService; PowerManagerInternal mPowerManagerInternal; - private final Handler mCallbackHandler; + private final MyHandler mHandler; /** * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed. */ @GuardedBy("mLock") - final ArraySet<Pair<Integer, String>> mForcedAppStandbyUidPackages = new ArraySet<>(); + final ArraySet<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>(); @GuardedBy("mLock") final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); @GuardedBy("mLock") + private int[] mPowerWhitelistedAllAppIds = new int[0]; + + @GuardedBy("mLock") + private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds; + + @GuardedBy("mLock") final ArraySet<Listener> mListeners = new ArraySet<>(); @GuardedBy("mLock") @@ -80,16 +110,116 @@ public class ForceAppStandbyTracker { boolean mForceAllAppsStandby; public static abstract class Listener { - public void onRestrictionChanged(int uid, @Nullable String packageName) { + /** + * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package. + */ + private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender, + int uid, @NonNull String packageName) { + updateJobsForUidPackage(uid, packageName); + + if (!sender.areAlarmsRestricted(uid, packageName)) { + unblockAlarmsForUidPackage(uid, packageName); + } + } + + /** + * This is called when the foreground state changed for a UID. + */ + private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) { + updateJobsForUid(uid); + + if (sender.isInForeground(uid)) { + unblockAlarmsForUid(uid); + } + } + + /** + * This is called when an app-id(s) is removed from the power save whitelist. + */ + private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) { + updateAllJobs(); + unblockAllUnrestrictedAlarms(); + } + + /** + * This is called when the power save whitelist changes, excluding the + * {@link #onPowerSaveUnwhitelisted} case. + */ + private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) { + updateAllJobs(); + } + + /** + * This is called when the temp whitelist changes. + */ + private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) { + + // TODO This case happens rather frequently; consider optimizing and update jobs + // only for affected app-ids. + + updateAllJobs(); + } + + /** + * This is called when the global "force all apps standby" flag changes. + */ + private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) { + updateAllJobs(); + + if (!sender.isForceAllAppsStandbyEnabled()) { + unblockAllUnrestrictedAlarms(); + } + } + + /** + * Called when the job restrictions for multiple UIDs might have changed, so the job + * scheduler should re-evaluate all restrictions for all jobs. + */ + public void updateAllJobs() { + } + + /** + * Called when the job restrictions for a UID might have changed, so the job + * scheduler should re-evaluate all restrictions for all jobs. + */ + public void updateJobsForUid(int uid) { } - public void onGlobalRestrictionChanged() { + /** + * Called when the job restrictions for a UID - package might have changed, so the job + * scheduler should re-evaluate all restrictions for all jobs. + */ + public void updateJobsForUidPackage(int uid, String packageName) { + } + + /** + * Called when the job restrictions for multiple UIDs might have changed, so the alarm + * manager should re-evaluate all restrictions for all blocked jobs. + */ + public void unblockAllUnrestrictedAlarms() { + } + + /** + * Called when all jobs for a specific UID are unblocked. + */ + public void unblockAlarmsForUid(int uid) { + } + + /** + * Called when all alarms for a specific UID - package are unblocked. + */ + public void unblockAlarmsForUidPackage(int uid, String packageName) { } } - private ForceAppStandbyTracker(Context context) { + @VisibleForTesting + ForceAppStandbyTracker(Context context, Looper looper) { mContext = context; - mCallbackHandler = FgThread.getHandler(); + mHandler = new MyHandler(looper); + } + + private ForceAppStandbyTracker(Context context) { + this(context, FgThread.get().getLooper()); } /** @@ -112,45 +242,65 @@ public class ForceAppStandbyTracker { } mStarted = true; - mAppOpsManager = Preconditions.checkNotNull( - mContext.getSystemService(AppOpsManager.class)); - mAppOpsService = Preconditions.checkNotNull( - IAppOpsService.Stub.asInterface( - ServiceManager.getService(Context.APP_OPS_SERVICE))); - mPowerManagerInternal = Preconditions.checkNotNull( - LocalServices.getService(PowerManagerInternal.class)); + mIActivityManager = Preconditions.checkNotNull(injectIActivityManager()); + mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager()); + mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService()); + mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal()); try { - ActivityManager.getService().registerUidObserver(new UidObserver(), + mIActivityManager.registerUidObserver(new UidObserver(), ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE, ActivityManager.PROCESS_STATE_UNKNOWN, null); - mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null, + mAppOpsService.startWatchingMode(TARGET_OP, null, new AppOpsWatcher()); } catch (RemoteException e) { // shouldn't happen. } + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiver(new MyReceiver(), filter); + + refreshForcedAppStandbyUidPackagesLocked(); + mPowerManagerInternal.registerLowPowerModeObserver( ServiceType.FORCE_ALL_APPS_STANDBY, - state -> updateForceAllAppsStandby(state.batterySaverEnabled)); + (state) -> updateForceAllAppsStandby(state.batterySaverEnabled)); - updateForceAllAppsStandby( - mPowerManagerInternal.getLowPowerState(ServiceType.FORCE_ALL_APPS_STANDBY) - .batterySaverEnabled); - - refreshForcedAppStandbyUidPackagesLocked(); + updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState( + ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled); } } + @VisibleForTesting + AppOpsManager injectAppOpsManager() { + return mContext.getSystemService(AppOpsManager.class); + } + + @VisibleForTesting + IAppOpsService injectIAppOpsService() { + return IAppOpsService.Stub.asInterface( + ServiceManager.getService(Context.APP_OPS_SERVICE)); + } + + @VisibleForTesting + IActivityManager injectIActivityManager() { + return ActivityManager.getService(); + } + + @VisibleForTesting + PowerManagerInternal injectPowerManagerInternal() { + return LocalServices.getService(PowerManagerInternal.class); + } + /** - * Update {@link #mForcedAppStandbyUidPackages} with the current app ops state. + * Update {@link #mRunAnyRestrictedPackages} with the current app ops state. */ private void refreshForcedAppStandbyUidPackagesLocked() { - final int op = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND; - - mForcedAppStandbyUidPackages.clear(); - final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(new int[] {op}); + mRunAnyRestrictedPackages.clear(); + final List<PackageOps> ops = mAppOpsManager.getPackagesForOps( + new int[] {TARGET_OP}); if (ops == null) { return; @@ -162,31 +312,38 @@ public class ForceAppStandbyTracker { for (int j = 0; j < entries.size(); j++) { AppOpsManager.OpEntry ent = entries.get(j); - if (ent.getOp() != op) { + if (ent.getOp() != TARGET_OP) { continue; } if (ent.getMode() != AppOpsManager.MODE_ALLOWED) { - mForcedAppStandbyUidPackages.add(Pair.create( + mRunAnyRestrictedPackages.add(Pair.create( pkg.getUid(), pkg.getPackageName())); } } } } - boolean isRunAnyInBackgroundAppOpRestricted(int uid, @NonNull String packageName) { - try { - return mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, - uid, packageName) != AppOpsManager.MODE_ALLOWED; - } catch (RemoteException e) { - return false; // shouldn't happen. + /** + * Update {@link #mForceAllAppsStandby} and notifies the listeners. + */ + void updateForceAllAppsStandby(boolean enable) { + synchronized (mLock) { + if (enable == mForceAllAppsStandby) { + return; + } + mForceAllAppsStandby = enable; + + mHandler.notifyForceAllAppsStandbyChanged(); } } private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) { - // TODO Maybe we should switch to indexOf(Pair.create()) if the array size is too big. - final int size = mForcedAppStandbyUidPackages.size(); + final int size = mRunAnyRestrictedPackages.size(); + if (size > 8) { + return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName)); + } for (int i = 0; i < size; i++) { - final Pair<Integer, String> pair = mForcedAppStandbyUidPackages.valueAt(i); + final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i); if ((pair.first == uid) && packageName.equals(pair.second)) { return i; @@ -196,13 +353,16 @@ public class ForceAppStandbyTracker { } /** - * @return whether a uid package-name pair is in mForcedAppStandbyUidPackages. + * @return whether a uid package-name pair is in mRunAnyRestrictedPackages. */ - boolean isUidPackageRestrictedLocked(int uid, @NonNull String packageName) { + boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) { return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0; } - boolean updateRestrictedUidPackageLocked(int uid, @NonNull String packageName, + /** + * Add to / remove from {@link #mRunAnyRestrictedPackages}. + */ + boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName, boolean restricted) { final int index = findForcedAppStandbyUidPackageIndexLocked(uid, packageName); final boolean wasRestricted = index >= 0; @@ -210,13 +370,16 @@ public class ForceAppStandbyTracker { return false; } if (restricted) { - mForcedAppStandbyUidPackages.add(Pair.create(uid, packageName)); + mRunAnyRestrictedPackages.add(Pair.create(uid, packageName)); } else { - mForcedAppStandbyUidPackages.removeAt(index); + mRunAnyRestrictedPackages.removeAt(index); } return true; } + /** + * Puts a UID to {@link #mForegroundUids}. + */ void uidToForeground(int uid) { synchronized (mLock) { if (!UserHandle.isApp(uid)) { @@ -228,10 +391,13 @@ public class ForceAppStandbyTracker { return; } mForegroundUids.put(uid, true); - notifyForUidPackage(uid, null); + mHandler.notifyUidForegroundStateChanged(uid); } } + /** + * Sets false for a UID {@link #mForegroundUids}, or remove it when {@code remove} is true. + */ void uidToBackground(int uid, boolean remove) { synchronized (mLock) { if (!UserHandle.isApp(uid)) { @@ -247,13 +413,11 @@ public class ForceAppStandbyTracker { } else { mForegroundUids.put(uid, false); } - notifyForUidPackage(uid, null); + mHandler.notifyUidForegroundStateChanged(uid); } } - // Event handlers - - final class UidObserver extends IUidObserver.Stub { + private final class UidObserver extends IUidObserver.Stub { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) { } @@ -277,11 +441,28 @@ public class ForceAppStandbyTracker { private final class AppOpsWatcher extends IAppOpsCallback.Stub { @Override public void opChanged(int op, int uid, String packageName) throws RemoteException { + boolean restricted = false; + try { + restricted = mAppOpsService.checkOperation(TARGET_OP, + uid, packageName) != AppOpsManager.MODE_ALLOWED; + } catch (RemoteException e) { + // Shouldn't happen + } synchronized (mLock) { - final boolean restricted = isRunAnyInBackgroundAppOpRestricted(uid, packageName); + if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) { + mHandler.notifyRunAnyAppOpsChanged(uid, packageName); + } + } + } + } - if (updateRestrictedUidPackageLocked(uid, packageName, restricted)) { - notifyForUidPackage(uid, packageName); + private final class MyReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) { + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); + if (userId > 0) { + mHandler.doUserRemoved(userId); } } } @@ -293,33 +474,185 @@ public class ForceAppStandbyTracker { } } - void notifyForUidPackage(int uid, String packageName) { - mCallbackHandler.post(() -> { - for (Listener l : cloneListeners()) { - l.onRestrictionChanged(uid, packageName); + private class MyHandler extends Handler { + private static final int MSG_UID_STATE_CHANGED = 1; + private static final int MSG_RUN_ANY_CHANGED = 2; + private static final int MSG_ALL_UNWHITELISTED = 3; + private static final int MSG_ALL_WHITELIST_CHANGED = 4; + private static final int MSG_TEMP_WHITELIST_CHANGED = 5; + private static final int MSG_FORCE_ALL_CHANGED = 6; + + private static final int MSG_USER_REMOVED = 7; + + public MyHandler(Looper looper) { + super(looper); + } + + public void notifyUidForegroundStateChanged(int uid) { + obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget(); + } + public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) { + obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget(); + } + + public void notifyAllUnwhitelisted() { + obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget(); + } + + public void notifyAllWhitelistChanged() { + obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget(); + } + + public void notifyTempWhitelistChanged() { + obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget(); + } + + public void notifyForceAllAppsStandbyChanged() { + obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget(); + } + + public void doUserRemoved(int userId) { + obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget(); + } + + @Override + public void dispatchMessage(Message msg) { + switch (msg.what) { + case MSG_USER_REMOVED: + handleUserRemoved(msg.arg1); + return; + } + + // Only notify the listeners when started. + synchronized (mLock) { + if (!mStarted) { + return; + } } - }); + final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this; + + switch (msg.what) { + case MSG_UID_STATE_CHANGED: + for (Listener l : cloneListeners()) { + l.onUidForegroundStateChanged(sender, msg.arg1); + } + return; + case MSG_RUN_ANY_CHANGED: + for (Listener l : cloneListeners()) { + l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj); + } + return; + case MSG_ALL_UNWHITELISTED: + for (Listener l : cloneListeners()) { + l.onPowerSaveUnwhitelisted(sender); + } + return; + case MSG_ALL_WHITELIST_CHANGED: + for (Listener l : cloneListeners()) { + l.onPowerSaveWhitelistedChanged(sender); + } + return; + case MSG_TEMP_WHITELIST_CHANGED: + for (Listener l : cloneListeners()) { + l.onTempPowerSaveWhitelistChanged(sender); + } + return; + case MSG_FORCE_ALL_CHANGED: + for (Listener l : cloneListeners()) { + l.onForceAllAppsStandbyChanged(sender); + } + return; + case MSG_USER_REMOVED: + handleUserRemoved(msg.arg1); + return; + } + } } - void notifyGlobal() { - mCallbackHandler.post(() -> { - for (Listener l : cloneListeners()) { - l.onGlobalRestrictionChanged(); + void handleUserRemoved(int removedUserId) { + synchronized (mLock) { + for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) { + final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i); + final int uid = pair.first; + final int userId = UserHandle.getUserId(uid); + + if (userId == removedUserId) { + mRunAnyRestrictedPackages.removeAt(i); + } + } + for (int i = mForegroundUids.size() - 1; i >= 0; i--) { + final int uid = mForegroundUids.keyAt(i); + final int userId = UserHandle.getUserId(uid); + + if (userId == removedUserId) { + mForegroundUids.removeAt(i); + } } - }); + } } - void updateForceAllAppsStandby(boolean forceAllAppsStandby) { + /** + * Called by device idle controller to update the power save whitelists. + */ + public void setPowerSaveWhitelistAppIds( + int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) { synchronized (mLock) { - if (mForceAllAppsStandby == forceAllAppsStandby) { - return; + final int[] previousWhitelist = mPowerWhitelistedAllAppIds; + final int[] previousTempWhitelist = mTempWhitelistedAppIds; + + mPowerWhitelistedAllAppIds = powerSaveWhitelistAllAppIdArray; + mTempWhitelistedAppIds = tempWhitelistAppIdArray; + + if (isAnyAppIdUnwhitelisted(previousWhitelist, mPowerWhitelistedAllAppIds)) { + mHandler.notifyAllUnwhitelisted(); + } else if (!Arrays.equals(previousWhitelist, mPowerWhitelistedAllAppIds)) { + mHandler.notifyAllWhitelistChanged(); + } + + if (!Arrays.equals(previousTempWhitelist, mTempWhitelistedAppIds)) { + mHandler.notifyTempWhitelistChanged(); } - mForceAllAppsStandby = forceAllAppsStandby; - Slog.i(TAG, "Force all app standby: " + mForceAllAppsStandby); - notifyGlobal(); + } } + /** + * @retunr true if a sorted app-id array {@code prevArray} has at least one element + * that's not in a sorted app-id array {@code newArray}. + */ + @VisibleForTesting + static boolean isAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray) { + int i1 = 0; + int i2 = 0; + boolean prevFinished; + boolean newFinished; + + for (;;) { + prevFinished = i1 >= prevArray.length; + newFinished = i2 >= newArray.length; + if (prevFinished || newFinished) { + break; + } + int a1 = prevArray[i1]; + int a2 = newArray[i2]; + + if (a1 == a2) { + i1++; + i2++; + continue; + } + if (a1 < a2) { + // prevArray has an element that's not in a2. + return true; + } + i2++; + } + if (prevFinished) { + return false; + } + return newFinished; + } + // Public interface. /** @@ -332,21 +665,51 @@ public class ForceAppStandbyTracker { } /** - * Whether force-app-standby is effective for a UID package-name. + * @return whether alarms should be restricted for a UID package-name. */ - public boolean isRestricted(int uid, @NonNull String packageName) { + public boolean areAlarmsRestricted(int uid, @NonNull String packageName) { + return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false); + } + + /** + * @return whether jobs should be restricted for a UID package-name. + */ + public boolean areJobsRestricted(int uid, @NonNull String packageName) { + return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true); + } + + /** + * @return whether force-app-standby is effective for a UID package-name. + */ + private boolean isRestricted(int uid, @NonNull String packageName, + boolean useTempWhitelistToo) { if (isInForeground(uid)) { return false; } synchronized (mLock) { + // Whitelisted? + final int appId = UserHandle.getAppId(uid); + if (ArrayUtils.contains(mPowerWhitelistedAllAppIds, appId)) { + return false; + } + if (useTempWhitelistToo && + ArrayUtils.contains(mTempWhitelistedAppIds, appId)) { + return false; + } + if (mForceAllAppsStandby) { return true; } - return isUidPackageRestrictedLocked(uid, packageName); + + return isRunAnyRestrictedLocked(uid, packageName); } } - /** For dumpsys -- otherwise the callers don't need to know it. */ + /** + * @return whether a UID is in the foreground or not. + * + * Note clients normally shouldn't need to access it. It's only for dumpsys. + */ public boolean isInForeground(int uid) { if (!UserHandle.isApp(uid)) { return true; @@ -356,31 +719,120 @@ public class ForceAppStandbyTracker { } } - /** For dumpsys -- otherwise the callers don't need to know it. */ - public boolean isForceAllAppsStandbyEnabled() { + /** + * @return whether force all apps standby is enabled or not. + * + * Note clients normally shouldn't need to access it. + */ + boolean isForceAllAppsStandbyEnabled() { synchronized (mLock) { return mForceAllAppsStandby; } } - /** For dumpsys -- otherwise the callers don't need to know it. */ + /** + * @return whether a UID/package has {@code OP_RUN_ANY_IN_BACKGROUND} allowed or not. + * + * Note clients normally shouldn't need to access it. It's only for dumpsys. + */ public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) { synchronized (mLock) { - return !isUidPackageRestrictedLocked(uid, packageName); + return !isRunAnyRestrictedLocked(uid, packageName); } } - /** For dumpsys -- otherwise the callers don't need to know it. */ - public SparseBooleanArray getForegroudUids() { + /** + * @return whether a UID is in the user / system defined power-save whitelist or not. + * + * Note clients normally shouldn't need to access it. It's only for dumpsys. + */ + public boolean isUidPowerSaveWhitelisted(int uid) { synchronized (mLock) { - return mForegroundUids.clone(); + return ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid)); } } - /** For dumpsys -- otherwise the callers don't need to know it. */ - public ArraySet<Pair<Integer, String>> getRestrictedUidPackages() { + /** + * @return whether a UID is in the temp power-save whitelist or not. + * + * Note clients normally shouldn't need to access it. It's only for dumpsys. + */ + public boolean isUidTempPowerSaveWhitelisted(int uid) { synchronized (mLock) { - return new ArraySet(mForcedAppStandbyUidPackages); + return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid)); + } + } + + public void dump(PrintWriter pw, String indent) { + synchronized (mLock) { + pw.print(indent); + pw.print("Force all apps standby: "); + pw.println(isForceAllAppsStandbyEnabled()); + + pw.print(indent); + pw.print("Foreground uids: ["); + + String sep = ""; + for (int i = 0; i < mForegroundUids.size(); i++) { + if (mForegroundUids.valueAt(i)) { + pw.print(sep); + pw.print(UserHandle.formatUid(mForegroundUids.keyAt(i))); + sep = " "; + } + } + pw.println("]"); + + pw.print(indent); + pw.print("Whitelist appids: "); + pw.println(Arrays.toString(mPowerWhitelistedAllAppIds)); + + pw.print(indent); + pw.print("Temp whitelist appids: "); + pw.println(Arrays.toString(mTempWhitelistedAppIds)); + + pw.print(indent); + pw.println("Restricted packages:"); + for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) { + pw.print(indent); + pw.print(" "); + pw.print(UserHandle.formatUid(uidAndPackage.first)); + pw.print(" "); + pw.print(uidAndPackage.second); + pw.println(); + } + } + } + + public void dumpProto(ProtoOutputStream proto, long fieldId) { + synchronized (mLock) { + final long token = proto.start(fieldId); + + proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby); + + for (int i = 0; i < mForegroundUids.size(); i++) { + if (mForegroundUids.valueAt(i)) { + proto.write(ForceAppStandbyTrackerProto.FOREGROUND_UIDS, + mForegroundUids.keyAt(i)); + } + } + + for (int appId : mPowerWhitelistedAllAppIds) { + proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_WHITELIST_APP_IDS, appId); + } + + for (int appId : mTempWhitelistedAppIds) { + proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId); + } + + for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) { + final long token2 = proto.start( + ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES); + proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first); + proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME, + uidAndPackage.second); + proto.end(token2); + } + proto.end(token); } } } diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java index f67bd0469b24..fc4015d0fdbe 100644 --- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -16,19 +16,12 @@ package com.android.server.job.controllers; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.os.IDeviceIdleController; -import android.os.PowerManager; -import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; import android.os.UserHandle; -import android.util.Pair; import android.util.Slog; -import android.util.SparseBooleanArray; import com.android.internal.util.ArrayUtils; import com.android.server.ForceAppStandbyTracker; @@ -37,7 +30,6 @@ import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; import java.io.PrintWriter; -import java.util.function.Predicate; public final class BackgroundJobsController extends StateController { @@ -51,9 +43,6 @@ public final class BackgroundJobsController extends StateController { private final JobSchedulerService mJobSchedulerService; private final IDeviceIdleController mDeviceIdleController; - private int[] mPowerWhitelistedUserAppIds; - private int[] mTempWhitelistedAppIds; - private final ForceAppStandbyTracker mForceAppStandbyTracker; @@ -67,28 +56,6 @@ public final class BackgroundJobsController extends StateController { } } - private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - synchronized (mLock) { - try { - switch (intent.getAction()) { - case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: - mPowerWhitelistedUserAppIds = - mDeviceIdleController.getAppIdUserWhitelist(); - break; - case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED: - mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist(); - break; - } - } catch (RemoteException rexc) { - Slog.e(LOG_TAG, "Device idle controller not reachable"); - } - updateAllJobRestrictionsLocked(); - } - } - }; - private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) { super(service, context, lock); mJobSchedulerService = service; @@ -97,19 +64,6 @@ public final class BackgroundJobsController extends StateController { mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context); - try { - mPowerWhitelistedUserAppIds = mDeviceIdleController.getAppIdUserWhitelist(); - mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist(); - } catch (RemoteException rexc) { - // Shouldn't happen as they are in the same process. - Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc); - } - IntentFilter powerWhitelistFilter = new IntentFilter(); - powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); - powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED); - context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter, - null, null); - mForceAppStandbyTracker.addListener(mForceAppStandbyListener); mForceAppStandbyTracker.start(); } @@ -128,31 +82,7 @@ public final class BackgroundJobsController extends StateController { public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { pw.println("BackgroundJobsController"); - pw.print("Force all apps standby: "); - pw.println(mForceAppStandbyTracker.isForceAllAppsStandbyEnabled()); - - pw.print("Foreground uids: ["); - final SparseBooleanArray foregroundUids = mForceAppStandbyTracker.getForegroudUids(); - - String sep = ""; - for (int i = 0; i < foregroundUids.size(); i++) { - if (foregroundUids.valueAt(i)) { - pw.print(sep); - pw.print(UserHandle.formatUid(foregroundUids.keyAt(i))); - sep = " "; - } - } - pw.println("]"); - - pw.println("Restricted packages:"); - for (Pair<Integer, String> uidAndPackage - : mForceAppStandbyTracker.getRestrictedUidPackages()) { - pw.print(" "); - pw.print(UserHandle.formatUid(uidAndPackage.first)); - pw.print(" "); - pw.print(uidAndPackage.second); - pw.println(); - } + mForceAppStandbyTracker.dump(pw, ""); pw.println("Job state:"); mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> { @@ -165,15 +95,17 @@ public final class BackgroundJobsController extends StateController { pw.print(" from "); UserHandle.formatUid(pw, uid); pw.print(mForceAppStandbyTracker.isInForeground(uid) ? " foreground" : " background"); - if (isWhitelistedLocked(uid)) { + if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) || + mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) { pw.print(", whitelisted"); } pw.print(": "); pw.print(jobStatus.getSourcePackageName()); - pw.print(" [background restrictions "); + pw.print(" [RUN_ANY_IN_BACKGROUND "); pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed( - jobStatus.getSourceUid(), jobStatus.getSourcePackageName()) ? "off]" : "on]"); + jobStatus.getSourceUid(), jobStatus.getSourcePackageName()) + ? "allowed]" : "disallowed]"); if ((jobStatus.satisfiedConstraints & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) { @@ -218,19 +150,12 @@ public final class BackgroundJobsController extends StateController { } } - private boolean isWhitelistedLocked(int uid) { - final int appId = UserHandle.getAppId(uid); - return ArrayUtils.contains(mTempWhitelistedAppIds, appId) - || ArrayUtils.contains(mPowerWhitelistedUserAppIds, appId); - } - boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) { final int uid = jobStatus.getSourceUid(); final String packageName = jobStatus.getSourcePackageName(); - final boolean canRun = isWhitelistedLocked(uid) - || !mForceAppStandbyTracker.isRestricted(uid, packageName); + final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName); return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun); } @@ -261,13 +186,18 @@ public final class BackgroundJobsController extends StateController { private final Listener mForceAppStandbyListener = new Listener() { @Override - public void onRestrictionChanged(int uid, String packageName) { + public void updateAllJobs() { + updateAllJobRestrictionsLocked(); + } + + @Override + public void updateJobsForUid(int uid) { updateJobRestrictionsForUidLocked(uid); } @Override - public void onGlobalRestrictionChanged() { - updateAllJobRestrictionsLocked(); + public void updateJobsForUidPackage(int uid, String packageName) { + updateJobRestrictionsForUidLocked(uid); } }; } diff --git a/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java new file mode 100644 index 000000000000..918807d0392c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server; + +import static android.app.AlarmManager.RTC; +import static android.app.AlarmManager.RTC_WAKEUP; + +import static org.junit.Assert.assertEquals; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.SparseArray; + +import com.android.internal.util.ObjectUtils; +import com.android.server.AlarmManagerService.Alarm; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AlarmManagerServiceTest { + private SparseArray<ArrayList<Alarm>> addPendingAlarm( + SparseArray<ArrayList<Alarm>> all, int uid, String name, boolean removeIt) { + ArrayList<Alarm> uidAlarms = all.get(uid); + if (uidAlarms == null) { + all.put(uid, uidAlarms = new ArrayList<>()); + } + // Details don't matter. + uidAlarms.add(new Alarm( + removeIt ? RTC : RTC_WAKEUP, + 0, 0, 0, 0, 0, null, null, null, null, 0, null, uid, name)); + return all; + } + + private static String toString(SparseArray<ArrayList<Alarm>> pendingAlarms) { + final StringBuilder sb = new StringBuilder(); + + String sep = ""; + for (int i = 0; i < pendingAlarms.size(); i++) { + sb.append(sep); + sep = ", "; + sb.append("["); + sb.append(pendingAlarms.keyAt(i)); + sb.append(": "); + sb.append(toString(pendingAlarms.valueAt(i))); + sb.append("]"); + } + return sb.toString(); + } + + private static String toString(ArrayList<Alarm> alarms) { + final StringBuilder sb = new StringBuilder(); + + alarms.sort((a, b) -> ObjectUtils.compare(a.packageName, b.packageName)); + + String sep = ""; + for (Alarm a : alarms) { + sb.append(sep); + sep = ", "; + sb.append(a.packageName); + } + return sb.toString(); + } + + private void runCheckAllPendingAlarms( + SparseArray<ArrayList<Alarm>> pending, ArrayList<Alarm> alarmsToDeliver) { + // RTC_WAKEUP alarms are restricted. + AlarmManagerService.findAllUnrestrictedPendingBackgroundAlarmsLockedInner(pending, + alarmsToDeliver, alarm -> alarm.type == RTC_WAKEUP); + } + + @Test + public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_empty() { + SparseArray<ArrayList<Alarm>> pending = new SparseArray<>(); + + final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>(); + + runCheckAllPendingAlarms(pending, alarmsToDeliver); + + assertEquals("", toString(pending)); + assertEquals("", toString(alarmsToDeliver)); + } + + @Test + public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_single_remove() { + SparseArray<ArrayList<Alarm>> pending = new SparseArray<>(); + + addPendingAlarm(pending, 100001, "a1", false); + + final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>(); + + runCheckAllPendingAlarms(pending, alarmsToDeliver); + + assertEquals("[100001: a1]", toString(pending)); + assertEquals("", toString(alarmsToDeliver)); + } + + @Test + public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_single_nonremove() { + SparseArray<ArrayList<Alarm>> pending = new SparseArray<>(); + + addPendingAlarm(pending, 100001, "a1", true); + + final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>(); + runCheckAllPendingAlarms(pending, alarmsToDeliver); + + + assertEquals("", toString(pending)); + assertEquals("a1", toString(alarmsToDeliver)); + } + + @Test + public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_complex() { + SparseArray<ArrayList<Alarm>> pending = new SparseArray<>(); + + addPendingAlarm(pending, 100001, "a11", false); + addPendingAlarm(pending, 100001, "a12", true); + addPendingAlarm(pending, 100001, "a13", false); + addPendingAlarm(pending, 100001, "a14", true); + + addPendingAlarm(pending, 100002, "a21", false); + + addPendingAlarm(pending, 100003, "a31", true); + + addPendingAlarm(pending, 100004, "a41", false); + addPendingAlarm(pending, 100004, "a42", false); + + addPendingAlarm(pending, 100005, "a51", true); + addPendingAlarm(pending, 100005, "a52", true); + + addPendingAlarm(pending, 100006, "a61", true); + addPendingAlarm(pending, 100006, "a62", false); + addPendingAlarm(pending, 100006, "a63", true); + addPendingAlarm(pending, 100006, "a64", false); + + final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>(); + runCheckAllPendingAlarms(pending, alarmsToDeliver); + + + assertEquals("[100001: a11, a13], [100002: a21], [100004: a41, a42], [100006: a62, a64]", + toString(pending)); + assertEquals("a12, a14, a31, a51, a52, a61, a63", toString(alarmsToDeliver)); + } + + @Test + public void findAllUnrestrictedPendingBackgroundAlarmsLockedInner_complex_allRemove() { + SparseArray<ArrayList<Alarm>> pending = new SparseArray<>(); + + addPendingAlarm(pending, 100001, "a11", true); + addPendingAlarm(pending, 100001, "a12", true); + addPendingAlarm(pending, 100001, "a13", true); + addPendingAlarm(pending, 100001, "a14", true); + + addPendingAlarm(pending, 100002, "a21", true); + + addPendingAlarm(pending, 100003, "a31", true); + + addPendingAlarm(pending, 100004, "a41", true); + addPendingAlarm(pending, 100004, "a42", true); + + addPendingAlarm(pending, 100005, "a51", true); + addPendingAlarm(pending, 100005, "a52", true); + + addPendingAlarm(pending, 100006, "a61", true); + addPendingAlarm(pending, 100006, "a62", true); + addPendingAlarm(pending, 100006, "a63", true); + addPendingAlarm(pending, 100006, "a64", true); + + final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>(); + runCheckAllPendingAlarms(pending, alarmsToDeliver); + + + assertEquals("", toString(pending)); + assertEquals("a11, a12, a13, a14, a21, a31, a41, a42, a51, a52, a61, a62, a63, a64", + toString(alarmsToDeliver)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java new file mode 100644 index 000000000000..66d0da13fff1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java @@ -0,0 +1,900 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server; + +import static com.android.server.ForceAppStandbyTracker.TARGET_OP; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.AppOpsManager; +import android.app.AppOpsManager.OpEntry; +import android.app.AppOpsManager.PackageOps; +import android.app.IActivityManager; +import android.app.IUidObserver; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.Looper; +import android.os.PowerManager.ServiceType; +import android.os.PowerManagerInternal; +import android.os.PowerSaveState; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.ArraySet; +import android.util.Pair; + +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsService; +import com.android.server.ForceAppStandbyTracker.Listener; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ForceAppStandbyTrackerTest { + + private class ForceAppStandbyTrackerTestable extends ForceAppStandbyTracker { + ForceAppStandbyTrackerTestable() { + super(mMockContext, Looper.getMainLooper()); + } + + @Override + AppOpsManager injectAppOpsManager() { + return mMockAppOpsManager; + } + + @Override + IAppOpsService injectIAppOpsService() { + return mMockIAppOpsService; + } + + @Override + IActivityManager injectIActivityManager() { + return mMockIActivityManager; + } + + @Override + PowerManagerInternal injectPowerManagerInternal() { + return mMockPowerManagerInternal; + } + } + + private static final int UID_1 = Process.FIRST_APPLICATION_UID + 1; + private static final int UID_2 = Process.FIRST_APPLICATION_UID + 2; + private static final int UID_3 = Process.FIRST_APPLICATION_UID + 3; + private static final int UID_10_1 = UserHandle.getUid(10, UID_1); + private static final int UID_10_2 = UserHandle.getUid(10, UID_2); + private static final int UID_10_3 = UserHandle.getUid(10, UID_3); + private static final String PACKAGE_1 = "package1"; + private static final String PACKAGE_2 = "package2"; + private static final String PACKAGE_3 = "package3"; + private static final String PACKAGE_SYSTEM = "android"; + + private Handler mMainHandler; + + @Mock + private Context mMockContext; + + @Mock + private IActivityManager mMockIActivityManager; + + @Mock + private AppOpsManager mMockAppOpsManager; + + @Mock + private IAppOpsService mMockIAppOpsService; + + @Mock + private PowerManagerInternal mMockPowerManagerInternal; + + private IUidObserver mIUidObserver; + private IAppOpsCallback.Stub mAppOpsCallback; + private Consumer<PowerSaveState> mPowerSaveObserver; + private BroadcastReceiver mReceiver; + + private boolean mPowerSaveMode; + + private final ArraySet<Pair<Integer, String>> mRestrictedPackages = new ArraySet(); + + @Before + public void setUp() { + mMainHandler = new Handler(Looper.getMainLooper()); + } + + private void waitUntilMainHandlerDrain() throws Exception { + final CountDownLatch l = new CountDownLatch(1); + mMainHandler.post(() -> { + l.countDown(); + }); + assertTrue(l.await(5, TimeUnit.SECONDS)); + } + + private PowerSaveState getPowerSaveState() { + return new PowerSaveState.Builder().setBatterySaverEnabled(mPowerSaveMode).build(); + } + + private ForceAppStandbyTrackerTestable newInstance() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mMockIAppOpsService.checkOperation(eq(TARGET_OP), anyInt(), anyString())) + .thenAnswer(inv -> { + return mRestrictedPackages.indexOf( + Pair.create(inv.getArgument(1), inv.getArgument(2))) >= 0 ? + AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED; + }); + + final ForceAppStandbyTrackerTestable instance = new ForceAppStandbyTrackerTestable(); + + return instance; + } + + private void callStart(ForceAppStandbyTrackerTestable instance) throws RemoteException { + + // Set up functions that start() calls. + when(mMockPowerManagerInternal.getLowPowerState(eq(ServiceType.FORCE_ALL_APPS_STANDBY))) + .thenAnswer(inv -> getPowerSaveState()); + when(mMockAppOpsManager.getPackagesForOps( + any(int[].class) + )).thenAnswer(inv -> new ArrayList<AppOpsManager.PackageOps>()); + + // Call start. + instance.start(); + + // Capture the listeners. + ArgumentCaptor<IUidObserver> uidObserverArgumentCaptor = + ArgumentCaptor.forClass(IUidObserver.class); + ArgumentCaptor<IAppOpsCallback.Stub> appOpsCallbackCaptor = + ArgumentCaptor.forClass(IAppOpsCallback.Stub.class); + ArgumentCaptor<Consumer<PowerSaveState>> powerSaveObserverCaptor = + ArgumentCaptor.forClass(Consumer.class); + ArgumentCaptor<BroadcastReceiver> receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + + verify(mMockIActivityManager).registerUidObserver( + uidObserverArgumentCaptor.capture(), + eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE + | ActivityManager.UID_OBSERVER_ACTIVE), + eq(ActivityManager.PROCESS_STATE_UNKNOWN), + isNull()); + verify(mMockIAppOpsService).startWatchingMode( + eq(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND), + isNull(), + appOpsCallbackCaptor.capture()); + verify(mMockPowerManagerInternal).registerLowPowerModeObserver( + eq(ServiceType.FORCE_ALL_APPS_STANDBY), + powerSaveObserverCaptor.capture()); + + verify(mMockContext).registerReceiver( + receiverCaptor.capture(), any(IntentFilter.class)); + + mIUidObserver = uidObserverArgumentCaptor.getValue(); + mAppOpsCallback = appOpsCallbackCaptor.getValue(); + mPowerSaveObserver = powerSaveObserverCaptor.getValue(); + mReceiver = receiverCaptor.getValue(); + + assertNotNull(mIUidObserver); + assertNotNull(mAppOpsCallback); + assertNotNull(mPowerSaveObserver); + assertNotNull(mReceiver); + } + + private void setAppOps(int uid, String packageName, boolean restrict) throws RemoteException { + final Pair p = Pair.create(uid, packageName); + if (restrict) { + mRestrictedPackages.add(p); + } else { + mRestrictedPackages.remove(p); + } + if (mAppOpsCallback != null) { + mAppOpsCallback.opChanged(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); + } + } + + private static final int NONE = 0; + private static final int ALARMS_ONLY = 1 << 0; + private static final int JOBS_ONLY = 1 << 1; + private static final int JOBS_AND_ALARMS = ALARMS_ONLY | JOBS_ONLY; + + private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName, + int restrictionTypes) { + assertEquals(((restrictionTypes & JOBS_ONLY) != 0), + instance.areJobsRestricted(uid, packageName)); + assertEquals(((restrictionTypes & ALARMS_ONLY) != 0), + instance.areAlarmsRestricted(uid, packageName)); + } + + @Test + public void testAll() throws Exception { + final ForceAppStandbyTrackerTestable instance = newInstance(); + callStart(instance); + + assertFalse(instance.isForceAllAppsStandbyEnabled()); + areRestricted(instance, UID_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, NONE); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + assertTrue(instance.isForceAllAppsStandbyEnabled()); + + areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + // Toggle the foreground state. + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + assertFalse(instance.isInForeground(UID_1)); + assertFalse(instance.isInForeground(UID_2)); + assertTrue(instance.isInForeground(Process.SYSTEM_UID)); + + mIUidObserver.onUidActive(UID_1); + areRestricted(instance, UID_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + assertTrue(instance.isInForeground(UID_1)); + assertFalse(instance.isInForeground(UID_2)); + + mIUidObserver.onUidGone(UID_1, /*disable=*/ false); + areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + assertFalse(instance.isInForeground(UID_1)); + assertFalse(instance.isInForeground(UID_2)); + + mIUidObserver.onUidActive(UID_1); + areRestricted(instance, UID_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + mIUidObserver.onUidIdle(UID_1, /*disable=*/ false); + areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + assertFalse(instance.isInForeground(UID_1)); + assertFalse(instance.isInForeground(UID_2)); + + // Toggle the app ops. + mPowerSaveMode = false; + mPowerSaveObserver.accept(getPowerSaveState()); + + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1)); + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1)); + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2)); + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2)); + + areRestricted(instance, UID_1, PACKAGE_1, NONE); + areRestricted(instance, UID_10_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, NONE); + areRestricted(instance, UID_10_2, PACKAGE_2, NONE); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + setAppOps(UID_1, PACKAGE_1, true); + setAppOps(UID_10_2, PACKAGE_2, true); + assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1)); + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1)); + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2)); + assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2)); + + areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, NONE); + areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + // Toggle power saver, should still be the same. + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + mPowerSaveMode = false; + mPowerSaveObserver.accept(getPowerSaveState()); + + areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, NONE); + areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + // Clear the app ops and update the whitelist. + setAppOps(UID_1, PACKAGE_1, false); + setAppOps(UID_10_2, PACKAGE_2, false); + + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + instance.setPowerSaveWhitelistAppIds(new int[] {UID_1}, new int[] {UID_2}); + + areRestricted(instance, UID_1, PACKAGE_1, NONE); + areRestricted(instance, UID_10_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY); + areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY); + areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + // Again, make sure toggling the global state doesn't change it. + mPowerSaveMode = false; + mPowerSaveObserver.accept(getPowerSaveState()); + + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + areRestricted(instance, UID_1, PACKAGE_1, NONE); + areRestricted(instance, UID_10_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, ALARMS_ONLY); + areRestricted(instance, UID_10_2, PACKAGE_2, ALARMS_ONLY); + areRestricted(instance, UID_3, PACKAGE_3, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_3, PACKAGE_3, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + assertTrue(instance.isUidPowerSaveWhitelisted(UID_1)); + assertTrue(instance.isUidPowerSaveWhitelisted(UID_10_1)); + assertFalse(instance.isUidPowerSaveWhitelisted(UID_2)); + assertFalse(instance.isUidPowerSaveWhitelisted(UID_10_2)); + + assertFalse(instance.isUidTempPowerSaveWhitelisted(UID_1)); + assertFalse(instance.isUidTempPowerSaveWhitelisted(UID_10_1)); + assertTrue(instance.isUidTempPowerSaveWhitelisted(UID_2)); + assertTrue(instance.isUidTempPowerSaveWhitelisted(UID_10_2)); + } + + public void loadPersistedAppOps() throws Exception { + final ForceAppStandbyTrackerTestable instance = newInstance(); + + final List<PackageOps> ops = new ArrayList<>(); + + //-------------------------------------------------- + List<OpEntry> entries = new ArrayList<>(); + entries.add(new AppOpsManager.OpEntry( + AppOpsManager.OP_ACCESS_NOTIFICATIONS, + AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null)); + entries.add(new AppOpsManager.OpEntry( + ForceAppStandbyTracker.TARGET_OP, + AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null)); + + ops.add(new PackageOps(PACKAGE_1, UID_1, entries)); + + //-------------------------------------------------- + entries = new ArrayList<>(); + entries.add(new AppOpsManager.OpEntry( + ForceAppStandbyTracker.TARGET_OP, + AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null)); + + ops.add(new PackageOps(PACKAGE_2, UID_2, entries)); + + //-------------------------------------------------- + entries = new ArrayList<>(); + entries.add(new AppOpsManager.OpEntry( + ForceAppStandbyTracker.TARGET_OP, + AppOpsManager.MODE_ALLOWED, 0, 0, 0, 0, null)); + + ops.add(new PackageOps(PACKAGE_1, UID_10_1, entries)); + + //-------------------------------------------------- + entries = new ArrayList<>(); + entries.add(new AppOpsManager.OpEntry( + ForceAppStandbyTracker.TARGET_OP, + AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null)); + entries.add(new AppOpsManager.OpEntry( + AppOpsManager.OP_ACCESS_NOTIFICATIONS, + AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null)); + + ops.add(new PackageOps(PACKAGE_3, UID_10_3, entries)); + + callStart(instance); + + assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_1, PACKAGE_1)); + assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2)); + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_3, PACKAGE_3)); + + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_1, PACKAGE_1)); + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2)); + assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_3, PACKAGE_3)); + } + + private void assertNoCallbacks(Listener l) throws Exception { + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + } + + @Test + public void testPowerSaveListener() throws Exception { + final ForceAppStandbyTrackerTestable instance = newInstance(); + callStart(instance); + + ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class); + instance.addListener(l); + + // Power save on. + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + // Power save off. + mPowerSaveMode = false; + mPowerSaveObserver.accept(getPowerSaveState()); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(1)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + // Power save on. + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + assertNoCallbacks(l); + } + + @Test + public void testAllListeners() throws Exception { + final ForceAppStandbyTrackerTestable instance = newInstance(); + callStart(instance); + + ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class); + instance.addListener(l); + + // ------------------------------------------------------------------------- + // Test with apppops. + + setAppOps(UID_10_2, PACKAGE_2, true); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2)); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + setAppOps(UID_10_2, PACKAGE_2, false); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2)); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(eq(UID_10_2), eq(PACKAGE_2)); + reset(l); + + setAppOps(UID_10_2, PACKAGE_2, false); + + verify(l, times(0)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2)); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + + // Unrestrict while battery saver is on. Shouldn't fire. + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + // Note toggling appops while BS is on will suppress unblockAlarmsForUidPackage(). + setAppOps(UID_10_2, PACKAGE_2, true); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(1)).updateJobsForUidPackage(eq(UID_10_2), eq(PACKAGE_2)); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + // Battery saver off. + mPowerSaveMode = false; + mPowerSaveObserver.accept(getPowerSaveState()); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(1)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + // ------------------------------------------------------------------------- + // Tests with system/user/temp whitelist. + + instance.setPowerSaveWhitelistAppIds(new int[] {UID_1, UID_2}, new int[] {}); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {}); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(1)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + // Update temp whitelist. + instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_1, UID_3}); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_3}); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + // Do the same thing with battery saver on. (Currently same callbacks are called.) + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + instance.setPowerSaveWhitelistAppIds(new int[] {UID_1, UID_2}, new int[] {}); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {}); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(1)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + // Update temp whitelist. + instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_1, UID_3}); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + instance.setPowerSaveWhitelistAppIds(new int[] {UID_2}, new int[] {UID_3}); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(anyInt()); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + + // ------------------------------------------------------------------------- + // Tests with proc state changes. + + // With battery save. + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + mIUidObserver.onUidActive(UID_10_1); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(1)).updateJobsForUid(eq(UID_10_1)); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + mIUidObserver.onUidGone(UID_10_1, true); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(1)).updateJobsForUid(eq(UID_10_1)); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + mIUidObserver.onUidActive(UID_10_1); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(1)).updateJobsForUid(eq(UID_10_1)); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + mIUidObserver.onUidIdle(UID_10_1, true); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(1)).updateJobsForUid(eq(UID_10_1)); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + // Without battery save. + mPowerSaveMode = false; + mPowerSaveObserver.accept(getPowerSaveState()); + + mIUidObserver.onUidActive(UID_10_1); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(1)).updateJobsForUid(eq(UID_10_1)); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + mIUidObserver.onUidGone(UID_10_1, true); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(1)).updateJobsForUid(eq(UID_10_1)); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + mIUidObserver.onUidActive(UID_10_1); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(1)).updateJobsForUid(eq(UID_10_1)); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + + mIUidObserver.onUidIdle(UID_10_1, true); + + waitUntilMainHandlerDrain(); + verify(l, times(0)).updateAllJobs(); + verify(l, times(1)).updateJobsForUid(eq(UID_10_1)); + verify(l, times(0)).updateJobsForUidPackage(anyInt(), anyString()); + + verify(l, times(0)).unblockAllUnrestrictedAlarms(); + verify(l, times(0)).unblockAlarmsForUid(anyInt()); + verify(l, times(0)).unblockAlarmsForUidPackage(anyInt(), anyString()); + reset(l); + } + + @Test + public void testUserRemoved() throws Exception { + final ForceAppStandbyTrackerTestable instance = newInstance(); + callStart(instance); + + mIUidObserver.onUidActive(UID_1); + mIUidObserver.onUidActive(UID_10_1); + + setAppOps(UID_2, PACKAGE_2, true); + setAppOps(UID_10_2, PACKAGE_2, true); + + assertTrue(instance.isInForeground(UID_1)); + assertTrue(instance.isInForeground(UID_10_1)); + + assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2)); + assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2)); + + final Intent intent = new Intent(Intent.ACTION_USER_REMOVED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, 10); + mReceiver.onReceive(mMockContext, intent); + + waitUntilMainHandlerDrain(); + + assertTrue(instance.isInForeground(UID_1)); + assertFalse(instance.isInForeground(UID_10_1)); + + assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2)); + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2)); + } + + static int[] array(int... appIds) { + Arrays.sort(appIds); + return appIds; + } + + private final Random mRandom = new Random(); + + int[] makeRandomArray() { + final ArrayList<Integer> list = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + if (mRandom.nextDouble() < 0.5) { + list.add(i); + } + } + return Arrays.stream(list.toArray(new Integer[list.size()])) + .mapToInt(Integer::intValue).toArray(); + } + + static boolean isAnyAppIdUnwhitelistedSlow(int[] prevArray, int[] newArray) { + Arrays.sort(newArray); // Just in case... + for (int p : prevArray) { + if (Arrays.binarySearch(newArray, p) < 0) { + return true; + } + } + return false; + } + + private void checkAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray, boolean expected) { + assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray), + expected, ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(prevArray, newArray)); + + // Also test isAnyAppIdUnwhitelistedSlow. + assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray), + expected, isAnyAppIdUnwhitelistedSlow(prevArray, newArray)); + } + + @Test + public void isAnyAppIdUnwhitelisted() { + checkAnyAppIdUnwhitelisted(array(), array(), false); + + checkAnyAppIdUnwhitelisted(array(1), array(), true); + checkAnyAppIdUnwhitelisted(array(1), array(1), false); + checkAnyAppIdUnwhitelisted(array(1), array(0, 1), false); + checkAnyAppIdUnwhitelisted(array(1), array(0, 1, 2), false); + checkAnyAppIdUnwhitelisted(array(1), array(0, 1, 2), false); + + checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(), true); + checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(1, 2), true); + checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(1, 2, 10), false); + checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(2, 10), true); + checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(0, 1, 2, 4, 3, 10), false); + checkAnyAppIdUnwhitelisted(array(1, 2, 10), array(0, 0, 1, 2, 10), false); + + // Random test + int trueCount = 0; + final int count = 10000; + for (int i = 0; i < count; i++) { + final int[] array1 = makeRandomArray(); + final int[] array2 = makeRandomArray(); + + final boolean expected = isAnyAppIdUnwhitelistedSlow(array1, array2); + final boolean actual = ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(array1, array2); + + assertEquals("Input: " + Arrays.toString(array1) + " " + Arrays.toString(array2), + expected, actual); + if (expected) { + trueCount++; + } + } + + // Make sure makeRandomArray() didn't generate all same arrays by accident. + assertTrue(trueCount > 0); + assertTrue(trueCount < count); + } +} |