/* * 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.ActivityManagerInternal.AppBackgroundRestrictionListener; import android.app.AppOpsManager; import android.app.AppOpsManager.PackageOps; import android.app.IActivityManager; import android.app.usage.UsageStatsManager; 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.IndentingPrintWriter; 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.StatLogger; import com.android.modules.expresslog.Counter; import com.android.server.AppStateTrackerProto.ExemptedPackage; import com.android.server.AppStateTrackerProto.RunAnyInBackgroundRestrictedPackages; import com.android.server.usage.AppStandbyInternal; import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener; import java.io.PrintWriter; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; /** * 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 exemption list * - Temporary power save exemption list * - Global "force all apps standby" mode enforced by battery saver. * * Test: atest com.android.server.AppStateTrackerTest */ public class AppStateTrackerImpl implements AppStateTracker { private static final boolean DEBUG = false; private static final String APP_RESTRICTION_COUNTER_METRIC_ID = "battery.value_app_background_restricted"; 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; AppStandbyInternal mAppStandbyInternal; 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<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>(); /** UIDs that are active. */ @GuardedBy("mLock") final SparseBooleanArray mActiveUids = new SparseBooleanArray(); /** * System except-idle + user exemption list in the device idle controller. */ @GuardedBy("mLock") private int[] mPowerExemptAllAppIds = new int[0]; /** * User exempted apps in the device idle controller. */ @GuardedBy("mLock") private int[] mPowerExemptUserAppIds = new int[0]; @GuardedBy("mLock") private int[] mTempExemptAppIds = mPowerExemptAllAppIds; /** * Per-user packages that are in the EXEMPTED bucket. */ @GuardedBy("mLock") @VisibleForTesting final SparseSetArray<String> mExemptedBucketPackages = new SparseSetArray<>(); @GuardedBy("mLock") final ArraySet<Listener> 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; /** * A lock-free set of (uid, packageName) pairs in background restricted mode. * * <p> * It's basically shadowing the {@link #mRunAnyRestrictedPackages}, any mutations on it would * result in copy-on-write. * </p> */ volatile Set<Pair<Integer, String>> mBackgroundRestrictedUidPackages = Collections.emptySet(); @Override public void addBackgroundRestrictedAppListener( @NonNull BackgroundRestrictedAppListener listener) { addListener(new Listener() { @Override public void updateBackgroundRestrictedForUidPackage(int uid, String packageName, boolean restricted) { listener.updateBackgroundRestrictedForUidPackage(uid, packageName, restricted); } }); } @Override public boolean isAppBackgroundRestricted(int uid, @NonNull String packageName) { final Set<Pair<Integer, String>> bgRestrictedUidPkgs = mBackgroundRestrictedUidPackages; return bgRestrictedUidPkgs.contains(Pair.create(uid, packageName)); } interface Stats { int UID_FG_STATE_CHANGED = 0; int UID_ACTIVE_STATE_CHANGED = 1; int RUN_ANY_CHANGED = 2; int ALL_UNEXEMPTED = 3; int ALL_EXEMPTION_LIST_CHANGED = 4; int TEMP_EXEMPTION_LIST_CHANGED = 5; int EXEMPTED_BUCKET_CHANGED = 6; int FORCE_ALL_CHANGED = 7; int IS_UID_ACTIVE_CACHED = 8; int IS_UID_ACTIVE_RAW = 9; } private final StatLogger mStatLogger = new StatLogger(new String[] { "UID_FG_STATE_CHANGED", "UID_ACTIVE_STATE_CHANGED", "RUN_ANY_CHANGED", "ALL_UNEXEMPTED", "ALL_EXEMPTION_LIST_CHANGED", "TEMP_EXEMPTION_LIST_CHANGED", "EXEMPTED_BUCKET_CHANGED", "FORCE_ALL_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_FOR_SMALL_BATTERY_ENABLED), false, this); } 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_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); } } } private final AppBackgroundRestrictionListener mAppBackgroundRestrictionListener = new AppBackgroundRestrictionListener() { @Override public void onAutoRestrictedBucketFeatureFlagChanged(boolean autoRestrictedBucket) { mHandler.notifyAutoRestrictedBucketFeatureFlagChanged(autoRestrictedBucket); } }; /** * Listener for any state changes that affect any app's eligibility to run. */ public abstract static class Listener { /** * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package. */ private void onRunAnyAppOpsChanged(AppStateTrackerImpl sender, int uid, @NonNull String packageName) { updateJobsForUidPackage(uid, packageName, sender.isUidActive(uid)); if (!sender.areAlarmsRestricted(uid, packageName)) { unblockAlarmsForUidPackage(uid, packageName); } if (!sender.isRunAnyInBackgroundAppOpsAllowed(uid, packageName)) { Slog.v(TAG, "Package " + packageName + "/" + uid + " toggled into fg service restriction"); updateBackgroundRestrictedForUidPackage(uid, packageName, true); } else { Slog.v(TAG, "Package " + packageName + "/" + uid + " toggled out of fg service restriction"); updateBackgroundRestrictedForUidPackage(uid, packageName, false); } } /** * This is called when the active/idle state changed for a UID. */ private void onUidActiveStateChanged(AppStateTrackerImpl sender, int uid) { final boolean isActive = sender.isUidActive(uid); updateJobsForUid(uid, isActive); updateAlarmsForUid(uid); if (isActive) { unblockAlarmsForUid(uid); } } /** * This is called when an app-id(s) is removed from the power save allow-list. */ private void onPowerSaveUnexempted(AppStateTrackerImpl sender) { updateAllJobs(); updateAllAlarms(); } /** * This is called when the power save exemption list changes, excluding the * {@link #onPowerSaveUnexempted} case. */ private void onPowerSaveExemptionListChanged(AppStateTrackerImpl sender) { updateAllJobs(); updateAllAlarms(); unblockAllUnrestrictedAlarms(); } /** * This is called when the temp exemption list changes. */ private void onTempPowerSaveExemptionListChanged(AppStateTrackerImpl 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 exemption list, we do *not* drain pending // alarms. } /** * This is called when the EXEMPTED bucket is updated. */ private void onExemptedBucketChanged(AppStateTrackerImpl sender) { // This doesn't happen very often, so just re-evaluate all jobs / alarms. updateAllJobs(); updateAllAlarms(); } /** * This is called when the global "force all apps standby" flag changes. */ private void onForceAllAppsStandbyChanged(AppStateTrackerImpl sender) { updateAllJobs(); updateAllAlarms(); } /** * Called when toggling the feature flag of moving to restricted standby bucket * automatically on background-restricted. */ private void onAutoRestrictedBucketFeatureFlagChanged(AppStateTrackerImpl sender, boolean autoRestrictedBucket) { updateAllJobs(); if (autoRestrictedBucket) { 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, boolean isNowActive) { } /** * 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, boolean isNowActive) { } /** * Called when an app goes in/out of background restricted mode. */ public void updateBackgroundRestrictedForUidPackage(int uid, String packageName, boolean restricted) { } /** * Called when all alarms need to be re-evaluated for eligibility based on * {@link #areAlarmsRestrictedByBatterySaver}. */ public void updateAllAlarms() { } /** * Called when the given uid state changes to active / idle. */ public void updateAlarmsForUid(int uid) { } /** * 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 an ephemeral uid goes to the background, so its alarms need to be removed. */ public void removeAlarmsForUid(int uid) { } /** * Called when a uid goes into cached, so its alarms using a listener should be removed. */ public void handleUidCachedChanged(int uid, boolean cached) { } } public AppStateTrackerImpl(Context context, Looper looper) { mContext = context; mHandler = new MyHandler(looper); } private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); switch (intent.getAction()) { case Intent.ACTION_USER_REMOVED: if (userId > 0) { mHandler.doUserRemoved(userId); } break; case Intent.ACTION_BATTERY_CHANGED: synchronized (mLock) { mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0); } updateForceAllAppStandbyState(); break; case Intent.ACTION_PACKAGE_REMOVED: if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { final String pkgName = intent.getData().getSchemeSpecificPart(); final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); // No need to notify for state change as all the alarms and jobs should be // removed too. synchronized (mLock) { mExemptedBucketPackages.remove(userId, pkgName); mRunAnyRestrictedPackages.remove(Pair.create(uid, pkgName)); updateBackgroundRestrictedUidPackagesLocked(); mActiveUids.delete(uid); } } break; } } }; /** * Call it when the system is ready. */ public void onSystemServicesReady() { synchronized (mLock) { if (mStarted) { return; } mStarted = true; mIActivityManager = Objects.requireNonNull(injectIActivityManager()); mActivityManagerInternal = Objects.requireNonNull(injectActivityManagerInternal()); mAppOpsManager = Objects.requireNonNull(injectAppOpsManager()); mAppOpsService = Objects.requireNonNull(injectIAppOpsService()); mPowerManagerInternal = Objects.requireNonNull(injectPowerManagerInternal()); mAppStandbyInternal = Objects.requireNonNull(injectAppStandbyInternal()); mFlagsObserver = new FeatureFlagsObserver(); mFlagsObserver.register(); mForceAllAppStandbyForSmallBattery = mFlagsObserver.isForcedAppStandbyForSmallBatteryEnabled(); mStandbyTracker = new StandbyTracker(); mAppStandbyInternal.addListener(mStandbyTracker); mActivityManagerInternal.addAppBackgroundRestrictionListener( mAppBackgroundRestrictionListener); try { mIActivityManager.registerUidObserver(new UidObserver(), ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE | ActivityManager.UID_OBSERVER_CACHED, 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(mReceiver, filter); filter = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme(IntentFilter.SCHEME_PACKAGE); mContext.registerReceiver(mReceiver, 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 AppStandbyInternal injectAppStandbyInternal() { return LocalServices.getService(AppStandbyInternal.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. */ @GuardedBy("mLock") private void refreshForcedAppStandbyUidPackagesLocked() { mRunAnyRestrictedPackages.clear(); final List<PackageOps> 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<AppOpsManager.OpEntry> 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())); } } } updateBackgroundRestrictedUidPackagesLocked(); } /** * Update the {@link #mBackgroundRestrictedUidPackages} upon mutations on * {@link #mRunAnyRestrictedPackages}. */ @GuardedBy("mLock") private void updateBackgroundRestrictedUidPackagesLocked() { Set<Pair<Integer, String>> fasUidPkgs = new ArraySet<>(); for (int i = 0, size = mRunAnyRestrictedPackages.size(); i < size; i++) { fasUidPkgs.add(mRunAnyRestrictedPackages.valueAt(i)); } mBackgroundRestrictedUidPackages = Collections.unmodifiableSet(fasUidPkgs); } private void updateForceAllAppStandbyState() { synchronized (mLock) { if (mForceAllAppStandbyForSmallBattery && isSmallBatteryDevice()) { toggleForceAllAppsStandbyLocked(!mIsPluggedIn); } else { toggleForceAllAppsStandbyLocked(mBatterySaverEnabled); } } } /** * Update {@link #mForceAllAppsStandby} and notifies the listeners. */ @GuardedBy("mLock") private void toggleForceAllAppsStandbyLocked(boolean enable) { if (enable == mForceAllAppsStandby) { return; } mForceAllAppsStandby = enable; mHandler.notifyForceAllAppsStandbyChanged(); } @GuardedBy("mLock") 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<Integer, String> 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. */ @GuardedBy("mLock") boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) { return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0; } /** * Add to / remove from {@link #mRunAnyRestrictedPackages}. */ @GuardedBy("mLock") 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); } updateBackgroundRestrictedUidPackagesLocked(); 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 android.app.UidObserver { @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) { mHandler.onUidCachedChanged(uid, cached); } } private final class AppOpsWatcher extends IAppOpsCallback.Stub { @Override public void opChanged(int op, int uid, String packageName, String persistentDeviceId) throws RemoteException { boolean restricted = false; try { restricted = mAppOpsService.checkOperation(TARGET_OP, uid, packageName) != AppOpsManager.MODE_ALLOWED; } catch (RemoteException e) { // Shouldn't happen } if (restricted) { Counter.logIncrementWithUid(APP_RESTRICTION_COUNTER_METRIC_ID, uid); } synchronized (mLock) { if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) { mHandler.notifyRunAnyAppOpsChanged(uid, packageName); } } } } final class StandbyTracker extends AppIdleStateChangeListener { @Override public void onAppIdleStateChanged(String packageName, int userId, boolean idle, int bucket, int reason) { if (DEBUG) { Slog.d(TAG, "onAppIdleStateChanged: " + packageName + " u" + userId + (idle ? " idle" : " active") + " " + bucket); } synchronized (mLock) { final boolean changed; if (bucket == UsageStatsManager.STANDBY_BUCKET_EXEMPTED) { changed = mExemptedBucketPackages.add(userId, packageName); } else { changed = mExemptedBucketPackages.remove(userId, packageName); } if (changed) { mHandler.notifyExemptedBucketChanged(); } } } } 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; // Unused ids 1, 2. private static final int MSG_RUN_ANY_CHANGED = 3; private static final int MSG_ALL_UNEXEMPTED = 4; private static final int MSG_ALL_EXEMPTION_LIST_CHANGED = 5; private static final int MSG_TEMP_EXEMPTION_LIST_CHANGED = 6; private static final int MSG_FORCE_ALL_CHANGED = 7; private static final int MSG_USER_REMOVED = 8; // Unused id 9. private static final int MSG_EXEMPTED_BUCKET_CHANGED = 10; private static final int MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_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; private static final int MSG_ON_UID_CACHED = 15; MyHandler(Looper looper) { super(looper); } public void notifyUidActiveStateChanged(int uid) { obtainMessage(MSG_UID_ACTIVE_STATE_CHANGED, uid, 0).sendToTarget(); } public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) { obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget(); } public void notifyAllUnexempted() { removeMessages(MSG_ALL_UNEXEMPTED); obtainMessage(MSG_ALL_UNEXEMPTED).sendToTarget(); } public void notifyAllExemptionListChanged() { removeMessages(MSG_ALL_EXEMPTION_LIST_CHANGED); obtainMessage(MSG_ALL_EXEMPTION_LIST_CHANGED).sendToTarget(); } public void notifyTempExemptionListChanged() { removeMessages(MSG_TEMP_EXEMPTION_LIST_CHANGED); obtainMessage(MSG_TEMP_EXEMPTION_LIST_CHANGED).sendToTarget(); } public void notifyForceAllAppsStandbyChanged() { removeMessages(MSG_FORCE_ALL_CHANGED); obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget(); } public void notifyExemptedBucketChanged() { removeMessages(MSG_EXEMPTED_BUCKET_CHANGED); obtainMessage(MSG_EXEMPTED_BUCKET_CHANGED).sendToTarget(); } public void notifyAutoRestrictedBucketFeatureFlagChanged(boolean autoRestrictedBucket) { removeMessages(MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED); obtainMessage(MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED, autoRestrictedBucket ? 1 : 0, 0).sendToTarget(); } public void doUserRemoved(int userId) { obtainMessage(MSG_USER_REMOVED, userId, 0).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(); } public void onUidCachedChanged(int uid, boolean cached) { obtainMessage(MSG_ON_UID_CACHED, uid, cached ? 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 AppStateTrackerImpl sender = AppStateTrackerImpl.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_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_UNEXEMPTED: for (Listener l : cloneListeners()) { l.onPowerSaveUnexempted(sender); } mStatLogger.logDurationStat(Stats.ALL_UNEXEMPTED, start); return; case MSG_ALL_EXEMPTION_LIST_CHANGED: for (Listener l : cloneListeners()) { l.onPowerSaveExemptionListChanged(sender); } mStatLogger.logDurationStat(Stats.ALL_EXEMPTION_LIST_CHANGED, start); return; case MSG_TEMP_EXEMPTION_LIST_CHANGED: for (Listener l : cloneListeners()) { l.onTempPowerSaveExemptionListChanged(sender); } mStatLogger.logDurationStat(Stats.TEMP_EXEMPTION_LIST_CHANGED, start); return; case MSG_EXEMPTED_BUCKET_CHANGED: for (Listener l : cloneListeners()) { l.onExemptedBucketChanged(sender); } mStatLogger.logDurationStat(Stats.EXEMPTED_BUCKET_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_USER_REMOVED: handleUserRemoved(msg.arg1); return; case MSG_AUTO_RESTRICTED_BUCKET_FEATURE_FLAG_CHANGED: final boolean autoRestrictedBucket = msg.arg1 == 1; for (Listener l : cloneListeners()) { l.onAutoRestrictedBucketFeatureFlagChanged(sender, autoRestrictedBucket); } return; case MSG_ON_UID_ACTIVE: handleUidActive(msg.arg1); return; case MSG_ON_UID_GONE: handleUidGone(msg.arg1); if (msg.arg2 != 0) { handleUidDisabled(msg.arg1); } return; case MSG_ON_UID_IDLE: handleUidIdle(msg.arg1); if (msg.arg2 != 0) { handleUidDisabled(msg.arg1); } return; case MSG_ON_UID_CACHED: handleUidCached(msg.arg1, (msg.arg2 != 0)); return; } } private void handleUidCached(int uid, boolean cached) { for (Listener l : cloneListeners()) { l.handleUidCachedChanged(uid, cached); } } private void handleUidDisabled(int uid) { for (Listener l : cloneListeners()) { l.removeAlarmsForUid(uid); } } public void handleUidActive(int uid) { synchronized (mLock) { if (addUidToArray(mActiveUids, uid)) { mHandler.notifyUidActiveStateChanged(uid); } } } public void handleUidGone(int uid) { removeUid(uid, true); } public void handleUidIdle(int uid) { // 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); } } } } void handleUserRemoved(int removedUserId) { synchronized (mLock) { for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) { final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i); final int uid = pair.first; final int userId = UserHandle.getUserId(uid); if (userId == removedUserId) { mRunAnyRestrictedPackages.removeAt(i); } } updateBackgroundRestrictedUidPackagesLocked(); cleanUpArrayForUser(mActiveUids, removedUserId); mExemptedBucketPackages.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 exemption lists. */ public void setPowerSaveExemptionListAppIds( int[] powerSaveExemptionListExceptIdleAppIdArray, int[] powerSaveExemptionListUserAppIdArray, int[] tempExemptionListAppIdArray) { synchronized (mLock) { final int[] previousExemptionList = mPowerExemptAllAppIds; final int[] previousTempExemptionList = mTempExemptAppIds; mPowerExemptAllAppIds = powerSaveExemptionListExceptIdleAppIdArray; mTempExemptAppIds = tempExemptionListAppIdArray; mPowerExemptUserAppIds = powerSaveExemptionListUserAppIdArray; if (isAnyAppIdUnexempt(previousExemptionList, mPowerExemptAllAppIds)) { mHandler.notifyAllUnexempted(); } else if (!Arrays.equals(previousExemptionList, mPowerExemptAllAppIds)) { mHandler.notifyAllExemptionListChanged(); } if (!Arrays.equals(previousTempExemptionList, mTempExemptAppIds)) { mHandler.notifyTempExemptionListChanged(); } } } /** * @return 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 isAnyAppIdUnexempt(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 listener to get callbacks when any state changes. */ public void addListener(@NonNull Listener listener) { synchronized (mLock) { mListeners.add(listener); } } /** * @return whether alarms should be restricted for a UID package-name, due to explicit * user-forced app standby. Use {{@link #areAlarmsRestrictedByBatterySaver} to check for * restrictions induced by battery saver. */ public boolean areAlarmsRestricted(int uid, @NonNull String packageName) { if (isUidActive(uid)) { return false; } synchronized (mLock) { final int appId = UserHandle.getAppId(uid); if (ArrayUtils.contains(mPowerExemptAllAppIds, appId)) { return false; } // If apps will be put into restricted standby bucket automatically on user-forced // app standby, instead of blocking alarms completely, let the restricted standby bucket // policy take care of it. return (!mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled() && isRunAnyRestrictedLocked(uid, packageName)); } } /** * @return whether alarms should be restricted when due to battery saver. */ public boolean areAlarmsRestrictedByBatterySaver(int uid, @NonNull String packageName) { if (isUidActive(uid)) { return false; } synchronized (mLock) { final int appId = UserHandle.getAppId(uid); if (ArrayUtils.contains(mPowerExemptAllAppIds, appId)) { return false; } final int userId = UserHandle.getUserId(uid); if (mAppStandbyInternal.isAppIdleEnabled() && !mAppStandbyInternal.isInParole() && mExemptedBucketPackages.contains(userId, packageName)) { return false; } return mForceAllAppsStandby; } } /** * @return whether jobs should be restricted for a UID package-name. This could be due to * battery saver or user-forced app standby */ public boolean areJobsRestricted(int uid, @NonNull String packageName, boolean hasForegroundExemption) { if (isUidActive(uid)) { return false; } synchronized (mLock) { final int appId = UserHandle.getAppId(uid); if (ArrayUtils.contains(mPowerExemptAllAppIds, appId) || ArrayUtils.contains(mTempExemptAppIds, appId)) { return false; } // If apps will be put into restricted standby bucket automatically on user-forced // app standby, instead of blocking jobs completely, let the restricted standby bucket // policy take care of it. if (!mActivityManagerInternal.isBgAutoRestrictedBucketFeatureFlagEnabled() && isRunAnyRestrictedLocked(uid, packageName)) { return true; } if (hasForegroundExemption) { return false; } final int userId = UserHandle.getUserId(uid); if (mAppStandbyInternal.isAppIdleEnabled() && !mAppStandbyInternal.isInParole() && mExemptedBucketPackages.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 force all apps standby is enabled or not. */ public 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 exemption list or not. * * Note clients normally shouldn't need to access it. It's only for dumpsys. */ public boolean isUidPowerSaveExempt(int uid) { synchronized (mLock) { return ArrayUtils.contains(mPowerExemptAllAppIds, UserHandle.getAppId(uid)); } } /** * @param uid the uid to check for * @return whether a UID is in the user defined power-save exemption list or not. */ public boolean isUidPowerSaveUserExempt(int uid) { synchronized (mLock) { return ArrayUtils.contains(mPowerExemptUserAppIds, UserHandle.getAppId(uid)); } } /** * @return whether a UID is in the temp power-save exemption list or not. * * Note clients normally shouldn't need to access it. It's only for dumpsys. */ public boolean isUidTempPowerSaveExempt(int uid) { synchronized (mLock) { return ArrayUtils.contains(mTempExemptAppIds, UserHandle.getAppId(uid)); } } /** * Dump the internal state to the given PrintWriter. Can be included in the dump * of a binder service to be output on the shell command "dumpsys". */ public void dump(IndentingPrintWriter pw) { synchronized (mLock) { pw.println("Current AppStateTracker State:"); pw.increaseIndent(); pw.print("Force all apps standby: "); pw.println(isForceAllAppsStandbyEnabled()); pw.print("Small Battery Device: "); pw.println(isSmallBatteryDevice()); pw.print("Force all apps standby for small battery device: "); pw.println(mForceAllAppStandbyForSmallBattery); pw.print("Plugged In: "); pw.println(mIsPluggedIn); pw.print("Active uids: "); dumpUids(pw, mActiveUids); pw.print("Except-idle + user exemption list appids: "); pw.println(Arrays.toString(mPowerExemptAllAppIds)); pw.print("User exemption list appids: "); pw.println(Arrays.toString(mPowerExemptUserAppIds)); pw.print("Temp exemption list appids: "); pw.println(Arrays.toString(mTempExemptAppIds)); pw.println("Exempted bucket packages:"); pw.increaseIndent(); for (int i = 0; i < mExemptedBucketPackages.size(); i++) { pw.print("User "); pw.print(mExemptedBucketPackages.keyAt(i)); pw.println(); pw.increaseIndent(); for (int j = 0; j < mExemptedBucketPackages.sizeAt(i); j++) { pw.print(mExemptedBucketPackages.valueAt(i, j)); pw.println(); } pw.decreaseIndent(); } pw.decreaseIndent(); pw.println(); pw.println("Restricted packages:"); pw.increaseIndent(); for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) { pw.print(UserHandle.formatUid(uidAndPackage.first)); pw.print(" "); pw.print(uidAndPackage.second); pw.println(); } pw.decreaseIndent(); mStatLogger.dump(pw); pw.decreaseIndent(); } } 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("]"); } /** * Proto version of {@link #dump(IndentingPrintWriter)} */ public void dumpProto(ProtoOutputStream proto, long fieldId) { synchronized (mLock) { final long token = proto.start(fieldId); proto.write(AppStateTrackerProto.FORCE_ALL_APPS_STANDBY, isForceAllAppsStandbyEnabled()); proto.write(AppStateTrackerProto.IS_SMALL_BATTERY_DEVICE, isSmallBatteryDevice()); proto.write(AppStateTrackerProto.FORCE_ALL_APPS_STANDBY_FOR_SMALL_BATTERY, mForceAllAppStandbyForSmallBattery); proto.write(AppStateTrackerProto.IS_PLUGGED_IN, mIsPluggedIn); for (int i = 0; i < mActiveUids.size(); i++) { if (mActiveUids.valueAt(i)) { proto.write(AppStateTrackerProto.ACTIVE_UIDS, mActiveUids.keyAt(i)); } } for (int appId : mPowerExemptAllAppIds) { proto.write(AppStateTrackerProto.POWER_SAVE_EXEMPT_APP_IDS, appId); } for (int appId : mPowerExemptUserAppIds) { proto.write(AppStateTrackerProto.POWER_SAVE_USER_EXEMPT_APP_IDS, appId); } for (int appId : mTempExemptAppIds) { proto.write(AppStateTrackerProto.TEMP_POWER_SAVE_EXEMPT_APP_IDS, appId); } for (int i = 0; i < mExemptedBucketPackages.size(); i++) { for (int j = 0; j < mExemptedBucketPackages.sizeAt(i); j++) { final long token2 = proto.start(AppStateTrackerProto.EXEMPTED_BUCKET_PACKAGES); proto.write(ExemptedPackage.USER_ID, mExemptedBucketPackages.keyAt(i)); proto.write(ExemptedPackage.PACKAGE_NAME, mExemptedBucketPackages.valueAt(i, j)); proto.end(token2); } } for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) { final long token2 = proto.start( AppStateTrackerProto.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, AppStateTrackerProto.STATS); proto.end(token); } } }