| /* |
| * 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); |
| } |
| } |
| } |