diff options
10 files changed, 416 insertions, 20 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 0dab3de599dc..5e9fdfbb6f6f 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1349,12 +1349,26 @@ public class ActivityManager { public static final int RESTRICTION_LEVEL_BACKGROUND_RESTRICTED = 50; /** - * The most restricted level where the apps are considered "in-hibernation", - * its package visibility to the rest of the system is limited. + * The restricted level where the apps are in a force-stopped state. * * @hide */ - public static final int RESTRICTION_LEVEL_HIBERNATION = 60; + public static final int RESTRICTION_LEVEL_FORCE_STOPPED = 60; + + /** + * The heavily background restricted level, where apps cannot start without an explicit + * launch by the user. + * + * @hide + */ + public static final int RESTRICTION_LEVEL_USER_LAUNCH_ONLY = 70; + + /** + * A reserved restriction level that is not well-defined. + * + * @hide + */ + public static final int RESTRICTION_LEVEL_CUSTOM = 90; /** * Not a valid restriction level, it defines the maximum numerical value of restriction level. @@ -1371,12 +1385,116 @@ public class ActivityManager { RESTRICTION_LEVEL_ADAPTIVE_BUCKET, RESTRICTION_LEVEL_RESTRICTED_BUCKET, RESTRICTION_LEVEL_BACKGROUND_RESTRICTED, - RESTRICTION_LEVEL_HIBERNATION, + RESTRICTION_LEVEL_FORCE_STOPPED, + RESTRICTION_LEVEL_USER_LAUNCH_ONLY, + RESTRICTION_LEVEL_CUSTOM, RESTRICTION_LEVEL_MAX, }) @Retention(RetentionPolicy.SOURCE) public @interface RestrictionLevel{} + /** + * Maximum string length for sub reason for restriction. + * + * @hide + */ + public static final int RESTRICTION_SUBREASON_MAX_LENGTH = 16; + + /** + * Restriction reason unknown - do not use directly. + * + * For use with noteAppRestrictionEnabled() + * @hide + */ + public static final int RESTRICTION_REASON_UNKNOWN = 0; + + /** + * Restriction reason to be used when this is normal behavior for the state. + * + * For use with noteAppRestrictionEnabled() + * @hide + */ + public static final int RESTRICTION_REASON_DEFAULT = 1; + + /** + * Restriction reason is some kind of timeout that moves the app to a more restricted state. + * The threshold should specify how long the app was dormant, in milliseconds. + * + * For use with noteAppRestrictionEnabled() + * @hide + */ + public static final int RESTRICTION_REASON_DORMANT = 2; + + /** + * Restriction reason to be used when removing a restriction due to direct or indirect usage + * of the app, especially to undo any automatic restrictions. + * + * For use with noteAppRestrictionEnabled() + * @hide + */ + public static final int RESTRICTION_REASON_USAGE = 3; + + /** + * Restriction reason to be used when the user chooses to manually restrict the app, through + * UI or command line interface. + * + * For use with noteAppRestrictionEnabled() + * @hide + */ + public static final int RESTRICTION_REASON_USER = 4; + + /** + * Restriction reason to be used when the user chooses to manually restrict the app on being + * prompted by the OS or some anomaly detection algorithm. For example, if the app is causing + * high battery drain or affecting system performance and the OS recommends that the user + * restrict the app. + * + * For use with noteAppRestrictionEnabled() + * @hide + */ + public static final int RESTRICTION_REASON_USER_NUDGED = 5; + + /** + * Restriction reason to be used when the OS automatically detects that the app is causing + * system health issues such as performance degradation, battery drain, high memory usage, etc. + * + * For use with noteAppRestrictionEnabled() + * @hide + */ + public static final int RESTRICTION_REASON_SYSTEM_HEALTH = 6; + + /** + * Restriction reason to be used when there is a server-side decision made to restrict an app + * that is showing widespread problems on user devices, or violating policy in some way. + * + * For use with noteAppRestrictionEnabled() + * @hide + */ + public static final int RESTRICTION_REASON_REMOTE_TRIGGER = 7; + + /** + * Restriction reason to be used when some other problem requires restricting the app. + * + * For use with noteAppRestrictionEnabled() + * @hide + */ + public static final int RESTRICTION_REASON_OTHER = 8; + + /** @hide */ + @IntDef(prefix = { "RESTRICTION_REASON_" }, value = { + RESTRICTION_REASON_UNKNOWN, + RESTRICTION_REASON_DEFAULT, + RESTRICTION_REASON_DORMANT, + RESTRICTION_REASON_USAGE, + RESTRICTION_REASON_USER, + RESTRICTION_REASON_USER_NUDGED, + RESTRICTION_REASON_SYSTEM_HEALTH, + RESTRICTION_REASON_REMOTE_TRIGGER, + RESTRICTION_REASON_OTHER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RestrictionReason{} + /** @hide */ @android.ravenwood.annotation.RavenwoodKeep public static String restrictionLevelToName(@RestrictionLevel int level) { @@ -1393,12 +1511,16 @@ public class ActivityManager { return "restricted_bucket"; case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED: return "background_restricted"; - case RESTRICTION_LEVEL_HIBERNATION: - return "hibernation"; + case RESTRICTION_LEVEL_FORCE_STOPPED: + return "stopped"; + case RESTRICTION_LEVEL_USER_LAUNCH_ONLY: + return "user_only"; + case RESTRICTION_LEVEL_CUSTOM: + return "custom"; case RESTRICTION_LEVEL_MAX: return "max"; default: - return ""; + return String.valueOf(level); } } @@ -6127,6 +6249,43 @@ public class ActivityManager { } /** + * Requests the system to log the reason for restricting/unrestricting an app. This API + * should be called before applying any change to the restriction level. + * <p> + * The {@code enabled} value determines whether the state is being applied or removed. + * Not all restrictions are actual restrictions. For example, + * {@link #RESTRICTION_LEVEL_ADAPTIVE} is a normal state, where there is default lifecycle + * management applied to the app. Also, {@link #RESTRICTION_LEVEL_EXEMPTED} is used when the + * app is being put in a power-save allowlist. + * + * @param packageName the package name of the app + * @param uid the uid of the app + * @param restrictionLevel the restriction level specified in {@code RestrictionLevel} + * @param enabled whether the state is being applied or removed + * @param reason the reason for the restriction state change, from {@code RestrictionReason} + * @param subReason a string sub reason limited to 16 characters that specifies additional + * information about the reason for restriction. + * @param threshold for reasons that are due to exceeding some threshold, the threshold value + * must be specified. The unit of the threshold depends on the reason and/or + * subReason. For time, use milliseconds. For memory, use KB. For count, use + * the actual count or normalized as per-hour. For power, use milliwatts. Etc. + * + * @hide + */ + @RequiresPermission(Manifest.permission.DEVICE_POWER) + public void noteAppRestrictionEnabled(@NonNull String packageName, int uid, + @RestrictionLevel int restrictionLevel, boolean enabled, + @RestrictionReason int reason, + @Nullable String subReason, long threshold) { + try { + getService().noteAppRestrictionEnabled(packageName, uid, restrictionLevel, enabled, + reason, subReason, threshold); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Notifies {@link #getRunningAppProcesses app processes} that the system properties * have changed. * diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index dca164d6acc1..3765c817b117 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -1009,4 +1009,12 @@ interface IActivityManager { * @param originatingUid The UID of the instrumented app that initialized the override */ void clearAllOverridePermissionStates(int originatingUid); + + /** + * Request the system to log the reason for restricting / unrestricting an app. + * @see ActivityManager#noteAppRestrictionEnabled + */ + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.DEVICE_POWER)") + void noteAppRestrictionEnabled(in String packageName, int uid, int restrictionType, + boolean enabled, int reason, in String subReason, long threshold); } diff --git a/core/tests/coretests/src/android/app/ActivityManagerTest.java b/core/tests/coretests/src/android/app/ActivityManagerTest.java index d930e4d79a3a..3c042bac895d 100644 --- a/core/tests/coretests/src/android/app/ActivityManagerTest.java +++ b/core/tests/coretests/src/android/app/ActivityManagerTest.java @@ -78,6 +78,6 @@ public class ActivityManagerTest { public void testRestrictionLevel() throws Exception { // For the moment mostly want to confirm we don't crash assertNotNull(ActivityManager.restrictionLevelToName( - ActivityManager.RESTRICTION_LEVEL_HIBERNATION)); + ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED)); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java index 59e9c754a84f..822a60889931 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java @@ -18,12 +18,15 @@ package com.android.settingslib.fuelgauge; import static android.provider.DeviceConfig.NAMESPACE_ACTIVITY_MANAGER; +import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.os.IDeviceIdleController; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -172,27 +175,77 @@ public class PowerAllowlistBackend { /** * Add app into power save allow list. - * @param pkg packageName + * @param pkg packageName of the app */ + // TODO: Fix all callers to pass in UID public void addApp(String pkg) { + addApp(pkg, Process.INVALID_UID); + } + + /** + * Add app into power save allow list. + * @param pkg packageName of the app + * @param uid uid of the app + */ + public void addApp(String pkg, int uid) { try { + if (android.app.Flags.appRestrictionsApi()) { + if (uid == Process.INVALID_UID) { + uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0); + } + final boolean wasInList = isAllowlisted(pkg, uid); + + if (!wasInList) { + mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled( + pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED, + true, ActivityManager.RESTRICTION_REASON_USER, + "settings", 0); + } + } + mDeviceIdleService.addPowerSaveWhitelistApp(pkg); mAllowlistedApps.add(pkg); } catch (RemoteException e) { Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } catch (NameNotFoundException e) { + Log.w(TAG, "Unable to find package", e); } } /** * Remove package from power save allow list. - * @param pkg + * @param pkg packageName of the app */ public void removeApp(String pkg) { + removeApp(pkg, Process.INVALID_UID); + } + + /** + * Remove package from power save allow list. + * @param pkg packageName of the app + * @param uid uid of the app + */ + public void removeApp(String pkg, int uid) { try { + if (android.app.Flags.appRestrictionsApi()) { + if (uid == Process.INVALID_UID) { + uid = mAppContext.getSystemService(PackageManager.class).getPackageUid(pkg, 0); + } + final boolean wasInList = isAllowlisted(pkg, uid); + if (wasInList) { + mAppContext.getSystemService(ActivityManager.class).noteAppRestrictionEnabled( + pkg, uid, ActivityManager.RESTRICTION_LEVEL_EXEMPTED, + false, ActivityManager.RESTRICTION_REASON_USER, + "settings", 0); + } + } + mDeviceIdleService.removePowerSaveWhitelistApp(pkg); mAllowlistedApps.remove(pkg); } catch (RemoteException e) { Log.w(TAG, "Unable to reach IDeviceIdleController", e); + } catch (NameNotFoundException e) { + Log.w(TAG, "Unable to find package", e); } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java index b656253ba147..0d318c346521 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/PowerAllowlistBackendTest.java @@ -96,7 +96,7 @@ public class PowerAllowlistBackendTest { assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue(); assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse(); - mPowerAllowlistBackend.addApp(PACKAGE_TWO); + mPowerAllowlistBackend.addApp(PACKAGE_TWO, UID); verify(mDeviceIdleService, atLeastOnce()).addPowerSaveWhitelistApp(PACKAGE_TWO); assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue(); @@ -104,7 +104,7 @@ public class PowerAllowlistBackendTest { assertThat(mPowerAllowlistBackend.isAllowlisted( new String[] {PACKAGE_ONE, PACKAGE_TWO}, UID)).isTrue(); - mPowerAllowlistBackend.removeApp(PACKAGE_TWO); + mPowerAllowlistBackend.removeApp(PACKAGE_TWO, UID); verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_TWO); assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isTrue(); @@ -112,7 +112,7 @@ public class PowerAllowlistBackendTest { assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_ONE}, UID)).isTrue(); assertThat(mPowerAllowlistBackend.isAllowlisted(new String[] {PACKAGE_TWO}, UID)).isFalse(); - mPowerAllowlistBackend.removeApp(PACKAGE_ONE); + mPowerAllowlistBackend.removeApp(PACKAGE_ONE, UID); verify(mDeviceIdleService, atLeastOnce()).removePowerSaveWhitelistApp(PACKAGE_ONE); assertThat(mPowerAllowlistBackend.isAllowlisted(PACKAGE_ONE, UID)).isFalse(); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9ed6e7e13f28..4568624ec200 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -37,6 +37,9 @@ import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED; +import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT; +import static android.app.ActivityManager.RESTRICTION_REASON_USAGE; import static android.app.ActivityManager.StopUserOnSwitch; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN; @@ -5105,10 +5108,19 @@ public class ActivityManagerService extends IActivityManager.Stub } // else, stopped packages in private space may still hit the logic below } } + + final boolean wasForceStopped = app.wasForceStopped() + || app.getWindowProcessController().wasForceStopped(); + if (android.app.Flags.appRestrictionsApi() && wasForceStopped) { + noteAppRestrictionEnabled(app.info.packageName, app.uid, + RESTRICTION_LEVEL_FORCE_STOPPED, false, + RESTRICTION_REASON_USAGE, "unknown", 0L); + } + if (!sendBroadcast) { if (!android.content.pm.Flags.stayStopped()) return; // Nothing to do if it wasn't previously stopped - if (!app.wasForceStopped() && !app.getWindowProcessController().wasForceStopped()) { + if (!wasForceStopped) { return; } } @@ -14329,6 +14341,20 @@ public class ActivityManagerService extends IActivityManager.Stub int newBackupUid; synchronized(this) { + if (android.app.Flags.appRestrictionsApi()) { + try { + final boolean wasStopped = mPackageManagerInt.isPackageStopped(app.packageName, + UserHandle.getUserId(app.uid)); + if (wasStopped) { + noteAppRestrictionEnabled(app.packageName, app.uid, + RESTRICTION_LEVEL_FORCE_STOPPED, false, + RESTRICTION_REASON_DEFAULT, "restore", 0L); + } + } catch (NameNotFoundException e) { + Slog.w(TAG, "No such package", e); + } + } + // !!! TODO: currently no check here that we're already bound // Backup agent is now in use, its package can't be stopped. try { @@ -20114,6 +20140,34 @@ public class ActivityManagerService extends IActivityManager.Stub } /** + * Log the reason for changing an app restriction. Purely used for logging purposes and does not + * cause any change to app state. + * + * @see ActivityManager#noteAppRestrictionEnabled(String, int, int, boolean, int, String, long) + */ + @Override + public void noteAppRestrictionEnabled(String packageName, int uid, + @RestrictionLevel int restrictionType, boolean enabled, + @ActivityManager.RestrictionReason int reason, String subReason, long threshold) { + if (!android.app.Flags.appRestrictionsApi()) return; + + enforceCallingPermission(android.Manifest.permission.DEVICE_POWER, + "noteAppRestrictionEnabled()"); + + final int userId = UserHandle.getCallingUserId(); + final long callingId = Binder.clearCallingIdentity(); + try { + if (uid == -1) { + uid = mPackageManagerInt.getPackageUid(packageName, 0, userId); + } + mAppRestrictionController.noteAppRestrictionEnabled(packageName, uid, restrictionType, + enabled, reason, subReason, threshold); + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + + /** * Get an app's background restriction level. * This interface is intended for the shell command to use. */ diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 372ec45763bf..5b15c37b2528 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -4006,8 +4006,12 @@ final class ActivityManagerShellCommand extends ShellCommand { return ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; case "background_restricted": return ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; - case "hibernation": - return ActivityManager.RESTRICTION_LEVEL_HIBERNATION; + case "force_stopped": + return ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED; + case "user_launch_only": + return ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY; + case "custom": + return ActivityManager.RESTRICTION_LEVEL_CUSTOM; default: return ActivityManager.RESTRICTION_LEVEL_UNKNOWN; } diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index 117221f6e1f3..c5cad14ee3d7 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -23,11 +23,20 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED; import static android.app.ActivityManager.RESTRICTION_LEVEL_EXEMPTED; -import static android.app.ActivityManager.RESTRICTION_LEVEL_HIBERNATION; +import static android.app.ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED; import static android.app.ActivityManager.RESTRICTION_LEVEL_MAX; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; import static android.app.ActivityManager.RESTRICTION_LEVEL_UNKNOWN; import static android.app.ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED; +import static android.app.ActivityManager.RESTRICTION_LEVEL_USER_LAUNCH_ONLY; +import static android.app.ActivityManager.RESTRICTION_REASON_DEFAULT; +import static android.app.ActivityManager.RESTRICTION_REASON_DORMANT; +import static android.app.ActivityManager.RESTRICTION_REASON_REMOTE_TRIGGER; +import static android.app.ActivityManager.RESTRICTION_REASON_SYSTEM_HEALTH; +import static android.app.ActivityManager.RESTRICTION_REASON_USAGE; +import static android.app.ActivityManager.RESTRICTION_REASON_USER; +import static android.app.ActivityManager.RESTRICTION_REASON_USER_NUDGED; +import static android.app.ActivityManager.RESTRICTION_SUBREASON_MAX_LENGTH; import static android.app.ActivityManager.UID_OBSERVER_ACTIVE; import static android.app.ActivityManager.UID_OBSERVER_GONE; import static android.app.ActivityManager.UID_OBSERVER_IDLE; @@ -93,6 +102,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManager.RestrictionLevel; +import android.app.ActivityManager.RestrictionReason; import android.app.ActivityManagerInternal; import android.app.ActivityManagerInternal.AppBackgroundRestrictionListener; import android.app.AppOpsManager; @@ -344,6 +354,7 @@ public final class AppRestrictionController { static final int TRACKER_TYPE_PERMISSION = 5; static final int TRACKER_TYPE_BROADCAST_EVENTS = 6; static final int TRACKER_TYPE_BIND_SERVICE_EVENTS = 7; + @IntDef(prefix = { "TRACKER_TYPE_" }, value = { TRACKER_TYPE_UNKNOWN, TRACKER_TYPE_BATTERY, @@ -1714,7 +1725,7 @@ public final class AppRestrictionController { String packageName, @UsageStatsManager.StandbyBuckets int standbyBucket, boolean allowRequestBgRestricted, boolean calcTrackers) { if (mInjector.getAppHibernationInternal().isHibernatingForUser(packageName, userId)) { - return new Pair<>(RESTRICTION_LEVEL_HIBERNATION, mEmptyTrackerInfo); + return new Pair<>(RESTRICTION_LEVEL_FORCE_STOPPED, mEmptyTrackerInfo); } @RestrictionLevel int level; TrackerInfo trackerInfo = null; @@ -2034,7 +2045,7 @@ public final class AppRestrictionController { return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET; case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED: return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED; - case RESTRICTION_LEVEL_HIBERNATION: + case RESTRICTION_LEVEL_FORCE_STOPPED: return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION; default: return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN; @@ -2354,6 +2365,85 @@ public final class AppRestrictionController { } } + /** + * Log a change in restriction state with a reason and threshold. + * @param packageName + * @param uid + * @param restrictionType + * @param enabled + * @param reason + * @param subReason Eg: settings, cli, long_wakelock, crash, binder_spam, cpu, threads + * Length should not exceed RESTRICTON_SUBREASON_MAX_LENGTH + * @param threshold + */ + public void noteAppRestrictionEnabled(String packageName, int uid, + @RestrictionLevel int restrictionType, boolean enabled, + @RestrictionReason int reason, String subReason, long threshold) { + if (DEBUG_BG_RESTRICTION_CONTROLLER) { + Slog.i(TAG, (enabled ? "restricted " : "unrestricted ") + packageName + " to " + + restrictionType + " reason=" + reason + ", subReason=" + subReason + + ", threshold=" + threshold); + } + + // Limit the length of the free-form subReason string + if (subReason != null && subReason.length() > RESTRICTION_SUBREASON_MAX_LENGTH) { + subReason = subReason.substring(0, RESTRICTION_SUBREASON_MAX_LENGTH); + Slog.e(TAG, "Subreason is too long, truncating: " + subReason); + } + + // Log the restriction reason + FrameworkStatsLog.write(FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED, uid, + getRestrictionTypeStatsd(restrictionType), + enabled, + getRestrictionChangeReasonStatsd(reason, subReason), + subReason, + threshold); + } + + private int getRestrictionTypeStatsd(@RestrictionLevel int level) { + return switch (level) { + case RESTRICTION_LEVEL_UNKNOWN -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNKNOWN; + case RESTRICTION_LEVEL_UNRESTRICTED -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_UNRESTRICTED; + case RESTRICTION_LEVEL_EXEMPTED -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_EXEMPTED; + case RESTRICTION_LEVEL_ADAPTIVE_BUCKET -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_ADAPTIVE; + case RESTRICTION_LEVEL_RESTRICTED_BUCKET -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_RESTRICTED_BUCKET; + case RESTRICTION_LEVEL_BACKGROUND_RESTRICTED -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_BACKGROUND_RESTRICTED; + case RESTRICTION_LEVEL_FORCE_STOPPED -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_FORCE_STOPPED; + case RESTRICTION_LEVEL_USER_LAUNCH_ONLY -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_USER_LAUNCH_ONLY; + default -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__RESTRICTION_TYPE__TYPE_CUSTOM; + }; + } + + private int getRestrictionChangeReasonStatsd(int reason, String subReason) { + return switch (reason) { + case RESTRICTION_REASON_DEFAULT -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DEFAULT; + case RESTRICTION_REASON_DORMANT -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_DORMANT; + case RESTRICTION_REASON_USAGE -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USAGE; + case RESTRICTION_REASON_USER -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER; + case RESTRICTION_REASON_USER_NUDGED -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_USER_NUDGED; + case RESTRICTION_REASON_SYSTEM_HEALTH -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_SYSTEM_HEALTH; + case RESTRICTION_REASON_REMOTE_TRIGGER -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_REMOTE_TRIGGER; + default -> + FrameworkStatsLog.APP_RESTRICTION_STATE_CHANGED__MAIN_REASON__REASON_OTHER; + }; + } + static class NotificationHelper { static final String PACKAGE_SCHEME = "package"; static final String GROUP_KEY = "com.android.app.abusive_bg_apps"; diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index a17b3d579890..ce410796137b 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -461,6 +461,9 @@ public final class AppHibernationService extends SystemService { packageName, PACKAGE_MATCH_FLAGS, userId); StorageStats stats = mStorageStatsManager.queryStatsForPackage( info.storageUuid, packageName, new UserHandle(userId)); + if (android.app.Flags.appRestrictionsApi()) { + noteHibernationChange(packageName, info.uid, true); + } mIActivityManager.forceStopPackage(packageName, userId); mIPackageManager.deleteApplicationCacheFilesAsUser(packageName, userId, null /* observer */); @@ -490,6 +493,11 @@ public final class AppHibernationService extends SystemService { // Deliver LOCKED_BOOT_COMPLETE AND BOOT_COMPLETE broadcast so app can re-register // their alarms/jobs/etc. try { + if (android.app.Flags.appRestrictionsApi()) { + ApplicationInfo info = mIPackageManager.getApplicationInfo( + packageName, PACKAGE_MATCH_FLAGS, userId); + noteHibernationChange(packageName, info.uid, false); + } Intent lockedBcIntent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED) .setPackage(packageName); final String[] requiredPermissions = {Manifest.permission.RECEIVE_BOOT_COMPLETED}; @@ -555,6 +563,26 @@ public final class AppHibernationService extends SystemService { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } + /** Inform ActivityManager that the app being stopped or unstopped due to hibernation */ + private void noteHibernationChange(String packageName, int uid, boolean hibernated) { + try { + if (hibernated) { + // TODO: Switch to an ActivityManagerInternal API + mIActivityManager.noteAppRestrictionEnabled( + packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, + true, ActivityManager.RESTRICTION_REASON_DORMANT, null, + /* TODO: fetch actual timeout - 90 days */ 90 * 24 * 60 * 60_000L); + } else { + mIActivityManager.noteAppRestrictionEnabled( + packageName, uid, ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED, + false, ActivityManager.RESTRICTION_REASON_USAGE, null, + 0L); + } + } catch (RemoteException e) { + Slog.e(TAG, "Couldn't set restriction state change"); + } + } + /** * Initializes in-memory store of user-level hibernation states for the given user * diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index 9f4b3d256b1e..c393e921d957 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -1651,7 +1651,7 @@ public final class ContentService extends IContentService.Stub { return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET; case ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED: return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED; - case ActivityManager.RESTRICTION_LEVEL_HIBERNATION: + case ActivityManager.RESTRICTION_LEVEL_FORCE_STOPPED: return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION; default: return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN; |