From 34f0857acaf5b10927195c622eb5c1414d226c7b Mon Sep 17 00:00:00 2001 From: Eghosa Ewansiha-Vlachavas Date: Mon, 7 Nov 2022 13:15:41 +0000 Subject: [PATCH] Exempt from app standby logic When AppOpsManager.OP_SYSTEM_EXEMPT_FROM_APP_STANDBY is set to AppOpsManager.MODE_ALLOWED, app should be placed into UsageStatsManager.STANDBY_BUCKET_EXEMPT. Otherwise app standby buckets should behave as normal. Test: atest AppStandbyControllerTests Test: Manual testing: Install test dpc and set as role holder. Navigate to "Set Application Exemptions" in Application Exemptions category. Choose app and select "Exempt from App Standby" and click save. run "adb shell am get-standby-bucket packageName" where the package name is the name the app selected previously (can be viewed in the toast followed after setting the exemptions). Returned value should be 5 (standby bucket exempt). Navigate back to "Set Application Exemptions" in testDpc and save exemptions with "Exempt from App Standby" unchecked. Running "adb shell am get-standby-bucket packageName" should now return a value other then 5. Bug: 246330879 Change-Id: I8f28a15552e524cbb9cbd2c8c23fd6026d9341f9 --- .../server/usage/AppStandbyController.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 67d711c79b15a..b1c8b511ac25b 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -63,6 +63,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.AppOpsManager; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager.ForcedReasons; @@ -108,6 +109,7 @@ import android.util.IndentingPrintWriter; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.util.SparseLongArray; import android.util.TimeUtils; import android.view.Display; @@ -116,6 +118,8 @@ import android.widget.Toast; import com.android.internal.R; 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.app.IBatteryStats; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; @@ -279,6 +283,13 @@ public class AppStandbyController @GuardedBy("mPendingIdleStateChecks") private final SparseLongArray mPendingIdleStateChecks = new SparseLongArray(); + /** + * Map of uids to their current app-op mode for + * {@link AppOpsManager#OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY}. + */ + @GuardedBy("mSystemExemptionAppOpMode") + private final SparseIntArray mSystemExemptionAppOpMode = new SparseIntArray(); + // Cache the active network scorer queried from the network scorer service private volatile String mCachedNetworkScorer = null; // The last time the network scorer service was queried @@ -488,6 +499,7 @@ public class AppStandbyController private AppWidgetManager mAppWidgetManager; private PackageManager mPackageManager; + private AppOpsManager mAppOpsManager; Injector mInjector; private static class Pool { @@ -647,6 +659,28 @@ public class AppStandbyController settingsObserver.start(); mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class); + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); + IAppOpsService iAppOpsService = mInjector.getAppOpsService(); + try { + iAppOpsService.startWatchingMode( + AppOpsManager.OP_SYSTEM_EXEMPT_FROM_APP_STANDBY, + /*packageName=*/ null, + new IAppOpsCallback.Stub() { + @Override + public void opChanged(int op, int uid, String packageName) { + final int userId = UserHandle.getUserId(uid); + synchronized (mSystemExemptionAppOpMode) { + mSystemExemptionAppOpMode.delete(uid); + } + mHandler.obtainMessage( + MSG_CHECK_PACKAGE_IDLE_STATE, userId, uid, packageName) + .sendToTarget(); + } + }); + } catch (RemoteException e) { + // Should not happen. + Slog.wtf(TAG, "Failed start watching for app op", e); + } mInjector.registerDisplayListener(mDisplayListener, mHandler); synchronized (mAppIdleLock) { @@ -1417,6 +1451,23 @@ public class AppStandbyController return STANDBY_BUCKET_EXEMPTED; } + final int uid = UserHandle.getUid(userId, appId); + synchronized (mSystemExemptionAppOpMode) { + if (mSystemExemptionAppOpMode.indexOfKey(uid) >= 0) { + if (mSystemExemptionAppOpMode.get(uid) + == AppOpsManager.MODE_ALLOWED) { + return STANDBY_BUCKET_EXEMPTED; + } + } else { + int mode = mAppOpsManager.checkOpNoThrow( + AppOpsManager.OP_SYSTEM_EXEMPT_FROM_APP_STANDBY, uid, packageName); + mSystemExemptionAppOpMode.put(uid, mode); + if (mode == AppOpsManager.MODE_ALLOWED) { + return STANDBY_BUCKET_EXEMPTED; + } + } + } + if (mAppWidgetManager != null && mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) { return STANDBY_BUCKET_ACTIVE; @@ -2129,6 +2180,12 @@ public class AppStandbyController clearAppIdleForPackage(pkgName, userId); } } + synchronized (mSystemExemptionAppOpMode) { + if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + mSystemExemptionAppOpMode.delete(UserHandle.getUid(userId, getAppId(pkgName))); + } + } + } } @@ -2524,6 +2581,11 @@ public class AppStandbyController } } + IAppOpsService getAppOpsService() { + return IAppOpsService.Stub.asInterface( + ServiceManager.getService(Context.APP_OPS_SERVICE)); + } + /** * Returns {@code true} if the supplied package is the wellbeing app. Otherwise, * returns {@code false}.