blob: 3a7b5d6835077549427f5c02b1f4ee79b6e3f27d [file] [log] [blame]
/*
* 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.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.StatLogger;
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.
*
* Test:
atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
*/
public class AppStateTracker {
private static final String TAG = "AppStateTracker";
private static final boolean DEBUG = false;
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<Pair<Integer, String>> 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];
/**
* User whitelisted apps in the device idle controller.
*/
@GuardedBy("mLock")
private int[] mPowerWhitelistedUserAppIds = new int[0];
@GuardedBy("mLock")
private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds;
/**
* Per-user packages that are in the EXEMPT bucket.
*/
@GuardedBy("mLock")
private final SparseSetArray<String> mExemptedPackages = 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;
/**
* 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, sender.isUidActive(uid));
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();
}
if (!sender.isRunAnyInBackgroundAppOpsAllowed(uid, packageName)) {
Slog.v(TAG, "Package " + packageName + "/" + uid
+ " toggled into fg service restriction");
stopForegroundServicesForUidPackage(uid, packageName);
}
}
/**
* 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) {
final boolean isActive = sender.isUidActive(uid);
updateJobsForUid(uid, isActive);
if (isActive) {
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, 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 into forced app standby and its foreground
* services need to be removed from that state.
*/
public void stopForegroundServicesForUidPackage(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.
*/
@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()));
}
}
}
}
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);
}
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, int reason) {
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<Integer, String> 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[] powerSaveWhitelistExceptIdleAppIdArray,
int[] powerSaveWhitelistUserAppIdArray,
int[] tempWhitelistAppIdArray) {
synchronized (mLock) {
final int[] previousWhitelist = mPowerWhitelistedAllAppIds;
final int[] previousTempWhitelist = mTempWhitelistedAppIds;
mPowerWhitelistedAllAppIds = powerSaveWhitelistExceptIdleAppIdArray;
mTempWhitelistedAppIds = tempWhitelistAppIdArray;
mPowerWhitelistedUserAppIds = powerSaveWhitelistUserAppIdArray;
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 isExemptOnBatterySaver) {
return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false,
isExemptOnBatterySaver);
}
/**
* @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 foreground services should be suppressed in the background
* due to forced app standby for the given app
*/
public boolean areForegroundServicesRestricted(int uid, @NonNull String packageName) {
synchronized (mLock) {
return isRunAnyRestrictedLocked(uid, packageName);
}
}
/**
* @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));
}
}
/**
* @param uid the uid to check for
* @return whether a UID is in the user defined power-save whitelist or not.
*/
public boolean isUidPowerSaveUserWhitelisted(int uid) {
synchronized (mLock) {
return ArrayUtils.contains(mPowerWhitelistedUserAppIds, 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));
}
}
@Deprecated
public void dump(PrintWriter pw, String prefix) {
dump(new IndentingPrintWriter(pw, " ").setIndent(prefix));
}
public void dump(IndentingPrintWriter pw) {
synchronized (mLock) {
pw.println("Forced App Standby Feature enabled: " + mForcedAppStandbyEnabled);
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("Foreground uids: ");
dumpUids(pw, mForegroundUids);
pw.print("Except-idle + user whitelist appids: ");
pw.println(Arrays.toString(mPowerWhitelistedAllAppIds));
pw.print("User whitelist appids: ");
pw.println(Arrays.toString(mPowerWhitelistedUserAppIds));
pw.print("Temp whitelist appids: ");
pw.println(Arrays.toString(mTempWhitelistedAppIds));
pw.println("Exempted packages:");
pw.increaseIndent();
for (int i = 0; i < mExemptedPackages.size(); i++) {
pw.print("User ");
pw.print(mExemptedPackages.keyAt(i));
pw.println();
pw.increaseIndent();
for (int j = 0; j < mExemptedPackages.sizeAt(i); j++) {
pw.print(mExemptedPackages.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);
}
}
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 : mPowerWhitelistedUserAppIds) {
proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_USER_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<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
final long token2 = proto.start(
ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES);
proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first);
proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME,
uidAndPackage.second);
proto.end(token2);
}
mStatLogger.dumpProto(proto, ForceAppStandbyTrackerProto.STATS);
proto.end(token);
}
}
}