From 3f7be62d316cf366fabca64b718f17982c8f436d Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Wed, 7 Feb 2018 10:49:01 -0800 Subject: Retry: JobScheduler to use UID active state for job exemption This is a second try for I134ac3d84a26309dab56ab6cbb271de626acdf7d. Apparently alarms are firing even before PHASE_SYSTEM_SERVICES_READY, so add more null checks. Test: Boot on taimen. Test: atest CtsAlarmManagerTestCases Test: atest CtsJobSchedulerTestCases Test: atest CtsBatterySavingTestCases Test: atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java Change-Id: I4b2a5af04e4d78c996e73da0621047da7a7bff39 --- core/java/android/app/ActivityManagerInternal.java | 5 + .../com/android/server/AlarmManagerService.java | 33 +- .../java/com/android/server/AppStateTracker.java | 1322 ++++++++++++++++++++ .../com/android/server/DeviceIdleController.java | 8 +- .../com/android/server/ForceAppStandbyTracker.java | 1308 ------------------- .../core/java/com/android/server/StatLogger.java | 11 +- .../android/server/am/ActivityManagerService.java | 8 + .../android/server/job/JobSchedulerService.java | 29 +- .../job/controllers/BackgroundJobsController.java | 38 +- .../com/android/server/AppStateTrackerTest.java | 1237 ++++++++++++++++++ .../android/server/ForceAppStandbyTrackerTest.java | 1200 ------------------ 11 files changed, 2636 insertions(+), 2563 deletions(-) create mode 100644 services/core/java/com/android/server/AppStateTracker.java delete mode 100644 services/core/java/com/android/server/ForceAppStandbyTracker.java create mode 100644 services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java delete mode 100644 services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 5ee7edee9db7..4626cb274c0e 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -348,4 +348,9 @@ public abstract class ActivityManagerInternal { * Returns is the caller has the same uid as the Recents component */ public abstract boolean isCallerRecents(int callingUid); + + /** + * Whether an UID is active or idle. + */ + public abstract boolean isUidActive(int uid); } diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java index 17c617c095d8..355da2df87a9 100644 --- a/services/core/java/com/android/server/AlarmManagerService.java +++ b/services/core/java/com/android/server/AlarmManagerService.java @@ -98,7 +98,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.LocalLog; -import com.android.server.ForceAppStandbyTracker.Listener; +import com.android.internal.util.Preconditions; +import com.android.server.AppStateTracker.Listener; /** * Alarm manager implementaion. @@ -249,7 +250,7 @@ class AlarmManagerService extends SystemService { private final SparseArray mHandlerSparseAlarmClockArray = new SparseArray<>(); - private final ForceAppStandbyTracker mForceAppStandbyTracker; + private AppStateTracker mAppStateTracker; private boolean mAppStandbyParole; private ArrayMap, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>(); @@ -707,9 +708,6 @@ class AlarmManagerService extends SystemService { super(context); mConstants = new Constants(mHandler); - mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context); - mForceAppStandbyTracker.addListener(mForceAppStandbyListener); - publishLocalService(AlarmManagerInternal.class, new LocalService()); } @@ -1329,13 +1327,15 @@ class AlarmManagerService extends SystemService { @Override public void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { - mForceAppStandbyTracker.start(); mConstants.start(getContext().getContentResolver()); mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mLocalDeviceIdleController = LocalServices.getService(DeviceIdleController.LocalService.class); mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class); mUsageStatsManagerInternal.addAppIdleStateChangeListener(new AppStandbyTracker()); + + mAppStateTracker = LocalServices.getService(AppStateTracker.class); + mAppStateTracker.addListener(mForceAppStandbyListener); } } @@ -1729,7 +1729,8 @@ class AlarmManagerService extends SystemService { // timing restrictions. } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID || callingUid == mSystemUiUid - || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) { + || (mAppStateTracker != null + && mAppStateTracker.isUidPowerSaveWhitelisted(callingUid)))) { flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED; flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE; } @@ -1812,8 +1813,10 @@ class AlarmManagerService extends SystemService { mConstants.dump(pw); pw.println(); - mForceAppStandbyTracker.dump(pw, " "); - pw.println(); + if (mAppStateTracker != null) { + mAppStateTracker.dump(pw, " "); + pw.println(); + } pw.println(" App Standby Parole: " + mAppStandbyParole); pw.println(); @@ -2161,8 +2164,10 @@ class AlarmManagerService extends SystemService { mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS); - mForceAppStandbyTracker.dumpProto(proto, - AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER); + if (mAppStateTracker != null) { + mAppStateTracker.dumpProto(proto, + AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER); + } proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive); if (!mInteractive) { @@ -2942,7 +2947,7 @@ class AlarmManagerService extends SystemService { } final String sourcePackage = alarm.sourcePackage; final int sourceUid = alarm.creatorUid; - return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage, + return mAppStateTracker.areAlarmsRestricted(sourceUid, sourcePackage, allowWhileIdle); } @@ -2955,7 +2960,7 @@ class AlarmManagerService extends SystemService { private long getWhileIdleMinIntervalLocked(int uid) { final boolean dozing = mPendingIdleUntil != null; - final boolean ebs = mForceAppStandbyTracker.isForceAllAppsStandbyEnabled(); + final boolean ebs = mAppStateTracker.isForceAllAppsStandbyEnabled(); if (!dozing && !ebs) { return mConstants.ALLOW_WHILE_IDLE_SHORT_TIME; } @@ -4133,7 +4138,7 @@ class AlarmManagerService extends SystemService { if (allowWhileIdle) { // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm. mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED); - if (mForceAppStandbyTracker.isUidInForeground(alarm.creatorUid)) { + if (mAppStateTracker.isUidInForeground(alarm.creatorUid)) { mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true); } else { mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false); diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/AppStateTracker.java new file mode 100644 index 000000000000..51dff56d3c11 --- /dev/null +++ b/services/core/java/com/android/server/AppStateTracker.java @@ -0,0 +1,1322 @@ +/* + * 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 android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.app.AppOpsManager.PackageOps; +import android.app.IActivityManager; +import android.app.IUidObserver; +import android.app.usage.UsageStatsManager; +import android.app.usage.UsageStatsManagerInternal; +import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.BatteryManager; +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; +import android.os.ServiceManager; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.ArraySet; +import android.util.Pair; +import android.util.Slog; +import android.util.SparseBooleanArray; +import android.util.SparseSetArray; +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.ExemptedPackage; +import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; + +/** + * Class to keep track of the information related to "force app standby", which includes: + * - OP_RUN_ANY_IN_BACKGROUND for each package + * - UID foreground/active state + * - User+system power save whitelist + * - Temporary power save whitelist + * - Global "force all apps standby" mode enforced by battery saver. + * + * TODO: Make it a LocalService. + * + * Test: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java + */ +public class AppStateTracker { + private static final String TAG = "ForceAppStandbyTracker"; + private static final boolean DEBUG = true; + + @GuardedBy("AppStateTracker.class") + private static AppStateTracker sInstance; + + private final Object mLock = new Object(); + private final Context mContext; + + @VisibleForTesting + static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND; + + IActivityManager mIActivityManager; + ActivityManagerInternal mActivityManagerInternal; + AppOpsManager mAppOpsManager; + IAppOpsService mAppOpsService; + PowerManagerInternal mPowerManagerInternal; + StandbyTracker mStandbyTracker; + UsageStatsManagerInternal mUsageStatsManagerInternal; + + private final MyHandler mHandler; + + @VisibleForTesting + FeatureFlagsObserver mFlagsObserver; + + /** + * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed. + */ + @GuardedBy("mLock") + final ArraySet> mRunAnyRestrictedPackages = new ArraySet<>(); + + /** UIDs that are active. */ + @GuardedBy("mLock") + final SparseBooleanArray mActiveUids = new SparseBooleanArray(); + + /** UIDs that are in the foreground. */ + @GuardedBy("mLock") + final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); + + /** + * System except-idle + user whitelist in the device idle controller. + */ + @GuardedBy("mLock") + private int[] mPowerWhitelistedAllAppIds = new int[0]; + + @GuardedBy("mLock") + private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds; + + /** + * Per-user packages that are in the EXEMPT bucket. + */ + @GuardedBy("mLock") + private final SparseSetArray mExemptedPackages = new SparseSetArray<>(); + + @GuardedBy("mLock") + final ArraySet mListeners = new ArraySet<>(); + + @GuardedBy("mLock") + boolean mStarted; + + /** + * Only used for small battery use-case. + */ + @GuardedBy("mLock") + boolean mIsPluggedIn; + + @GuardedBy("mLock") + boolean mBatterySaverEnabled; + + /** + * True if the forced app standby is currently enabled + */ + @GuardedBy("mLock") + boolean mForceAllAppsStandby; + + /** + * True if the forced app standby for small battery devices feature is enabled in settings + */ + @GuardedBy("mLock") + boolean mForceAllAppStandbyForSmallBattery; + + /** + * True if the forced app standby feature is enabled in settings + */ + @GuardedBy("mLock") + boolean mForcedAppStandbyEnabled; + + interface Stats { + int UID_FG_STATE_CHANGED = 0; + int UID_ACTIVE_STATE_CHANGED = 1; + int RUN_ANY_CHANGED = 2; + int ALL_UNWHITELISTED = 3; + int ALL_WHITELIST_CHANGED = 4; + int TEMP_WHITELIST_CHANGED = 5; + int EXEMPT_CHANGED = 6; + int FORCE_ALL_CHANGED = 7; + int FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8; + + int IS_UID_ACTIVE_CACHED = 9; + int IS_UID_ACTIVE_RAW = 10; + } + + private final StatLogger mStatLogger = new StatLogger(new String[] { + "UID_FG_STATE_CHANGED", + "UID_ACTIVE_STATE_CHANGED", + "RUN_ANY_CHANGED", + "ALL_UNWHITELISTED", + "ALL_WHITELIST_CHANGED", + "TEMP_WHITELIST_CHANGED", + "EXEMPT_CHANGED", + "FORCE_ALL_CHANGED", + "FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED", + + "IS_UID_ACTIVE_CACHED", + "IS_UID_ACTIVE_RAW", + }); + + @VisibleForTesting + class FeatureFlagsObserver extends ContentObserver { + FeatureFlagsObserver() { + super(null); + } + + void register() { + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED), + false, this); + + mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED), false, this); + } + + boolean isForcedAppStandbyEnabled() { + return injectGetGlobalSettingInt(Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1; + } + + boolean isForcedAppStandbyForSmallBatteryEnabled() { + return injectGetGlobalSettingInt( + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 0) == 1; + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + if (Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED).equals(uri)) { + final boolean enabled = isForcedAppStandbyEnabled(); + synchronized (mLock) { + if (mForcedAppStandbyEnabled == enabled) { + return; + } + mForcedAppStandbyEnabled = enabled; + if (DEBUG) { + Slog.d(TAG,"Forced app standby feature flag changed: " + + mForcedAppStandbyEnabled); + } + } + mHandler.notifyForcedAppStandbyFeatureFlagChanged(); + } else if (Settings.Global.getUriFor( + Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED).equals(uri)) { + final boolean enabled = isForcedAppStandbyForSmallBatteryEnabled(); + synchronized (mLock) { + if (mForceAllAppStandbyForSmallBattery == enabled) { + return; + } + mForceAllAppStandbyForSmallBattery = enabled; + if (DEBUG) { + Slog.d(TAG, "Forced app standby for small battery feature flag changed: " + + mForceAllAppStandbyForSmallBattery); + } + updateForceAllAppStandbyState(); + } + } else { + Slog.w(TAG, "Unexpected feature flag uri encountered: " + uri); + } + } + } + + public static abstract class Listener { + /** + * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package. + */ + private void onRunAnyAppOpsChanged(AppStateTracker sender, + int uid, @NonNull String packageName) { + updateJobsForUidPackage(uid, packageName); + + if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ false)) { + unblockAlarmsForUidPackage(uid, packageName); + } else if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ true)){ + // we need to deliver the allow-while-idle alarms for this uid, package + unblockAllUnrestrictedAlarms(); + } + } + + /** + * This is called when the foreground state changed for a UID. + */ + private void onUidForegroundStateChanged(AppStateTracker sender, int uid) { + onUidForeground(uid, sender.isUidInForeground(uid)); + } + + /** + * This is called when the active/idle state changed for a UID. + */ + private void onUidActiveStateChanged(AppStateTracker sender, int uid) { + updateJobsForUid(uid); + + if (sender.isUidActive(uid)) { + unblockAlarmsForUid(uid); + } + } + + /** + * This is called when an app-id(s) is removed from the power save whitelist. + */ + private void onPowerSaveUnwhitelisted(AppStateTracker sender) { + updateAllJobs(); + unblockAllUnrestrictedAlarms(); + } + + /** + * This is called when the power save whitelist changes, excluding the + * {@link #onPowerSaveUnwhitelisted} case. + */ + private void onPowerSaveWhitelistedChanged(AppStateTracker sender) { + updateAllJobs(); + } + + /** + * This is called when the temp whitelist changes. + */ + private void onTempPowerSaveWhitelistChanged(AppStateTracker sender) { + + // TODO This case happens rather frequently; consider optimizing and update jobs + // only for affected app-ids. + + updateAllJobs(); + + // Note when an app is just put in the temp whitelist, we do *not* drain pending alarms. + } + + /** + * This is called when the EXEMPT bucket is updated. + */ + private void onExemptChanged(AppStateTracker sender) { + // This doesn't happen very often, so just re-evaluate all jobs / alarms. + updateAllJobs(); + unblockAllUnrestrictedAlarms(); + } + + /** + * This is called when the global "force all apps standby" flag changes. + */ + private void onForceAllAppsStandbyChanged(AppStateTracker 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) { + } + + /** + * 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) { + } + + /** + * Called when a UID comes into the foreground or the background. + * + * @see #isUidInForeground(int) + */ + public void onUidForeground(int uid, boolean foreground) { + } + } + + public AppStateTracker(Context context, Looper looper) { + mContext = context; + mHandler = new MyHandler(looper); + } + + /** + * Call it when the system is ready. + */ + public void onSystemServicesReady() { + synchronized (mLock) { + if (mStarted) { + return; + } + mStarted = true; + + mIActivityManager = Preconditions.checkNotNull(injectIActivityManager()); + mActivityManagerInternal = Preconditions.checkNotNull(injectActivityManagerInternal()); + mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager()); + mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService()); + mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal()); + mUsageStatsManagerInternal = Preconditions.checkNotNull( + injectUsageStatsManagerInternal()); + + mFlagsObserver = new FeatureFlagsObserver(); + mFlagsObserver.register(); + mForcedAppStandbyEnabled = mFlagsObserver.isForcedAppStandbyEnabled(); + mForceAllAppStandbyForSmallBattery = + mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled(); + mStandbyTracker = new StandbyTracker(); + mUsageStatsManagerInternal.addAppIdleStateChangeListener(mStandbyTracker); + + try { + mIActivityManager.registerUidObserver(new UidObserver(), + ActivityManager.UID_OBSERVER_GONE + | ActivityManager.UID_OBSERVER_IDLE + | ActivityManager.UID_OBSERVER_ACTIVE + | ActivityManager.UID_OBSERVER_PROCSTATE, + ActivityManager.PROCESS_STATE_UNKNOWN, null); + mAppOpsService.startWatchingMode(TARGET_OP, null, + new AppOpsWatcher()); + } catch (RemoteException e) { + // shouldn't happen. + } + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_USER_REMOVED); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + mContext.registerReceiver(new MyReceiver(), filter); + + refreshForcedAppStandbyUidPackagesLocked(); + + mPowerManagerInternal.registerLowPowerModeObserver( + ServiceType.FORCE_ALL_APPS_STANDBY, + (state) -> { + synchronized (mLock) { + mBatterySaverEnabled = state.batterySaverEnabled; + updateForceAllAppStandbyState(); + } + }); + + mBatterySaverEnabled = mPowerManagerInternal.getLowPowerState( + ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled; + + updateForceAllAppStandbyState(); + } + } + + @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 + ActivityManagerInternal injectActivityManagerInternal() { + return LocalServices.getService(ActivityManagerInternal.class); + } + + @VisibleForTesting + PowerManagerInternal injectPowerManagerInternal() { + return LocalServices.getService(PowerManagerInternal.class); + } + + @VisibleForTesting + UsageStatsManagerInternal injectUsageStatsManagerInternal() { + return LocalServices.getService(UsageStatsManagerInternal.class); + } + + @VisibleForTesting + boolean isSmallBatteryDevice() { + return ActivityManager.isSmallBatteryDevice(); + } + + @VisibleForTesting + int injectGetGlobalSettingInt(String key, int def) { + return Settings.Global.getInt(mContext.getContentResolver(), key, def); + } + + /** + * Update {@link #mRunAnyRestrictedPackages} with the current app ops state. + */ + private void refreshForcedAppStandbyUidPackagesLocked() { + mRunAnyRestrictedPackages.clear(); + final List ops = mAppOpsManager.getPackagesForOps( + new int[] {TARGET_OP}); + + if (ops == null) { + return; + } + final int size = ops.size(); + for (int i = 0; i < size; i++) { + final AppOpsManager.PackageOps pkg = ops.get(i); + final List entries = ops.get(i).getOps(); + + for (int j = 0; j < entries.size(); j++) { + AppOpsManager.OpEntry ent = entries.get(j); + if (ent.getOp() != TARGET_OP) { + continue; + } + if (ent.getMode() != AppOpsManager.MODE_ALLOWED) { + mRunAnyRestrictedPackages.add(Pair.create( + pkg.getUid(), pkg.getPackageName())); + } + } + } + } + + private void updateForceAllAppStandbyState() { + synchronized (mLock) { + if (mForceAllAppStandbyForSmallBattery && isSmallBatteryDevice()) { + toggleForceAllAppsStandbyLocked(!mIsPluggedIn); + } else { + toggleForceAllAppsStandbyLocked(mBatterySaverEnabled); + } + } + } + + /** + * Update {@link #mForceAllAppsStandby} and notifies the listeners. + */ + private void toggleForceAllAppsStandbyLocked(boolean enable) { + if (enable == mForceAllAppsStandby) { + return; + } + mForceAllAppsStandby = enable; + + mHandler.notifyForceAllAppsStandbyChanged(); + } + + private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) { + final int size = mRunAnyRestrictedPackages.size(); + if (size > 8) { + return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName)); + } + for (int i = 0; i < size; i++) { + final Pair pair = mRunAnyRestrictedPackages.valueAt(i); + + if ((pair.first == uid) && packageName.equals(pair.second)) { + return i; + } + } + return -1; + } + + /** + * @return whether a uid package-name pair is in mRunAnyRestrictedPackages. + */ + boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) { + return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0; + } + + /** + * 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; + if (wasRestricted == restricted) { + return false; + } + if (restricted) { + mRunAnyRestrictedPackages.add(Pair.create(uid, packageName)); + } else { + mRunAnyRestrictedPackages.removeAt(index); + } + return true; + } + + private static boolean addUidToArray(SparseBooleanArray array, int uid) { + if (UserHandle.isCore(uid)) { + return false; + } + if (array.get(uid)) { + return false; + } + array.put(uid, true); + return true; + } + + private static boolean removeUidFromArray(SparseBooleanArray array, int uid, boolean remove) { + if (UserHandle.isCore(uid)) { + return false; + } + if (!array.get(uid)) { + return false; + } + if (remove) { + array.delete(uid); + } else { + array.put(uid, false); + } + return true; + } + + private final class UidObserver extends IUidObserver.Stub { + @Override + public void onUidStateChanged(int uid, int procState, long procStateSeq) { + mHandler.onUidStateChanged(uid, procState); + } + + @Override + public void onUidActive(int uid) { + mHandler.onUidActive(uid); + } + + @Override + public void onUidGone(int uid, boolean disabled) { + mHandler.onUidGone(uid, disabled); + } + + @Override + public void onUidIdle(int uid, boolean disabled) { + mHandler.onUidIdle(uid, disabled); + } + + @Override + public void onUidCachedChanged(int uid, boolean cached) { + } + } + + 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) { + if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) { + mHandler.notifyRunAnyAppOpsChanged(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); + } + } else if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { + synchronized (mLock) { + mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0); + } + updateForceAllAppStandbyState(); + } + } + } + + final class StandbyTracker extends AppIdleStateChangeListener { + @Override + public void onAppIdleStateChanged(String packageName, int userId, boolean idle, + int bucket) { + if (DEBUG) { + Slog.d(TAG,"onAppIdleStateChanged: " + packageName + " u" + userId + + (idle ? " idle" : " active") + " " + bucket); + } + final boolean changed; + if (bucket == UsageStatsManager.STANDBY_BUCKET_EXEMPTED) { + changed = mExemptedPackages.add(userId, packageName); + } else { + changed = mExemptedPackages.remove(userId, packageName); + } + if (changed) { + mHandler.notifyExemptChanged(); + } + } + + @Override + public void onParoleStateChanged(boolean isParoleOn) { + } + } + + private Listener[] cloneListeners() { + synchronized (mLock) { + return mListeners.toArray(new Listener[mListeners.size()]); + } + } + + private class MyHandler extends Handler { + private static final int MSG_UID_ACTIVE_STATE_CHANGED = 0; + private static final int MSG_UID_FG_STATE_CHANGED = 1; + private static final int MSG_RUN_ANY_CHANGED = 3; + private static final int MSG_ALL_UNWHITELISTED = 4; + private static final int MSG_ALL_WHITELIST_CHANGED = 5; + private static final int MSG_TEMP_WHITELIST_CHANGED = 6; + private static final int MSG_FORCE_ALL_CHANGED = 7; + private static final int MSG_USER_REMOVED = 8; + private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9; + private static final int MSG_EXEMPT_CHANGED = 10; + + private static final int MSG_ON_UID_STATE_CHANGED = 11; + private static final int MSG_ON_UID_ACTIVE = 12; + private static final int MSG_ON_UID_GONE = 13; + private static final int MSG_ON_UID_IDLE = 14; + + public MyHandler(Looper looper) { + super(looper); + } + + public void notifyUidActiveStateChanged(int uid) { + obtainMessage(MSG_UID_ACTIVE_STATE_CHANGED, uid, 0).sendToTarget(); + } + + public void notifyUidForegroundStateChanged(int uid) { + obtainMessage(MSG_UID_FG_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() { + removeMessages(MSG_ALL_UNWHITELISTED); + obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget(); + } + + public void notifyAllWhitelistChanged() { + removeMessages(MSG_ALL_WHITELIST_CHANGED); + obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget(); + } + + public void notifyTempWhitelistChanged() { + removeMessages(MSG_TEMP_WHITELIST_CHANGED); + obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget(); + } + + public void notifyForceAllAppsStandbyChanged() { + removeMessages(MSG_FORCE_ALL_CHANGED); + obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget(); + } + + public void notifyForcedAppStandbyFeatureFlagChanged() { + removeMessages(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED); + obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget(); + } + + public void notifyExemptChanged() { + removeMessages(MSG_EXEMPT_CHANGED); + obtainMessage(MSG_EXEMPT_CHANGED).sendToTarget(); + } + + public void doUserRemoved(int userId) { + obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget(); + } + + public void onUidStateChanged(int uid, int procState) { + obtainMessage(MSG_ON_UID_STATE_CHANGED, uid, procState).sendToTarget(); + } + + public void onUidActive(int uid) { + obtainMessage(MSG_ON_UID_ACTIVE, uid, 0).sendToTarget(); + } + + public void onUidGone(int uid, boolean disabled) { + obtainMessage(MSG_ON_UID_GONE, uid, disabled ? 1 : 0).sendToTarget(); + } + + public void onUidIdle(int uid, boolean disabled) { + obtainMessage(MSG_ON_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget(); + } + + @Override + public void handleMessage(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 AppStateTracker sender = AppStateTracker.this; + + long start = mStatLogger.getTime(); + switch (msg.what) { + case MSG_UID_ACTIVE_STATE_CHANGED: + for (Listener l : cloneListeners()) { + l.onUidActiveStateChanged(sender, msg.arg1); + } + mStatLogger.logDurationStat(Stats.UID_ACTIVE_STATE_CHANGED, start); + return; + + case MSG_UID_FG_STATE_CHANGED: + for (Listener l : cloneListeners()) { + l.onUidForegroundStateChanged(sender, msg.arg1); + } + mStatLogger.logDurationStat(Stats.UID_FG_STATE_CHANGED, start); + return; + + case MSG_RUN_ANY_CHANGED: + for (Listener l : cloneListeners()) { + l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj); + } + mStatLogger.logDurationStat(Stats.RUN_ANY_CHANGED, start); + return; + + case MSG_ALL_UNWHITELISTED: + for (Listener l : cloneListeners()) { + l.onPowerSaveUnwhitelisted(sender); + } + mStatLogger.logDurationStat(Stats.ALL_UNWHITELISTED, start); + return; + + case MSG_ALL_WHITELIST_CHANGED: + for (Listener l : cloneListeners()) { + l.onPowerSaveWhitelistedChanged(sender); + } + mStatLogger.logDurationStat(Stats.ALL_WHITELIST_CHANGED, start); + return; + + case MSG_TEMP_WHITELIST_CHANGED: + for (Listener l : cloneListeners()) { + l.onTempPowerSaveWhitelistChanged(sender); + } + mStatLogger.logDurationStat(Stats.TEMP_WHITELIST_CHANGED, start); + return; + + case MSG_EXEMPT_CHANGED: + for (Listener l : cloneListeners()) { + l.onExemptChanged(sender); + } + mStatLogger.logDurationStat(Stats.EXEMPT_CHANGED, start); + return; + + case MSG_FORCE_ALL_CHANGED: + for (Listener l : cloneListeners()) { + l.onForceAllAppsStandbyChanged(sender); + } + mStatLogger.logDurationStat(Stats.FORCE_ALL_CHANGED, start); + return; + + case MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED: + // Feature flag for forced app standby changed. + final boolean unblockAlarms; + synchronized (mLock) { + unblockAlarms = !mForcedAppStandbyEnabled && !mForceAllAppsStandby; + } + for (Listener l : cloneListeners()) { + l.updateAllJobs(); + if (unblockAlarms) { + l.unblockAllUnrestrictedAlarms(); + } + } + mStatLogger.logDurationStat( + Stats.FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED, start); + return; + + case MSG_USER_REMOVED: + handleUserRemoved(msg.arg1); + return; + + case MSG_ON_UID_STATE_CHANGED: + handleUidStateChanged(msg.arg1, msg.arg2); + return; + case MSG_ON_UID_ACTIVE: + handleUidActive(msg.arg1); + return; + case MSG_ON_UID_GONE: + handleUidGone(msg.arg1, msg.arg1 != 0); + return; + case MSG_ON_UID_IDLE: + handleUidIdle(msg.arg1, msg.arg1 != 0); + return; + } + } + + public void handleUidStateChanged(int uid, int procState) { + synchronized (mLock) { + if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + if (removeUidFromArray(mForegroundUids, uid, false)) { + mHandler.notifyUidForegroundStateChanged(uid); + } + } else { + if (addUidToArray(mForegroundUids, uid)) { + mHandler.notifyUidForegroundStateChanged(uid); + } + } + } + } + + public void handleUidActive(int uid) { + synchronized (mLock) { + if (addUidToArray(mActiveUids, uid)) { + mHandler.notifyUidActiveStateChanged(uid); + } + } + } + + public void handleUidGone(int uid, boolean disabled) { + removeUid(uid, true); + } + + public void handleUidIdle(int uid, boolean disabled) { + // Just to avoid excessive memcpy, don't remove from the array in this case. + removeUid(uid, false); + } + + private void removeUid(int uid, boolean remove) { + synchronized (mLock) { + if (removeUidFromArray(mActiveUids, uid, remove)) { + mHandler.notifyUidActiveStateChanged(uid); + } + if (removeUidFromArray(mForegroundUids, uid, remove)) { + mHandler.notifyUidForegroundStateChanged(uid); + } + } + } + } + + void handleUserRemoved(int removedUserId) { + synchronized (mLock) { + for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) { + final Pair pair = mRunAnyRestrictedPackages.valueAt(i); + final int uid = pair.first; + final int userId = UserHandle.getUserId(uid); + + if (userId == removedUserId) { + mRunAnyRestrictedPackages.removeAt(i); + } + } + cleanUpArrayForUser(mActiveUids, removedUserId); + cleanUpArrayForUser(mForegroundUids, removedUserId); + mExemptedPackages.remove(removedUserId); + } + } + + private void cleanUpArrayForUser(SparseBooleanArray array, int removedUserId) { + for (int i = array.size() - 1; i >= 0; i--) { + final int uid = array.keyAt(i); + final int userId = UserHandle.getUserId(uid); + + if (userId == removedUserId) { + array.removeAt(i); + } + } + } + + /** + * Called by device idle controller to update the power save whitelists. + */ + public void setPowerSaveWhitelistAppIds( + int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) { + synchronized (mLock) { + 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(); + } + + } + } + + /** + * @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. + + /** + * Register a new listener. + */ + public void addListener(@NonNull Listener listener) { + synchronized (mLock) { + mListeners.add(listener); + } + } + + /** + * @return whether alarms should be restricted for a UID package-name. + */ + public boolean areAlarmsRestricted(int uid, @NonNull String packageName, + boolean allowWhileIdle) { + return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false, + /* exemptOnBatterySaver =*/ allowWhileIdle); + } + + /** + * @return whether jobs should be restricted for a UID package-name. + */ + public boolean areJobsRestricted(int uid, @NonNull String packageName, + boolean hasForegroundExemption) { + return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true, + hasForegroundExemption); + } + + /** + * @return whether force-app-standby is effective for a UID package-name. + */ + private boolean isRestricted(int uid, @NonNull String packageName, + boolean useTempWhitelistToo, boolean exemptOnBatterySaver) { + if (isUidActive(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 (mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName)) { + return true; + } + if (exemptOnBatterySaver) { + return false; + } + final int userId = UserHandle.getUserId(uid); + if (mExemptedPackages.contains(userId, packageName)) { + return false; + } + return mForceAllAppsStandby; + } + } + + /** + * @return whether a UID is in active or not *based on cached information.* + * + * Note this information is based on the UID proc state callback, meaning it's updated + * asynchronously and may subtly be stale. If the fresh data is needed, use + * {@link #isUidActiveSynced} instead. + */ + public boolean isUidActive(int uid) { + if (UserHandle.isCore(uid)) { + return true; + } + synchronized (mLock) { + return mActiveUids.get(uid); + } + } + + /** + * @return whether a UID is in active or not *right now.* + * + * This gives the fresh information, but may access the activity manager so is slower. + */ + public boolean isUidActiveSynced(int uid) { + if (isUidActive(uid)) { // Use the cached one first. + return true; + } + final long start = mStatLogger.getTime(); + + final boolean ret = mActivityManagerInternal.isUidActive(uid); + mStatLogger.logDurationStat(Stats.IS_UID_ACTIVE_RAW, start); + + return ret; + } + + /** + * @return whether a UID is in the foreground or not. + * + * Note this information is based on the UID proc state callback, meaning it's updated + * asynchronously and may subtly be stale. If the fresh data is needed, use + * {@link ActivityManagerInternal#getUidProcessState} instead. + */ + public boolean isUidInForeground(int uid) { + if (UserHandle.isCore(uid)) { + return true; + } + synchronized (mLock) { + return mForegroundUids.get(uid); + } + } + + /** + * @return whether force all apps standby is enabled or not. + * + */ + boolean isForceAllAppsStandbyEnabled() { + synchronized (mLock) { + return mForceAllAppsStandby; + } + } + + /** + * @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 !isRunAnyRestrictedLocked(uid, packageName); + } + } + + /** + * @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 ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid)); + } + } + + /** + * @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 ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid)); + } + } + + public void dump(PrintWriter pw, String indent) { + synchronized (mLock) { + pw.print(indent); + pw.println("Forced App Standby Feature enabled: " + mForcedAppStandbyEnabled); + + pw.print(indent); + pw.print("Force all apps standby: "); + pw.println(isForceAllAppsStandbyEnabled()); + + pw.print(indent); + pw.print("Small Battery Device: "); + pw.println(isSmallBatteryDevice()); + + pw.print(indent); + pw.print("Force all apps standby for small battery device: "); + pw.println(mForceAllAppStandbyForSmallBattery); + + pw.print(indent); + pw.print("Plugged In: "); + pw.println(mIsPluggedIn); + + pw.print(indent); + pw.print("Active uids: "); + dumpUids(pw, mActiveUids); + + pw.print(indent); + pw.print("Foreground uids: "); + dumpUids(pw, mForegroundUids); + + 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("Exempted packages:"); + for (int i = 0; i < mExemptedPackages.size(); i++) { + pw.print(indent); + pw.print(" User "); + pw.print(mExemptedPackages.keyAt(i)); + pw.println(); + + for (int j = 0; j < mExemptedPackages.sizeAt(i); j++) { + pw.print(indent); + pw.print(" "); + pw.print(mExemptedPackages.valueAt(i, j)); + pw.println(); + } + } + pw.println(); + + pw.print(indent); + pw.println("Restricted packages:"); + for (Pair uidAndPackage : mRunAnyRestrictedPackages) { + pw.print(indent); + pw.print(" "); + pw.print(UserHandle.formatUid(uidAndPackage.first)); + pw.print(" "); + pw.print(uidAndPackage.second); + pw.println(); + } + + mStatLogger.dump(pw, indent); + } + } + + private void dumpUids(PrintWriter pw, SparseBooleanArray array) { + pw.print("["); + + String sep = ""; + for (int i = 0; i < array.size(); i++) { + if (array.valueAt(i)) { + pw.print(sep); + pw.print(UserHandle.formatUid(array.keyAt(i))); + sep = " "; + } + } + 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); + proto.write(ForceAppStandbyTrackerProto.IS_SMALL_BATTERY_DEVICE, + isSmallBatteryDevice()); + proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY_FOR_SMALL_BATTERY, + mForceAllAppStandbyForSmallBattery); + proto.write(ForceAppStandbyTrackerProto.IS_PLUGGED_IN, mIsPluggedIn); + + for (int i = 0; i < mActiveUids.size(); i++) { + if (mActiveUids.valueAt(i)) { + proto.write(ForceAppStandbyTrackerProto.ACTIVE_UIDS, + mActiveUids.keyAt(i)); + } + } + + 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 (int i = 0; i < mExemptedPackages.size(); i++) { + for (int j = 0; j < mExemptedPackages.sizeAt(i); j++) { + final long token2 = proto.start( + ForceAppStandbyTrackerProto.EXEMPTED_PACKAGES); + + proto.write(ExemptedPackage.USER_ID, mExemptedPackages.keyAt(i)); + proto.write(ExemptedPackage.PACKAGE_NAME, mExemptedPackages.valueAt(i, j)); + + proto.end(token2); + } + } + + for (Pair 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); + } + + mStatLogger.dumpProto(proto, ForceAppStandbyTrackerProto.STATS); + + proto.end(token); + } + } +} diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java index 44974ffd3509..2b3c5852ac8b 100644 --- a/services/core/java/com/android/server/DeviceIdleController.java +++ b/services/core/java/com/android/server/DeviceIdleController.java @@ -82,6 +82,7 @@ import com.android.internal.os.AtomicFile; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; import com.android.server.net.NetworkPolicyManagerInternal; @@ -128,6 +129,7 @@ public class DeviceIdleController extends SystemService private Intent mIdleIntent; private Intent mLightIdleIntent; private AnyMotionDetector mAnyMotionDetector; + private final AppStateTracker mAppStateTracker; private boolean mLightEnabled; private boolean mDeepEnabled; private boolean mForceIdle; @@ -1371,6 +1373,8 @@ public class DeviceIdleController extends SystemService super(context); mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml")); mHandler = new MyHandler(BackgroundThread.getHandler().getLooper()); + mAppStateTracker = new AppStateTracker(context, FgThread.get().getLooper()); + LocalServices.addService(AppStateTracker.class, mAppStateTracker); } boolean isAppOnWhitelistInternal(int appid) { @@ -1501,6 +1505,8 @@ public class DeviceIdleController extends SystemService (PowerManager) getContext().getSystemService(Context.POWER_SERVICE), mHandler, mSensorManager, this, angleThreshold); + mAppStateTracker.onSystemServicesReady(); + mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED); mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); @@ -2615,7 +2621,7 @@ public class DeviceIdleController extends SystemService } private void passWhiteListToForceAppStandbyTrackerLocked() { - ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds( + mAppStateTracker.setPowerSaveWhitelistAppIds( mPowerSaveWhitelistExceptIdleAppIdArray, mTempWhitelistAppIdArray); } diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java deleted file mode 100644 index 100680df637d..000000000000 --- a/services/core/java/com/android/server/ForceAppStandbyTracker.java +++ /dev/null @@ -1,1308 +0,0 @@ -/* - * 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 android.annotation.NonNull; -import android.app.ActivityManager; -import android.app.ActivityManagerInternal; -import android.app.AppOpsManager; -import android.app.AppOpsManager.PackageOps; -import android.app.IActivityManager; -import android.app.IUidObserver; -import android.app.usage.UsageStatsManager; -import android.app.usage.UsageStatsManagerInternal; -import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.BatteryManager; -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; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.provider.Settings; -import android.util.ArraySet; -import android.util.Pair; -import android.util.Slog; -import android.util.SparseBooleanArray; -import android.util.SparseSetArray; -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.DeviceIdleController.LocalService; -import com.android.server.ForceAppStandbyTrackerProto.ExemptedPackage; -import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages; - -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.List; - -/** - * Class to keep track of the information related to "force app standby", which includes: - * - OP_RUN_ANY_IN_BACKGROUND for each package - * - UID foreground/active state - * - User+system power save whitelist - * - Temporary power save whitelist - * - Global "force all apps standby" mode enforced by battery saver. - * - * TODO: Make it a LocalService. - * - * 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"; - private static final boolean DEBUG = true; - - @GuardedBy("ForceAppStandbyTracker.class") - private static ForceAppStandbyTracker sInstance; - - 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; - StandbyTracker mStandbyTracker; - UsageStatsManagerInternal mUsageStatsManagerInternal; - - private final MyHandler mHandler; - - @VisibleForTesting - FeatureFlagsObserver mFlagsObserver; - - /** - * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed. - */ - @GuardedBy("mLock") - final ArraySet> mRunAnyRestrictedPackages = new ArraySet<>(); - - /** UIDs that are active. */ - @GuardedBy("mLock") - final SparseBooleanArray mActiveUids = new SparseBooleanArray(); - - /** UIDs that are in the foreground. */ - @GuardedBy("mLock") - final SparseBooleanArray mForegroundUids = new SparseBooleanArray(); - - /** - * System except-idle + user whitelist in the device idle controller. - */ - @GuardedBy("mLock") - private int[] mPowerWhitelistedAllAppIds = new int[0]; - - @GuardedBy("mLock") - private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds; - - /** - * Per-user packages that are in the EXEMPT bucket. - */ - @GuardedBy("mLock") - private final SparseSetArray mExemptedPackages = new SparseSetArray<>(); - - @GuardedBy("mLock") - final ArraySet mListeners = new ArraySet<>(); - - @GuardedBy("mLock") - boolean mStarted; - - /** - * Only used for small battery use-case. - */ - @GuardedBy("mLock") - boolean mIsPluggedIn; - - @GuardedBy("mLock") - boolean mBatterySaverEnabled; - - /** - * True if the forced app standby is currently enabled - */ - @GuardedBy("mLock") - boolean mForceAllAppsStandby; - - /** - * True if the forced app standby for small battery devices feature is enabled in settings - */ - @GuardedBy("mLock") - boolean mForceAllAppStandbyForSmallBattery; - - /** - * True if the forced app standby feature is enabled in settings - */ - @GuardedBy("mLock") - boolean mForcedAppStandbyEnabled; - - interface Stats { - int UID_FG_STATE_CHANGED = 0; - int UID_ACTIVE_STATE_CHANGED = 1; - int RUN_ANY_CHANGED = 2; - int ALL_UNWHITELISTED = 3; - int ALL_WHITELIST_CHANGED = 4; - int TEMP_WHITELIST_CHANGED = 5; - int EXEMPT_CHANGED = 6; - int FORCE_ALL_CHANGED = 7; - int FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8; - } - - private final StatLogger mStatLogger = new StatLogger(new String[] { - "UID_FG_STATE_CHANGED", - "UID_ACTIVE_STATE_CHANGED", - "RUN_ANY_CHANGED", - "ALL_UNWHITELISTED", - "ALL_WHITELIST_CHANGED", - "TEMP_WHITELIST_CHANGED", - "EXEMPT_CHANGED", - "FORCE_ALL_CHANGED", - "FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED", - }); - - @VisibleForTesting - class FeatureFlagsObserver extends ContentObserver { - FeatureFlagsObserver() { - super(null); - } - - void register() { - mContext.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED), - false, this); - - mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor( - Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED), false, this); - } - - boolean isForcedAppStandbyEnabled() { - return injectGetGlobalSettingInt(Settings.Global.FORCED_APP_STANDBY_ENABLED, 1) == 1; - } - - boolean isForcedAppStandbyForSmallBatteryEnabled() { - return injectGetGlobalSettingInt( - Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 0) == 1; - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (Settings.Global.getUriFor(Settings.Global.FORCED_APP_STANDBY_ENABLED).equals(uri)) { - final boolean enabled = isForcedAppStandbyEnabled(); - synchronized (mLock) { - if (mForcedAppStandbyEnabled == enabled) { - return; - } - mForcedAppStandbyEnabled = enabled; - if (DEBUG) { - Slog.d(TAG,"Forced app standby feature flag changed: " - + mForcedAppStandbyEnabled); - } - } - mHandler.notifyForcedAppStandbyFeatureFlagChanged(); - } else if (Settings.Global.getUriFor( - Settings.Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED).equals(uri)) { - final boolean enabled = isForcedAppStandbyForSmallBatteryEnabled(); - synchronized (mLock) { - if (mForceAllAppStandbyForSmallBattery == enabled) { - return; - } - mForceAllAppStandbyForSmallBattery = enabled; - if (DEBUG) { - Slog.d(TAG, "Forced app standby for small battery feature flag changed: " - + mForceAllAppStandbyForSmallBattery); - } - updateForceAllAppStandbyState(); - } - } else { - Slog.w(TAG, "Unexpected feature flag uri encountered: " + uri); - } - } - } - - public static abstract class Listener { - /** - * 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, /*allowWhileIdle=*/ false)) { - unblockAlarmsForUidPackage(uid, packageName); - } else if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ true)){ - // we need to deliver the allow-while-idle alarms for this uid, package - unblockAllUnrestrictedAlarms(); - } - } - - /** - * This is called when the foreground state changed for a UID. - */ - private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) { - onUidForeground(uid, sender.isUidInForeground(uid)); - } - - /** - * This is called when the active/idle state changed for a UID. - */ - private void onUidActiveStateChanged(ForceAppStandbyTracker sender, int uid) { - updateJobsForUid(uid); - - if (sender.isUidActive(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(); - - // Note when an app is just put in the temp whitelist, we do *not* drain pending alarms. - } - - /** - * This is called when the EXEMPT bucket is updated. - */ - private void onExemptChanged(ForceAppStandbyTracker sender) { - // This doesn't happen very often, so just re-evaluate all jobs / alarms. - updateAllJobs(); - unblockAllUnrestrictedAlarms(); - } - - /** - * 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) { - } - - /** - * 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) { - } - - /** - * Called when a UID comes into the foreground or the background. - * - * @see #isUidInForeground(int) - */ - public void onUidForeground(int uid, boolean foreground) { - } - } - - @VisibleForTesting - ForceAppStandbyTracker(Context context, Looper looper) { - mContext = context; - mHandler = new MyHandler(looper); - } - - private ForceAppStandbyTracker(Context context) { - this(context, FgThread.get().getLooper()); - } - - /** - * Get the singleton instance. - */ - public static synchronized ForceAppStandbyTracker getInstance(Context context) { - if (sInstance == null) { - sInstance = new ForceAppStandbyTracker(context); - } - return sInstance; - } - - /** - * Call it when the system is ready. - */ - public void start() { - synchronized (mLock) { - if (mStarted) { - return; - } - mStarted = true; - - mIActivityManager = Preconditions.checkNotNull(injectIActivityManager()); - mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager()); - mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService()); - mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal()); - mUsageStatsManagerInternal = Preconditions.checkNotNull( - injectUsageStatsManagerInternal()); - - mFlagsObserver = new FeatureFlagsObserver(); - mFlagsObserver.register(); - mForcedAppStandbyEnabled = mFlagsObserver.isForcedAppStandbyEnabled(); - mForceAllAppStandbyForSmallBattery = - mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled(); - mStandbyTracker = new StandbyTracker(); - mUsageStatsManagerInternal.addAppIdleStateChangeListener(mStandbyTracker); - - try { - mIActivityManager.registerUidObserver(new UidObserver(), - ActivityManager.UID_OBSERVER_GONE - | ActivityManager.UID_OBSERVER_IDLE - | ActivityManager.UID_OBSERVER_ACTIVE - | ActivityManager.UID_OBSERVER_PROCSTATE, - ActivityManager.PROCESS_STATE_UNKNOWN, null); - mAppOpsService.startWatchingMode(TARGET_OP, null, - new AppOpsWatcher()); - } catch (RemoteException e) { - // shouldn't happen. - } - - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_USER_REMOVED); - filter.addAction(Intent.ACTION_BATTERY_CHANGED); - mContext.registerReceiver(new MyReceiver(), filter); - - refreshForcedAppStandbyUidPackagesLocked(); - - mPowerManagerInternal.registerLowPowerModeObserver( - ServiceType.FORCE_ALL_APPS_STANDBY, - (state) -> { - synchronized (mLock) { - mBatterySaverEnabled = state.batterySaverEnabled; - updateForceAllAppStandbyState(); - } - }); - - mBatterySaverEnabled = mPowerManagerInternal.getLowPowerState( - ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled; - - updateForceAllAppStandbyState(); - } - } - - @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); - } - - @VisibleForTesting - UsageStatsManagerInternal injectUsageStatsManagerInternal() { - return LocalServices.getService(UsageStatsManagerInternal.class); - } - - @VisibleForTesting - boolean isSmallBatteryDevice() { - return ActivityManager.isSmallBatteryDevice(); - } - - @VisibleForTesting - int injectGetGlobalSettingInt(String key, int def) { - return Settings.Global.getInt(mContext.getContentResolver(), key, def); - } - - /** - * Update {@link #mRunAnyRestrictedPackages} with the current app ops state. - */ - private void refreshForcedAppStandbyUidPackagesLocked() { - mRunAnyRestrictedPackages.clear(); - final List ops = mAppOpsManager.getPackagesForOps( - new int[] {TARGET_OP}); - - if (ops == null) { - return; - } - final int size = ops.size(); - for (int i = 0; i < size; i++) { - final AppOpsManager.PackageOps pkg = ops.get(i); - final List entries = ops.get(i).getOps(); - - for (int j = 0; j < entries.size(); j++) { - AppOpsManager.OpEntry ent = entries.get(j); - if (ent.getOp() != TARGET_OP) { - continue; - } - if (ent.getMode() != AppOpsManager.MODE_ALLOWED) { - mRunAnyRestrictedPackages.add(Pair.create( - pkg.getUid(), pkg.getPackageName())); - } - } - } - } - - private void updateForceAllAppStandbyState() { - synchronized (mLock) { - if (mForceAllAppStandbyForSmallBattery && isSmallBatteryDevice()) { - toggleForceAllAppsStandbyLocked(!mIsPluggedIn); - } else { - toggleForceAllAppsStandbyLocked(mBatterySaverEnabled); - } - } - } - - /** - * Update {@link #mForceAllAppsStandby} and notifies the listeners. - */ - private void toggleForceAllAppsStandbyLocked(boolean enable) { - if (enable == mForceAllAppsStandby) { - return; - } - mForceAllAppsStandby = enable; - - mHandler.notifyForceAllAppsStandbyChanged(); - } - - private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) { - final int size = mRunAnyRestrictedPackages.size(); - if (size > 8) { - return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName)); - } - for (int i = 0; i < size; i++) { - final Pair pair = mRunAnyRestrictedPackages.valueAt(i); - - if ((pair.first == uid) && packageName.equals(pair.second)) { - return i; - } - } - return -1; - } - - /** - * @return whether a uid package-name pair is in mRunAnyRestrictedPackages. - */ - boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) { - return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0; - } - - /** - * 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; - if (wasRestricted == restricted) { - return false; - } - if (restricted) { - mRunAnyRestrictedPackages.add(Pair.create(uid, packageName)); - } else { - mRunAnyRestrictedPackages.removeAt(index); - } - return true; - } - - private static boolean addUidToArray(SparseBooleanArray array, int uid) { - if (UserHandle.isCore(uid)) { - return false; - } - if (array.get(uid)) { - return false; - } - array.put(uid, true); - return true; - } - - private static boolean removeUidFromArray(SparseBooleanArray array, int uid, boolean remove) { - if (UserHandle.isCore(uid)) { - return false; - } - if (!array.get(uid)) { - return false; - } - if (remove) { - array.delete(uid); - } else { - array.put(uid, false); - } - return true; - } - - private final class UidObserver extends IUidObserver.Stub { - @Override - public void onUidStateChanged(int uid, int procState, long procStateSeq) { - mHandler.onUidStateChanged(uid, procState); - } - - @Override - public void onUidActive(int uid) { - mHandler.onUidActive(uid); - } - - @Override - public void onUidGone(int uid, boolean disabled) { - mHandler.onUidGone(uid, disabled); - } - - @Override - public void onUidIdle(int uid, boolean disabled) { - mHandler.onUidIdle(uid, disabled); - } - - @Override - public void onUidCachedChanged(int uid, boolean cached) { - } - } - - 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) { - if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) { - mHandler.notifyRunAnyAppOpsChanged(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); - } - } else if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) { - synchronized (mLock) { - mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0); - } - updateForceAllAppStandbyState(); - } - } - } - - final class StandbyTracker extends AppIdleStateChangeListener { - @Override - public void onAppIdleStateChanged(String packageName, int userId, boolean idle, - int bucket) { - if (DEBUG) { - Slog.d(TAG,"onAppIdleStateChanged: " + packageName + " u" + userId - + (idle ? " idle" : " active") + " " + bucket); - } - final boolean changed; - if (bucket == UsageStatsManager.STANDBY_BUCKET_EXEMPTED) { - changed = mExemptedPackages.add(userId, packageName); - } else { - changed = mExemptedPackages.remove(userId, packageName); - } - if (changed) { - mHandler.notifyExemptChanged(); - } - } - - @Override - public void onParoleStateChanged(boolean isParoleOn) { - } - } - - private Listener[] cloneListeners() { - synchronized (mLock) { - return mListeners.toArray(new Listener[mListeners.size()]); - } - } - - private class MyHandler extends Handler { - private static final int MSG_UID_ACTIVE_STATE_CHANGED = 0; - private static final int MSG_UID_FG_STATE_CHANGED = 1; - private static final int MSG_RUN_ANY_CHANGED = 3; - private static final int MSG_ALL_UNWHITELISTED = 4; - private static final int MSG_ALL_WHITELIST_CHANGED = 5; - private static final int MSG_TEMP_WHITELIST_CHANGED = 6; - private static final int MSG_FORCE_ALL_CHANGED = 7; - private static final int MSG_USER_REMOVED = 8; - private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9; - private static final int MSG_EXEMPT_CHANGED = 10; - - private static final int MSG_ON_UID_STATE_CHANGED = 11; - private static final int MSG_ON_UID_ACTIVE = 12; - private static final int MSG_ON_UID_GONE = 13; - private static final int MSG_ON_UID_IDLE = 14; - - public MyHandler(Looper looper) { - super(looper); - } - - public void notifyUidActiveStateChanged(int uid) { - obtainMessage(MSG_UID_ACTIVE_STATE_CHANGED, uid, 0).sendToTarget(); - } - - public void notifyUidForegroundStateChanged(int uid) { - obtainMessage(MSG_UID_FG_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() { - removeMessages(MSG_ALL_UNWHITELISTED); - obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget(); - } - - public void notifyAllWhitelistChanged() { - removeMessages(MSG_ALL_WHITELIST_CHANGED); - obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget(); - } - - public void notifyTempWhitelistChanged() { - removeMessages(MSG_TEMP_WHITELIST_CHANGED); - obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget(); - } - - public void notifyForceAllAppsStandbyChanged() { - removeMessages(MSG_FORCE_ALL_CHANGED); - obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget(); - } - - public void notifyForcedAppStandbyFeatureFlagChanged() { - removeMessages(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED); - obtainMessage(MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED).sendToTarget(); - } - - public void notifyExemptChanged() { - removeMessages(MSG_EXEMPT_CHANGED); - obtainMessage(MSG_EXEMPT_CHANGED).sendToTarget(); - } - - public void doUserRemoved(int userId) { - obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget(); - } - - public void onUidStateChanged(int uid, int procState) { - obtainMessage(MSG_ON_UID_STATE_CHANGED, uid, procState).sendToTarget(); - } - - public void onUidActive(int uid) { - obtainMessage(MSG_ON_UID_ACTIVE, uid, 0).sendToTarget(); - } - - public void onUidGone(int uid, boolean disabled) { - obtainMessage(MSG_ON_UID_GONE, uid, disabled ? 1 : 0).sendToTarget(); - } - - public void onUidIdle(int uid, boolean disabled) { - obtainMessage(MSG_ON_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget(); - } - - @Override - public void handleMessage(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; - - long start = mStatLogger.getTime(); - switch (msg.what) { - case MSG_UID_ACTIVE_STATE_CHANGED: - for (Listener l : cloneListeners()) { - l.onUidActiveStateChanged(sender, msg.arg1); - } - mStatLogger.logDurationStat(Stats.UID_ACTIVE_STATE_CHANGED, start); - return; - - case MSG_UID_FG_STATE_CHANGED: - for (Listener l : cloneListeners()) { - l.onUidForegroundStateChanged(sender, msg.arg1); - } - mStatLogger.logDurationStat(Stats.UID_FG_STATE_CHANGED, start); - return; - - case MSG_RUN_ANY_CHANGED: - for (Listener l : cloneListeners()) { - l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj); - } - mStatLogger.logDurationStat(Stats.RUN_ANY_CHANGED, start); - return; - - case MSG_ALL_UNWHITELISTED: - for (Listener l : cloneListeners()) { - l.onPowerSaveUnwhitelisted(sender); - } - mStatLogger.logDurationStat(Stats.ALL_UNWHITELISTED, start); - return; - - case MSG_ALL_WHITELIST_CHANGED: - for (Listener l : cloneListeners()) { - l.onPowerSaveWhitelistedChanged(sender); - } - mStatLogger.logDurationStat(Stats.ALL_WHITELIST_CHANGED, start); - return; - - case MSG_TEMP_WHITELIST_CHANGED: - for (Listener l : cloneListeners()) { - l.onTempPowerSaveWhitelistChanged(sender); - } - mStatLogger.logDurationStat(Stats.TEMP_WHITELIST_CHANGED, start); - return; - - case MSG_EXEMPT_CHANGED: - for (Listener l : cloneListeners()) { - l.onExemptChanged(sender); - } - mStatLogger.logDurationStat(Stats.EXEMPT_CHANGED, start); - return; - - case MSG_FORCE_ALL_CHANGED: - for (Listener l : cloneListeners()) { - l.onForceAllAppsStandbyChanged(sender); - } - mStatLogger.logDurationStat(Stats.FORCE_ALL_CHANGED, start); - return; - - case MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED: - // Feature flag for forced app standby changed. - final boolean unblockAlarms; - synchronized (mLock) { - unblockAlarms = !mForcedAppStandbyEnabled && !mForceAllAppsStandby; - } - for (Listener l : cloneListeners()) { - l.updateAllJobs(); - if (unblockAlarms) { - l.unblockAllUnrestrictedAlarms(); - } - } - mStatLogger.logDurationStat( - Stats.FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED, start); - return; - - case MSG_USER_REMOVED: - handleUserRemoved(msg.arg1); - return; - - case MSG_ON_UID_STATE_CHANGED: - handleUidStateChanged(msg.arg1, msg.arg2); - return; - case MSG_ON_UID_ACTIVE: - handleUidActive(msg.arg1); - return; - case MSG_ON_UID_GONE: - handleUidGone(msg.arg1, msg.arg1 != 0); - return; - case MSG_ON_UID_IDLE: - handleUidIdle(msg.arg1, msg.arg1 != 0); - return; - } - } - - public void handleUidStateChanged(int uid, int procState) { - synchronized (mLock) { - if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { - if (removeUidFromArray(mForegroundUids, uid, false)) { - mHandler.notifyUidForegroundStateChanged(uid); - } - } else { - if (addUidToArray(mForegroundUids, uid)) { - mHandler.notifyUidForegroundStateChanged(uid); - } - } - } - } - - public void handleUidActive(int uid) { - synchronized (mLock) { - if (addUidToArray(mActiveUids, uid)) { - mHandler.notifyUidActiveStateChanged(uid); - } - } - } - - public void handleUidGone(int uid, boolean disabled) { - removeUid(uid, true); - } - - public void handleUidIdle(int uid, boolean disabled) { - // Just to avoid excessive memcpy, don't remove from the array in this case. - removeUid(uid, false); - } - - private void removeUid(int uid, boolean remove) { - synchronized (mLock) { - if (removeUidFromArray(mActiveUids, uid, remove)) { - mHandler.notifyUidActiveStateChanged(uid); - } - if (removeUidFromArray(mForegroundUids, uid, remove)) { - mHandler.notifyUidForegroundStateChanged(uid); - } - } - } - } - - void handleUserRemoved(int removedUserId) { - synchronized (mLock) { - for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) { - final Pair pair = mRunAnyRestrictedPackages.valueAt(i); - final int uid = pair.first; - final int userId = UserHandle.getUserId(uid); - - if (userId == removedUserId) { - mRunAnyRestrictedPackages.removeAt(i); - } - } - cleanUpArrayForUser(mActiveUids, removedUserId); - cleanUpArrayForUser(mForegroundUids, removedUserId); - mExemptedPackages.remove(removedUserId); - } - } - - private void cleanUpArrayForUser(SparseBooleanArray array, int removedUserId) { - for (int i = array.size() - 1; i >= 0; i--) { - final int uid = array.keyAt(i); - final int userId = UserHandle.getUserId(uid); - - if (userId == removedUserId) { - array.removeAt(i); - } - } - } - - /** - * Called by device idle controller to update the power save whitelists. - */ - public void setPowerSaveWhitelistAppIds( - int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) { - synchronized (mLock) { - 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(); - } - - } - } - - /** - * @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. - - /** - * Register a new listener. - */ - public void addListener(@NonNull Listener listener) { - synchronized (mLock) { - mListeners.add(listener); - } - } - - /** - * @return whether alarms should be restricted for a UID package-name. - */ - public boolean areAlarmsRestricted(int uid, @NonNull String packageName, - boolean allowWhileIdle) { - return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false, - /* exemptOnBatterySaver =*/ allowWhileIdle); - } - - /** - * @return whether jobs should be restricted for a UID package-name. - */ - public boolean areJobsRestricted(int uid, @NonNull String packageName, - boolean hasForegroundExemption) { - return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true, - hasForegroundExemption); - } - - /** - * @return whether force-app-standby is effective for a UID package-name. - */ - private boolean isRestricted(int uid, @NonNull String packageName, - boolean useTempWhitelistToo, boolean exemptOnBatterySaver) { - if (isUidActive(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 (mForcedAppStandbyEnabled && isRunAnyRestrictedLocked(uid, packageName)) { - return true; - } - if (exemptOnBatterySaver) { - return false; - } - final int userId = UserHandle.getUserId(uid); - if (mExemptedPackages.contains(userId, packageName)) { - return false; - } - return mForceAllAppsStandby; - } - } - - /** - * @return whether a UID is in active or not. - * - * Note this information is based on the UID proc state callback, meaning it's updated - * asynchronously and may subtly be stale. If the fresh data is needed, use - * {@link ActivityManagerInternal#getUidProcessState} instead. - */ - public boolean isUidActive(int uid) { - if (UserHandle.isCore(uid)) { - return true; - } - synchronized (mLock) { - return mActiveUids.get(uid); - } - } - - /** - * @return whether a UID is in the foreground or not. - * - * Note this information is based on the UID proc state callback, meaning it's updated - * asynchronously and may subtly be stale. If the fresh data is needed, use - * {@link ActivityManagerInternal#getUidProcessState} instead. - */ - public boolean isUidInForeground(int uid) { - if (UserHandle.isCore(uid)) { - return true; - } - synchronized (mLock) { - return mForegroundUids.get(uid); - } - } - - /** - * @return whether force all apps standby is enabled or not. - * - */ - boolean isForceAllAppsStandbyEnabled() { - synchronized (mLock) { - return mForceAllAppsStandby; - } - } - - /** - * @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 !isRunAnyRestrictedLocked(uid, packageName); - } - } - - /** - * @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 ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid)); - } - } - - /** - * @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 ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid)); - } - } - - public void dump(PrintWriter pw, String indent) { - synchronized (mLock) { - pw.print(indent); - pw.println("Forced App Standby Feature enabled: " + mForcedAppStandbyEnabled); - - pw.print(indent); - pw.print("Force all apps standby: "); - pw.println(isForceAllAppsStandbyEnabled()); - - pw.print(indent); - pw.print("Small Battery Device: "); - pw.println(isSmallBatteryDevice()); - - pw.print(indent); - pw.print("Force all apps standby for small battery device: "); - pw.println(mForceAllAppStandbyForSmallBattery); - - pw.print(indent); - pw.print("Plugged In: "); - pw.println(mIsPluggedIn); - - pw.print(indent); - pw.print("Active uids: "); - dumpUids(pw, mActiveUids); - - pw.print(indent); - pw.print("Foreground uids: "); - dumpUids(pw, mForegroundUids); - - 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("Exempted packages:"); - for (int i = 0; i < mExemptedPackages.size(); i++) { - pw.print(indent); - pw.print(" User "); - pw.print(mExemptedPackages.keyAt(i)); - pw.println(); - - for (int j = 0; j < mExemptedPackages.sizeAt(i); j++) { - pw.print(indent); - pw.print(" "); - pw.print(mExemptedPackages.valueAt(i, j)); - pw.println(); - } - } - pw.println(); - - pw.print(indent); - pw.println("Restricted packages:"); - for (Pair uidAndPackage : mRunAnyRestrictedPackages) { - pw.print(indent); - pw.print(" "); - pw.print(UserHandle.formatUid(uidAndPackage.first)); - pw.print(" "); - pw.print(uidAndPackage.second); - pw.println(); - } - - mStatLogger.dump(pw, indent); - } - } - - private void dumpUids(PrintWriter pw, SparseBooleanArray array) { - pw.print("["); - - String sep = ""; - for (int i = 0; i < array.size(); i++) { - if (array.valueAt(i)) { - pw.print(sep); - pw.print(UserHandle.formatUid(array.keyAt(i))); - sep = " "; - } - } - 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); - proto.write(ForceAppStandbyTrackerProto.IS_SMALL_BATTERY_DEVICE, - isSmallBatteryDevice()); - proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY_FOR_SMALL_BATTERY, - mForceAllAppStandbyForSmallBattery); - proto.write(ForceAppStandbyTrackerProto.IS_PLUGGED_IN, mIsPluggedIn); - - for (int i = 0; i < mActiveUids.size(); i++) { - if (mActiveUids.valueAt(i)) { - proto.write(ForceAppStandbyTrackerProto.ACTIVE_UIDS, - mActiveUids.keyAt(i)); - } - } - - 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 (int i = 0; i < mExemptedPackages.size(); i++) { - for (int j = 0; j < mExemptedPackages.sizeAt(i); j++) { - final long token2 = proto.start( - ForceAppStandbyTrackerProto.EXEMPTED_PACKAGES); - - proto.write(ExemptedPackage.USER_ID, mExemptedPackages.keyAt(i)); - proto.write(ExemptedPackage.PACKAGE_NAME, mExemptedPackages.valueAt(i, j)); - - proto.end(token2); - } - } - - for (Pair 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); - } - - mStatLogger.dumpProto(proto, ForceAppStandbyTrackerProto.STATS); - - proto.end(token); - } - } -} diff --git a/services/core/java/com/android/server/StatLogger.java b/services/core/java/com/android/server/StatLogger.java index f2117314df00..0e6f5e2338c7 100644 --- a/services/core/java/com/android/server/StatLogger.java +++ b/services/core/java/com/android/server/StatLogger.java @@ -17,6 +17,7 @@ package com.android.server; import android.os.SystemClock; +import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; @@ -33,6 +34,8 @@ import java.io.PrintWriter; * @hide */ public class StatLogger { + private static final String TAG = "StatLogger"; + private final Object mLock = new Object(); private final int SIZE; @@ -66,8 +69,12 @@ public class StatLogger { */ public void logDurationStat(int eventId, long start) { synchronized (mLock) { - mCountStats[eventId]++; - mDurationStats[eventId] += (getTime() - start); + if (eventId >= 0 && eventId < SIZE) { + mCountStats[eventId]++; + mDurationStats[eventId] += (getTime() - start); + } else { + Slog.wtf(TAG, "Invalid event ID: " + eventId); + } } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a6c4fc952839..9db4e2b4e4c2 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -25863,6 +25863,14 @@ public class ActivityManagerService extends IActivityManager.Stub public boolean isCallerRecents(int callingUid) { return getRecentTasks().isCallerRecents(callingUid); } + + @Override + public boolean isUidActive(int uid) { + synchronized (ActivityManagerService.this) { + final UidRecord uidRec = mActiveUids.get(uid); + return (uidRec != null) && !uidRec.idle; + } + } } /** diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java index 401c05e80307..47a4fb24201c 100644 --- a/services/core/java/com/android/server/job/JobSchedulerService.java +++ b/services/core/java/com/android/server/job/JobSchedulerService.java @@ -40,7 +40,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.Intent.UriFlags; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; @@ -79,7 +78,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.DeviceIdleController; import com.android.server.FgThread; -import com.android.server.ForceAppStandbyTracker; +import com.android.server.AppStateTracker; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob; import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob; @@ -184,7 +183,7 @@ public final class JobSchedulerService extends com.android.server.SystemService ActivityManagerInternal mActivityManagerInternal; IBatteryStats mBatteryStats; DeviceIdleController.LocalService mLocalDeviceIdleController; - final ForceAppStandbyTracker mForceAppStandbyTracker; + AppStateTracker mAppStateTracker; /** * Set to true once we are allowed to run third party apps. @@ -787,20 +786,13 @@ public final class JobSchedulerService extends com.android.server.SystemService } /** - * Return whether an UID is in the foreground or not. + * Return whether an UID is active or idle. */ - private boolean isUidInForeground(int uid) { - synchronized (mLock) { - if (mUidPriorityOverride.get(uid, 0) > 0) { - return true; - } - } - // Note UID observer may not be called in time, so we always check with the AM. - return mActivityManagerInternal.getUidProcessState(uid) - <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; + private boolean isUidActive(int uid) { + return mAppStateTracker.isUidActiveSynced(uid); } - private final Predicate mIsUidInForegroundPredicate = this::isUidInForeground; + private final Predicate mIsUidActivePredicate = this::isUidActive; public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName, int userId, String tag) { @@ -826,7 +818,7 @@ public final class JobSchedulerService extends com.android.server.SystemService // If any of work item is enqueued when the source is in the foreground, // exempt the entire job. - toCancel.maybeAddForegroundExemption(mIsUidInForegroundPredicate); + toCancel.maybeAddForegroundExemption(mIsUidActivePredicate); return JobScheduler.RESULT_SUCCESS; } @@ -838,7 +830,7 @@ public final class JobSchedulerService extends com.android.server.SystemService // Note if it's a sync job, this method is called on the handler so it's not exactly // the state when requestSync() was called, but that should be fine because of the // 1 minute foreground grace period. - jobStatus.maybeAddForegroundExemption(mIsUidInForegroundPredicate); + jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate); if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString()); // Jobs on behalf of others don't apply to the per-app job cap @@ -1123,8 +1115,6 @@ public final class JobSchedulerService extends com.android.server.SystemService mDeviceIdleJobsController = DeviceIdleJobsController.get(this); mControllers.add(mDeviceIdleJobsController); - mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context); - // If the job store determined that it can't yet reschedule persisted jobs, // we need to start watching the clock. if (!mJobs.jobTimesInflatedValid()) { @@ -1185,7 +1175,8 @@ public final class JobSchedulerService extends com.android.server.SystemService if (PHASE_SYSTEM_SERVICES_READY == phase) { mConstants.start(getContext().getContentResolver()); - mForceAppStandbyTracker.start(); + mAppStateTracker = Preconditions.checkNotNull( + LocalServices.getService(AppStateTracker.class)); // Register br for package removals and user removals. final IntentFilter filter = new IntentFilter(); 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 5eb77000a4f1..e8057fbd4150 100644 --- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -22,8 +22,10 @@ import android.os.UserHandle; import android.util.Slog; import android.util.proto.ProtoOutputStream; -import com.android.server.ForceAppStandbyTracker; -import com.android.server.ForceAppStandbyTracker.Listener; +import com.android.internal.util.Preconditions; +import com.android.server.AppStateTracker; +import com.android.server.AppStateTracker.Listener; +import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; import com.android.server.job.StateControllerProto; @@ -42,8 +44,7 @@ public final class BackgroundJobsController extends StateController { private final JobSchedulerService mJobSchedulerService; - private final ForceAppStandbyTracker mForceAppStandbyTracker; - + private final AppStateTracker mAppStateTracker; public static BackgroundJobsController get(JobSchedulerService service) { synchronized (sCreationLock) { @@ -59,10 +60,9 @@ public final class BackgroundJobsController extends StateController { super(service, context, lock); mJobSchedulerService = service; - mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context); - - mForceAppStandbyTracker.addListener(mForceAppStandbyListener); - mForceAppStandbyTracker.start(); + mAppStateTracker = Preconditions.checkNotNull( + LocalServices.getService(AppStateTracker.class)); + mAppStateTracker.addListener(mForceAppStandbyListener); } @Override @@ -79,7 +79,7 @@ public final class BackgroundJobsController extends StateController { public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { pw.println("BackgroundJobsController"); - mForceAppStandbyTracker.dump(pw, ""); + mAppStateTracker.dump(pw, ""); pw.println("Job state:"); mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> { @@ -92,16 +92,16 @@ public final class BackgroundJobsController extends StateController { jobStatus.printUniqueId(pw); pw.print(" from "); UserHandle.formatUid(pw, uid); - pw.print(mForceAppStandbyTracker.isUidActive(uid) ? " active" : " idle"); - if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) || - mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) { + pw.print(mAppStateTracker.isUidActive(uid) ? " active" : " idle"); + if (mAppStateTracker.isUidPowerSaveWhitelisted(uid) || + mAppStateTracker.isUidTempPowerSaveWhitelisted(uid)) { pw.print(", whitelisted"); } pw.print(": "); pw.print(sourcePkg); pw.print(" [RUN_ANY_IN_BACKGROUND "); - pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(uid, sourcePkg) + pw.print(mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, sourcePkg) ? "allowed]" : "disallowed]"); if ((jobStatus.satisfiedConstraints @@ -118,7 +118,7 @@ public final class BackgroundJobsController extends StateController { final long token = proto.start(fieldId); final long mToken = proto.start(StateControllerProto.BACKGROUND); - mForceAppStandbyTracker.dumpProto(proto, + mAppStateTracker.dumpProto(proto, StateControllerProto.BackgroundJobsController.FORCE_APP_STANDBY_TRACKER); mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> { @@ -136,14 +136,14 @@ public final class BackgroundJobsController extends StateController { proto.write(TrackedJob.SOURCE_PACKAGE_NAME, sourcePkg); proto.write(TrackedJob.IS_IN_FOREGROUND, - mForceAppStandbyTracker.isUidActive(sourceUid)); + mAppStateTracker.isUidActive(sourceUid)); proto.write(TrackedJob.IS_WHITELISTED, - mForceAppStandbyTracker.isUidPowerSaveWhitelisted(sourceUid) || - mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(sourceUid)); + mAppStateTracker.isUidPowerSaveWhitelisted(sourceUid) || + mAppStateTracker.isUidTempPowerSaveWhitelisted(sourceUid)); proto.write( TrackedJob.CAN_RUN_ANY_IN_BACKGROUND, - mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed( + mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed( sourceUid, sourcePkg)); proto.write( @@ -197,7 +197,7 @@ public final class BackgroundJobsController extends StateController { final int uid = jobStatus.getSourceUid(); final String packageName = jobStatus.getSourcePackageName(); - final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName, + final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName, (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION) != 0); diff --git a/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java new file mode 100644 index 000000000000..90db2a328c6f --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java @@ -0,0 +1,1237 @@ +/* + * 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.AppStateTracker.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.ActivityManagerInternal; +import android.app.AppOpsManager; +import android.app.AppOpsManager.OpEntry; +import android.app.AppOpsManager.PackageOps; +import android.app.IActivityManager; +import android.app.IUidObserver; +import android.app.usage.UsageStatsManager; +import android.app.usage.UsageStatsManagerInternal; +import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; +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.provider.Settings.Global; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.test.mock.MockContentResolver; +import android.util.ArraySet; +import android.util.Pair; + +import com.android.internal.app.IAppOpsCallback; +import com.android.internal.app.IAppOpsService; +import com.android.server.AppStateTracker.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.HashMap; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Tests for {@link AppStateTracker} + * + * Run with: + atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AppStateTrackerTest { + + private class AppStateTrackerTestable extends AppStateTracker { + AppStateTrackerTestable() { + super(mMockContext, Looper.getMainLooper()); + } + + @Override + AppOpsManager injectAppOpsManager() { + return mMockAppOpsManager; + } + + @Override + IAppOpsService injectIAppOpsService() { + return mMockIAppOpsService; + } + + @Override + IActivityManager injectIActivityManager() { + return mMockIActivityManager; + } + + @Override + ActivityManagerInternal injectActivityManagerInternal() { + return mMockIActivityManagerInternal; + } + + @Override + PowerManagerInternal injectPowerManagerInternal() { + return mMockPowerManagerInternal; + } + + @Override + UsageStatsManagerInternal injectUsageStatsManagerInternal() { + return mMockUsageStatsManagerInternal; + } + + @Override + int injectGetGlobalSettingInt(String key, int def) { + Integer val = mGlobalSettings.get(key); + + return (val == null) ? def : val; + } + + @Override + boolean isSmallBatteryDevice() { return mIsSmallBatteryDevice; }; + } + + 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 ActivityManagerInternal mMockIActivityManagerInternal; + + @Mock + private AppOpsManager mMockAppOpsManager; + + @Mock + private IAppOpsService mMockIAppOpsService; + + @Mock + private PowerManagerInternal mMockPowerManagerInternal; + + @Mock + private UsageStatsManagerInternal mMockUsageStatsManagerInternal; + + private MockContentResolver mMockContentResolver; + + private IUidObserver mIUidObserver; + private IAppOpsCallback.Stub mAppOpsCallback; + private Consumer mPowerSaveObserver; + private BroadcastReceiver mReceiver; + private AppIdleStateChangeListener mAppIdleStateChangeListener; + + private boolean mPowerSaveMode; + private boolean mIsSmallBatteryDevice; + + private final ArraySet> mRestrictedPackages = new ArraySet(); + + private final HashMap mGlobalSettings = new HashMap<>(); + + @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 AppStateTrackerTestable 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 AppStateTrackerTestable instance = new AppStateTrackerTestable(); + + return instance; + } + + private void callStart(AppStateTrackerTestable 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()); + + mMockContentResolver = new MockContentResolver(); + when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); + + // Call start. + instance.onSystemServicesReady(); + + // Capture the listeners. + ArgumentCaptor uidObserverArgumentCaptor = + ArgumentCaptor.forClass(IUidObserver.class); + ArgumentCaptor appOpsCallbackCaptor = + ArgumentCaptor.forClass(IAppOpsCallback.Stub.class); + ArgumentCaptor> powerSaveObserverCaptor = + ArgumentCaptor.forClass(Consumer.class); + ArgumentCaptor receiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + ArgumentCaptor appIdleStateChangeListenerCaptor = + ArgumentCaptor.forClass(AppIdleStateChangeListener.class); + + verify(mMockIActivityManager).registerUidObserver( + uidObserverArgumentCaptor.capture(), + eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE + | ActivityManager.UID_OBSERVER_ACTIVE + | ActivityManager.UID_OBSERVER_PROCSTATE), + 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)); + verify(mMockUsageStatsManagerInternal).addAppIdleStateChangeListener( + appIdleStateChangeListenerCaptor.capture()); + + mIUidObserver = uidObserverArgumentCaptor.getValue(); + mAppOpsCallback = appOpsCallbackCaptor.getValue(); + mPowerSaveObserver = powerSaveObserverCaptor.getValue(); + mReceiver = receiverCaptor.getValue(); + mAppIdleStateChangeListener = appIdleStateChangeListenerCaptor.getValue(); + + assertNotNull(mIUidObserver); + assertNotNull(mAppOpsCallback); + assertNotNull(mPowerSaveObserver); + assertNotNull(mReceiver); + assertNotNull(instance.mFlagsObserver); + } + + 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(AppStateTrackerTestable instance, int uid, String packageName, + int restrictionTypes, boolean exemptFromBatterySaver) { + assertEquals(((restrictionTypes & JOBS_ONLY) != 0), + instance.areJobsRestricted(uid, packageName, exemptFromBatterySaver)); + assertEquals(((restrictionTypes & ALARMS_ONLY) != 0), + instance.areAlarmsRestricted(uid, packageName, exemptFromBatterySaver)); + } + + private void areRestricted(AppStateTrackerTestable instance, int uid, String packageName, + int restrictionTypes) { + areRestricted(instance, uid, packageName, restrictionTypes, + /*exemptFromBatterySaver=*/ false); + } + + private void areRestrictedWithExemption(AppStateTrackerTestable instance, + int uid, String packageName, int restrictionTypes) { + areRestricted(instance, uid, packageName, restrictionTypes, + /*exemptFromBatterySaver=*/ true); + } + + @Test + public void testAll() throws Exception { + final AppStateTrackerTestable 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); + + areRestrictedWithExemption(instance, UID_1, PACKAGE_1, NONE); + areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE); + areRestrictedWithExemption(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); + + areRestrictedWithExemption(instance, UID_1, PACKAGE_1, NONE); + areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE); + areRestrictedWithExemption(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + // Toggle the foreground state. + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + + assertFalse(instance.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + assertTrue(instance.isUidActive(Process.SYSTEM_UID)); + + mIUidObserver.onUidActive(UID_1); + waitUntilMainHandlerDrain(); + 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.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + + mIUidObserver.onUidGone(UID_1, /*disable=*/ false); + waitUntilMainHandlerDrain(); + 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.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + + mIUidObserver.onUidActive(UID_1); + waitUntilMainHandlerDrain(); + 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); + waitUntilMainHandlerDrain(); + 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.isUidActive(UID_1)); + assertFalse(instance.isUidActive(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)); + } + + @Test + public void testUidStateForeground() throws Exception { + final AppStateTrackerTestable instance = newInstance(); + callStart(instance); + + mIUidObserver.onUidActive(UID_1); + + waitUntilMainHandlerDrain(); + assertTrue(instance.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + assertTrue(instance.isUidActive(Process.SYSTEM_UID)); + + assertTrue(instance.isUidActiveSynced(UID_1)); + assertFalse(instance.isUidActiveSynced(UID_2)); + assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID)); + + assertFalse(instance.isUidInForeground(UID_1)); + assertFalse(instance.isUidInForeground(UID_2)); + assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); + + + mIUidObserver.onUidStateChanged(UID_2, + ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 0); + + waitUntilMainHandlerDrain(); + assertTrue(instance.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + assertTrue(instance.isUidActive(Process.SYSTEM_UID)); + + assertTrue(instance.isUidActiveSynced(UID_1)); + assertFalse(instance.isUidActiveSynced(UID_2)); + assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID)); + + assertFalse(instance.isUidInForeground(UID_1)); + assertTrue(instance.isUidInForeground(UID_2)); + assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); + + + mIUidObserver.onUidStateChanged(UID_1, + ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0); + + waitUntilMainHandlerDrain(); + assertTrue(instance.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + assertTrue(instance.isUidActive(Process.SYSTEM_UID)); + + assertTrue(instance.isUidInForeground(UID_1)); + assertTrue(instance.isUidInForeground(UID_2)); + assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); + + mIUidObserver.onUidGone(UID_1, true); + + waitUntilMainHandlerDrain(); + assertFalse(instance.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + assertTrue(instance.isUidActive(Process.SYSTEM_UID)); + + assertFalse(instance.isUidInForeground(UID_1)); + assertTrue(instance.isUidInForeground(UID_2)); + assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); + + mIUidObserver.onUidIdle(UID_2, true); + + waitUntilMainHandlerDrain(); + assertFalse(instance.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + assertTrue(instance.isUidActive(Process.SYSTEM_UID)); + + assertFalse(instance.isUidInForeground(UID_1)); + assertFalse(instance.isUidInForeground(UID_2)); + assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); + + mIUidObserver.onUidStateChanged(UID_1, + ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0); + + waitUntilMainHandlerDrain(); + assertFalse(instance.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + assertTrue(instance.isUidActive(Process.SYSTEM_UID)); + + assertTrue(instance.isUidInForeground(UID_1)); + assertFalse(instance.isUidInForeground(UID_2)); + assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); + + mIUidObserver.onUidStateChanged(UID_1, + ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0); + + waitUntilMainHandlerDrain(); + assertFalse(instance.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + assertTrue(instance.isUidActive(Process.SYSTEM_UID)); + + assertFalse(instance.isUidActiveSynced(UID_1)); + assertFalse(instance.isUidActiveSynced(UID_2)); + assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID)); + + assertFalse(instance.isUidInForeground(UID_1)); + assertFalse(instance.isUidInForeground(UID_2)); + assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); + + // The result from AMI.isUidActive() only affects isUidActiveSynced(). + when(mMockIActivityManagerInternal.isUidActive(anyInt())).thenReturn(true); + + assertFalse(instance.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_2)); + assertTrue(instance.isUidActive(Process.SYSTEM_UID)); + + assertTrue(instance.isUidActiveSynced(UID_1)); + assertTrue(instance.isUidActiveSynced(UID_2)); + assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID)); + + assertFalse(instance.isUidInForeground(UID_1)); + assertFalse(instance.isUidInForeground(UID_2)); + assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); + + } + + @Test + public void testExempt() throws Exception { + final AppStateTrackerTestable 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, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); + + // Exempt package 2 on user-10. + mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false, + UsageStatsManager.STANDBY_BUCKET_EXEMPTED); + + areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_2, PACKAGE_2, NONE); + + areRestrictedWithExemption(instance, UID_1, PACKAGE_1, NONE); + areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE); + areRestrictedWithExemption(instance, UID_10_2, PACKAGE_2, NONE); + + // Exempt package 1 on user-0. + mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_1, /*user=*/ 0, false, + UsageStatsManager.STANDBY_BUCKET_EXEMPTED); + + areRestricted(instance, UID_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_2, PACKAGE_2, NONE); + + // Unexempt package 2 on user-10. + mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false, + UsageStatsManager.STANDBY_BUCKET_ACTIVE); + + areRestricted(instance, UID_1, PACKAGE_1, NONE); + areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); + areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS); + + // Check force-app-standby. + // EXEMPT doesn't exempt from force-app-standby. + mPowerSaveMode = false; + mPowerSaveObserver.accept(getPowerSaveState()); + + mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_1, /*user=*/ 0, false, + UsageStatsManager.STANDBY_BUCKET_EXEMPTED); + mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 0, false, + UsageStatsManager.STANDBY_BUCKET_EXEMPTED); + + setAppOps(UID_1, PACKAGE_1, true); + + areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestricted(instance, UID_2, PACKAGE_2, NONE); + + areRestrictedWithExemption(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); + areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE); + } + + public void loadPersistedAppOps() throws Exception { + final AppStateTrackerTestable instance = newInstance(); + + final List ops = new ArrayList<>(); + + //-------------------------------------------------- + List 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( + AppStateTracker.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( + AppStateTracker.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( + AppStateTracker.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( + AppStateTracker.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 AppStateTrackerTestable instance = newInstance(); + callStart(instance); + + AppStateTracker.Listener l = mock(AppStateTracker.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); + + // Updating to the same state should not fire listener + mPowerSaveMode = false; + mPowerSaveObserver.accept(getPowerSaveState()); + + assertNoCallbacks(l); + } + + @Test + public void testAllListeners() throws Exception { + final AppStateTrackerTestable instance = newInstance(); + callStart(instance); + + AppStateTracker.Listener l = mock(AppStateTracker.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(1)).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(0)).updateJobsForUidPackage(anyInt(), anyString()); + + 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()); + + 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_1, UID_2}, new int[] {}); + + waitUntilMainHandlerDrain(); + // Called once for updating all whitelist and once for updating temp whitelist + verify(l, times(2)).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(1)).unblockAlarmsForUid(eq(UID_10_1)); + 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(1)).unblockAlarmsForUid(eq(UID_10_1)); + 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()); + + waitUntilMainHandlerDrain(); + verify(l, times(1)).updateAllJobs(); + verify(l, times(0)).updateJobsForUid(eq(UID_10_1)); + 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); + + 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(1)).unblockAlarmsForUid(eq(UID_10_1)); + 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(1)).unblockAlarmsForUid(eq(UID_10_1)); + 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 AppStateTrackerTestable instance = newInstance(); + callStart(instance); + + mIUidObserver.onUidActive(UID_1); + mIUidObserver.onUidActive(UID_10_1); + + waitUntilMainHandlerDrain(); + + setAppOps(UID_2, PACKAGE_2, true); + setAppOps(UID_10_2, PACKAGE_2, true); + + assertTrue(instance.isUidActive(UID_1)); + assertTrue(instance.isUidActive(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.isUidActive(UID_1)); + assertFalse(instance.isUidActive(UID_10_1)); + + assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2)); + assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2)); + } + + @Test + public void testSmallBatteryAndPluggedIn() throws Exception { + // This is a small battery device + mIsSmallBatteryDevice = true; + + final AppStateTrackerTestable instance = newInstance(); + callStart(instance); + assertFalse(instance.isForceAllAppsStandbyEnabled()); + + // Setting/experiment for all app standby for small battery is enabled + mGlobalSettings.put(Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 1); + instance.mFlagsObserver.onChange(true, + Global.getUriFor(Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED)); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + + // When battery is plugged in, force app standby is disabled + Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); + intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB); + mReceiver.onReceive(mMockContext, intent); + assertFalse(instance.isForceAllAppsStandbyEnabled()); + + // When battery stops plugged in, force app standby is enabled + mReceiver.onReceive(mMockContext, new Intent(Intent.ACTION_BATTERY_CHANGED)); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + } + + @Test + public void testNotSmallBatteryAndPluggedIn() throws Exception { + // Not a small battery device, so plugged in status should not affect forced app standby + mIsSmallBatteryDevice = false; + + final AppStateTrackerTestable instance = newInstance(); + callStart(instance); + assertFalse(instance.isForceAllAppsStandbyEnabled()); + + mPowerSaveMode = true; + mPowerSaveObserver.accept(getPowerSaveState()); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + + // When battery is plugged in, force app standby is unaffected + Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); + intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB); + mReceiver.onReceive(mMockContext, intent); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + + // When battery stops plugged in, force app standby is unaffected + mReceiver.onReceive(mMockContext, new Intent(Intent.ACTION_BATTERY_CHANGED)); + assertTrue(instance.isForceAllAppsStandbyEnabled()); + } + + static int[] array(int... appIds) { + Arrays.sort(appIds); + return appIds; + } + + private final Random mRandom = new Random(); + + int[] makeRandomArray() { + final ArrayList 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, AppStateTracker.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 = AppStateTracker.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); + } +} diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java deleted file mode 100644 index a499472d197f..000000000000 --- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java +++ /dev/null @@ -1,1200 +0,0 @@ -/* - * 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.app.usage.UsageStatsManager; -import android.app.usage.UsageStatsManagerInternal; -import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.BatteryManager; -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.provider.Settings.Global; -import android.support.test.filters.SmallTest; -import android.support.test.runner.AndroidJUnit4; -import android.test.mock.MockContentResolver; -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.HashMap; -import java.util.List; -import java.util.Random; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - -/** - * Tests for {@link ForceAppStandbyTracker} - * - * Run with: - atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java - */ -@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; - } - - @Override - UsageStatsManagerInternal injectUsageStatsManagerInternal() { - return mMockUsageStatsManagerInternal; - } - - @Override - int injectGetGlobalSettingInt(String key, int def) { - Integer val = mGlobalSettings.get(key); - - return (val == null) ? def : val; - } - - @Override - boolean isSmallBatteryDevice() { return mIsSmallBatteryDevice; }; - } - - 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; - - @Mock - private UsageStatsManagerInternal mMockUsageStatsManagerInternal; - - private MockContentResolver mMockContentResolver; - - private IUidObserver mIUidObserver; - private IAppOpsCallback.Stub mAppOpsCallback; - private Consumer mPowerSaveObserver; - private BroadcastReceiver mReceiver; - private AppIdleStateChangeListener mAppIdleStateChangeListener; - - private boolean mPowerSaveMode; - private boolean mIsSmallBatteryDevice; - - private final ArraySet> mRestrictedPackages = new ArraySet(); - - private final HashMap mGlobalSettings = new HashMap<>(); - - @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()); - - mMockContentResolver = new MockContentResolver(); - when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); - - // Call start. - instance.start(); - - // Capture the listeners. - ArgumentCaptor uidObserverArgumentCaptor = - ArgumentCaptor.forClass(IUidObserver.class); - ArgumentCaptor appOpsCallbackCaptor = - ArgumentCaptor.forClass(IAppOpsCallback.Stub.class); - ArgumentCaptor> powerSaveObserverCaptor = - ArgumentCaptor.forClass(Consumer.class); - ArgumentCaptor receiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - ArgumentCaptor appIdleStateChangeListenerCaptor = - ArgumentCaptor.forClass(AppIdleStateChangeListener.class); - - verify(mMockIActivityManager).registerUidObserver( - uidObserverArgumentCaptor.capture(), - eq(ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE - | ActivityManager.UID_OBSERVER_ACTIVE - | ActivityManager.UID_OBSERVER_PROCSTATE), - 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)); - verify(mMockUsageStatsManagerInternal).addAppIdleStateChangeListener( - appIdleStateChangeListenerCaptor.capture()); - - mIUidObserver = uidObserverArgumentCaptor.getValue(); - mAppOpsCallback = appOpsCallbackCaptor.getValue(); - mPowerSaveObserver = powerSaveObserverCaptor.getValue(); - mReceiver = receiverCaptor.getValue(); - mAppIdleStateChangeListener = appIdleStateChangeListenerCaptor.getValue(); - - assertNotNull(mIUidObserver); - assertNotNull(mAppOpsCallback); - assertNotNull(mPowerSaveObserver); - assertNotNull(mReceiver); - assertNotNull(instance.mFlagsObserver); - } - - 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, boolean exemptFromBatterySaver) { - assertEquals(((restrictionTypes & JOBS_ONLY) != 0), - instance.areJobsRestricted(uid, packageName, exemptFromBatterySaver)); - assertEquals(((restrictionTypes & ALARMS_ONLY) != 0), - instance.areAlarmsRestricted(uid, packageName, exemptFromBatterySaver)); - } - - private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName, - int restrictionTypes) { - areRestricted(instance, uid, packageName, restrictionTypes, - /*exemptFromBatterySaver=*/ false); - } - - private void areRestrictedWithExemption(ForceAppStandbyTrackerTestable instance, - int uid, String packageName, int restrictionTypes) { - areRestricted(instance, uid, packageName, restrictionTypes, - /*exemptFromBatterySaver=*/ true); - } - - @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); - - areRestrictedWithExemption(instance, UID_1, PACKAGE_1, NONE); - areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE); - areRestrictedWithExemption(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); - - areRestrictedWithExemption(instance, UID_1, PACKAGE_1, NONE); - areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE); - areRestrictedWithExemption(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); - - // Toggle the foreground state. - mPowerSaveMode = true; - mPowerSaveObserver.accept(getPowerSaveState()); - - assertFalse(instance.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - - mIUidObserver.onUidActive(UID_1); - waitUntilMainHandlerDrain(); - 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.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - - mIUidObserver.onUidGone(UID_1, /*disable=*/ false); - waitUntilMainHandlerDrain(); - 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.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - - mIUidObserver.onUidActive(UID_1); - waitUntilMainHandlerDrain(); - 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); - waitUntilMainHandlerDrain(); - 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.isUidActive(UID_1)); - assertFalse(instance.isUidActive(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)); - } - - @Test - public void testUidStateForeground() throws Exception { - final ForceAppStandbyTrackerTestable instance = newInstance(); - callStart(instance); - - mIUidObserver.onUidActive(UID_1); - - waitUntilMainHandlerDrain(); - assertTrue(instance.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - - assertFalse(instance.isUidInForeground(UID_1)); - assertFalse(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - - - mIUidObserver.onUidStateChanged(UID_2, - ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 0); - - waitUntilMainHandlerDrain(); - assertTrue(instance.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - - assertFalse(instance.isUidInForeground(UID_1)); - assertTrue(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - - - mIUidObserver.onUidStateChanged(UID_1, - ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0); - - waitUntilMainHandlerDrain(); - assertTrue(instance.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - - assertTrue(instance.isUidInForeground(UID_1)); - assertTrue(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - - mIUidObserver.onUidGone(UID_1, true); - - waitUntilMainHandlerDrain(); - assertFalse(instance.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - - assertFalse(instance.isUidInForeground(UID_1)); - assertTrue(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - - mIUidObserver.onUidIdle(UID_2, true); - - waitUntilMainHandlerDrain(); - assertFalse(instance.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - - assertFalse(instance.isUidInForeground(UID_1)); - assertFalse(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - - mIUidObserver.onUidStateChanged(UID_1, - ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0); - - waitUntilMainHandlerDrain(); - assertFalse(instance.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - - assertTrue(instance.isUidInForeground(UID_1)); - assertFalse(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - - mIUidObserver.onUidStateChanged(UID_1, - ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0); - - waitUntilMainHandlerDrain(); - assertFalse(instance.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_2)); - assertTrue(instance.isUidActive(Process.SYSTEM_UID)); - - assertFalse(instance.isUidInForeground(UID_1)); - assertFalse(instance.isUidInForeground(UID_2)); - assertTrue(instance.isUidInForeground(Process.SYSTEM_UID)); - } - - @Test - public void testExempt() 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, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS); - areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE); - - // Exempt package 2 on user-10. - mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false, - UsageStatsManager.STANDBY_BUCKET_EXEMPTED); - - areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); - areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); - areRestricted(instance, UID_10_2, PACKAGE_2, NONE); - - areRestrictedWithExemption(instance, UID_1, PACKAGE_1, NONE); - areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE); - areRestrictedWithExemption(instance, UID_10_2, PACKAGE_2, NONE); - - // Exempt package 1 on user-0. - mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_1, /*user=*/ 0, false, - UsageStatsManager.STANDBY_BUCKET_EXEMPTED); - - areRestricted(instance, UID_1, PACKAGE_1, NONE); - areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); - areRestricted(instance, UID_10_2, PACKAGE_2, NONE); - - // Unexempt package 2 on user-10. - mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 10, false, - UsageStatsManager.STANDBY_BUCKET_ACTIVE); - - areRestricted(instance, UID_1, PACKAGE_1, NONE); - areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS); - areRestricted(instance, UID_10_2, PACKAGE_2, JOBS_AND_ALARMS); - - // Check force-app-standby. - // EXEMPT doesn't exempt from force-app-standby. - mPowerSaveMode = false; - mPowerSaveObserver.accept(getPowerSaveState()); - - mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_1, /*user=*/ 0, false, - UsageStatsManager.STANDBY_BUCKET_EXEMPTED); - mAppIdleStateChangeListener.onAppIdleStateChanged(PACKAGE_2, /*user=*/ 0, false, - UsageStatsManager.STANDBY_BUCKET_EXEMPTED); - - setAppOps(UID_1, PACKAGE_1, true); - - areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); - areRestricted(instance, UID_2, PACKAGE_2, NONE); - - areRestrictedWithExemption(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS); - areRestrictedWithExemption(instance, UID_2, PACKAGE_2, NONE); - } - - public void loadPersistedAppOps() throws Exception { - final ForceAppStandbyTrackerTestable instance = newInstance(); - - final List ops = new ArrayList<>(); - - //-------------------------------------------------- - List 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); - - // Updating to the same state should not fire listener - mPowerSaveMode = false; - 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(1)).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(0)).updateJobsForUidPackage(anyInt(), anyString()); - - 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()); - - 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_1, UID_2}, new int[] {}); - - waitUntilMainHandlerDrain(); - // Called once for updating all whitelist and once for updating temp whitelist - verify(l, times(2)).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(1)).unblockAlarmsForUid(eq(UID_10_1)); - 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(1)).unblockAlarmsForUid(eq(UID_10_1)); - 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()); - - waitUntilMainHandlerDrain(); - verify(l, times(1)).updateAllJobs(); - verify(l, times(0)).updateJobsForUid(eq(UID_10_1)); - 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); - - 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(1)).unblockAlarmsForUid(eq(UID_10_1)); - 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(1)).unblockAlarmsForUid(eq(UID_10_1)); - 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); - - waitUntilMainHandlerDrain(); - - setAppOps(UID_2, PACKAGE_2, true); - setAppOps(UID_10_2, PACKAGE_2, true); - - assertTrue(instance.isUidActive(UID_1)); - assertTrue(instance.isUidActive(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.isUidActive(UID_1)); - assertFalse(instance.isUidActive(UID_10_1)); - - assertFalse(instance.isRunAnyInBackgroundAppOpsAllowed(UID_2, PACKAGE_2)); - assertTrue(instance.isRunAnyInBackgroundAppOpsAllowed(UID_10_2, PACKAGE_2)); - } - - @Test - public void testSmallBatteryAndPluggedIn() throws Exception { - // This is a small battery device - mIsSmallBatteryDevice = true; - - final ForceAppStandbyTrackerTestable instance = newInstance(); - callStart(instance); - assertFalse(instance.isForceAllAppsStandbyEnabled()); - - // Setting/experiment for all app standby for small battery is enabled - mGlobalSettings.put(Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED, 1); - instance.mFlagsObserver.onChange(true, - Global.getUriFor(Global.FORCED_APP_STANDBY_FOR_SMALL_BATTERY_ENABLED)); - assertTrue(instance.isForceAllAppsStandbyEnabled()); - - // When battery is plugged in, force app standby is disabled - Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); - intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB); - mReceiver.onReceive(mMockContext, intent); - assertFalse(instance.isForceAllAppsStandbyEnabled()); - - // When battery stops plugged in, force app standby is enabled - mReceiver.onReceive(mMockContext, new Intent(Intent.ACTION_BATTERY_CHANGED)); - assertTrue(instance.isForceAllAppsStandbyEnabled()); - } - - @Test - public void testNotSmallBatteryAndPluggedIn() throws Exception { - // Not a small battery device, so plugged in status should not affect forced app standby - mIsSmallBatteryDevice = false; - - final ForceAppStandbyTrackerTestable instance = newInstance(); - callStart(instance); - assertFalse(instance.isForceAllAppsStandbyEnabled()); - - mPowerSaveMode = true; - mPowerSaveObserver.accept(getPowerSaveState()); - assertTrue(instance.isForceAllAppsStandbyEnabled()); - - // When battery is plugged in, force app standby is unaffected - Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED); - intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_USB); - mReceiver.onReceive(mMockContext, intent); - assertTrue(instance.isForceAllAppsStandbyEnabled()); - - // When battery stops plugged in, force app standby is unaffected - mReceiver.onReceive(mMockContext, new Intent(Intent.ACTION_BATTERY_CHANGED)); - assertTrue(instance.isForceAllAppsStandbyEnabled()); - } - - static int[] array(int... appIds) { - Arrays.sort(appIds); - return appIds; - } - - private final Random mRandom = new Random(); - - int[] makeRandomArray() { - final ArrayList 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); - } -} -- cgit v1.2.3-59-g8ed1b