Loading apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +13 −0 Original line number Diff line number Diff line Loading @@ -103,6 +103,8 @@ public interface AppStandbyInternal { /** * Changes an app's standby bucket to the provided value. The caller can only set the standby * bucket for a different app than itself. * If attempting to automatically place an app in the RESTRICTED bucket, use * {@link #restrictApp(String, int, int)} instead. */ void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, int callingPid); Loading @@ -113,6 +115,17 @@ public interface AppStandbyInternal { void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid, int callingPid); /** * Put the specified app in the * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} * bucket. If it has been used by the user recently, the restriction will delayed until an * appropriate time. * * @param restrictReason The restrictReason for restricting the app. Should be one of the * UsageStatsManager.REASON_SUB_RESTRICT_* reasons. */ void restrictApp(@NonNull String packageName, int userId, int restrictReason); void addActiveDeviceAdmin(String adminPkg, int userId); void setActiveAdminApps(Set<String> adminPkgs, int userId); Loading apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +5 −3 Original line number Diff line number Diff line Loading @@ -277,6 +277,7 @@ public class JobSchedulerService extends com.android.server.SystemService DeviceIdleInternal mLocalDeviceIdleController; AppStateTracker mAppStateTracker; final UsageStatsManagerInternal mUsageStats; private final AppStandbyInternal mAppStandbyInternal; /** * Set to true once we are allowed to run third party apps. Loading Loading @@ -1062,7 +1063,8 @@ public class JobSchedulerService extends com.android.server.SystemService packageName == null ? job.getService().getPackageName() : packageName; if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) { Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times"); // TODO(b/145551233): attempt to restrict app mAppStandbyInternal.restrictApp( pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY); if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION && mPlatformCompat.isChangeEnabledByPackageName( CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) { Loading Loading @@ -1430,8 +1432,8 @@ public class JobSchedulerService extends com.android.server.SystemService mConstants.API_QUOTA_SCHEDULE_COUNT, mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); appStandby.addListener(mStandbyTracker); mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class); mAppStandbyInternal.addListener(mStandbyTracker); // The job store needs to call back publishLocalService(JobSchedulerInternal.class, new LocalService()); Loading apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +96 −15 Original line number Diff line number Diff line Loading @@ -25,8 +25,11 @@ import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACT import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.usage.AppStandbyController.isUserUsage; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageStatsManager; import android.os.SystemClock; Loading Loading @@ -81,6 +84,8 @@ public class AppIdleHistory { private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; // Elapsed timebase time when app was last used private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime"; // Elapsed timebase time when app was last used by the user private static final String ATTR_LAST_USED_BY_USER_ELAPSED = "lastUsedByUserElapsedTime"; // Elapsed timebase time when the app bucket was last predicted externally private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime"; // The standby bucket for the app Loading @@ -93,6 +98,12 @@ public class AppIdleHistory { private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime"; // The time when the forced working_set state can be overridden. private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime"; // Elapsed timebase time when the app was last marked for restriction. private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED = "lastRestrictionAttemptElapsedTime"; // Reason why the app was last marked for restriction. private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON = "lastRestrictionAttemptReason"; // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration Loading @@ -107,8 +118,10 @@ public class AppIdleHistory { private boolean mScreenOn; static class AppUsageHistory { // Last used time using elapsed timebase // Last used time (including system usage), using elapsed timebase long lastUsedElapsedTime; // Last time the user used the app, using elapsed timebase long lastUsedByUserElapsedTime; // Last used time using screen_on timebase long lastUsedScreenTime; // Last predicted time using elapsed timebase Loading Loading @@ -136,6 +149,10 @@ public class AppIdleHistory { // under any active state timeout, so that it becomes applicable after the active state // timeout expires. long bucketWorkingSetTimeoutTime; // The last time an agent attempted to put the app into the RESTRICTED bucket. long lastRestrictAttemptElapsedTime; // The last reason the app was marked to be put into the RESTRICTED bucket. int lastRestrictReason; } AppIdleHistory(File storageDir, long elapsedRealtime) { Loading Loading @@ -229,6 +246,14 @@ public class AppIdleHistory { */ public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int newBucket, int usageReason, long elapsedRealtime, long timeout) { int bucketingReason = REASON_MAIN_USAGE | usageReason; final boolean isUserUsage = isUserUsage(bucketingReason); if (appUsageHistory.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage) { // Only user usage should bring an app out of the RESTRICTED bucket. newBucket = STANDBY_BUCKET_RESTRICTED; bucketingReason = appUsageHistory.bucketingReason; } else { // Set the timeout if applicable if (timeout > elapsedRealtime) { // Convert to elapsed timebase Loading @@ -240,14 +265,18 @@ public class AppIdleHistory { appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime, appUsageHistory.bucketWorkingSetTimeoutTime); } else { throw new IllegalArgumentException("Cannot set a timeout on bucket=" + newBucket); throw new IllegalArgumentException("Cannot set a timeout on bucket=" + newBucket); } } } if (elapsedRealtime != 0) { appUsageHistory.lastUsedElapsedTime = mElapsedDuration + (elapsedRealtime - mElapsedSnapshot); if (isUserUsage) { appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime; } appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); } Loading @@ -259,7 +288,7 @@ public class AppIdleHistory { + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason)); } } appUsageHistory.bucketingReason = REASON_MAIN_USAGE | usageReason; appUsageHistory.bucketingReason = bucketingReason; return appUsageHistory; } Loading Loading @@ -385,6 +414,24 @@ public class AppIdleHistory { appUsageHistory.lastJobRunTime = getElapsedTime(elapsedRealtime); } /** * Notes an attempt to put the app in the {@link UsageStatsManager#STANDBY_BUCKET_RESTRICTED} * bucket. * * @param packageName The package name of the app that is being restricted * @param userId The ID of the user in which the app is being restricted * @param elapsedRealtime The time the attempt was made, in the (unadjusted) elapsed realtime * timebase * @param reason The reason for the restriction attempt */ void noteRestrictionAttempt(String packageName, int userId, long elapsedRealtime, int reason) { ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, elapsedRealtime, true); appUsageHistory.lastRestrictAttemptElapsedTime = getElapsedTime(elapsedRealtime); appUsageHistory.lastRestrictReason = reason; } /** * Returns the time since the last job was run for this app. This can be larger than the * current elapsedRealtime, in case it happened before boot or a really large value if no jobs Loading Loading @@ -547,6 +594,9 @@ public class AppIdleHistory { AppUsageHistory appUsageHistory = new AppUsageHistory(); appUsageHistory.lastUsedElapsedTime = Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE)); appUsageHistory.lastUsedByUserElapsedTime = getLongValue(parser, ATTR_LAST_USED_BY_USER_ELAPSED, appUsageHistory.lastUsedElapsedTime); appUsageHistory.lastUsedScreenTime = Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE)); appUsageHistory.lastPredictedTime = getLongValue(parser, Loading @@ -570,6 +620,19 @@ public class AppIdleHistory { appUsageHistory.bucketingReason = Integer.parseInt(bucketingReason, 16); } catch (NumberFormatException nfe) { Slog.wtf(TAG, "Unable to read bucketing reason", nfe); } } appUsageHistory.lastRestrictAttemptElapsedTime = getLongValue(parser, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, 0); String lastRestrictReason = parser.getAttributeValue( null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON); if (lastRestrictReason != null) { try { appUsageHistory.lastRestrictReason = Integer.parseInt(lastRestrictReason, 16); } catch (NumberFormatException nfe) { Slog.wtf(TAG, "Unable to read last restrict reason", nfe); } } appUsageHistory.lastInformedBucket = -1; Loading Loading @@ -618,6 +681,8 @@ public class AppIdleHistory { xml.attribute(null, ATTR_NAME, packageName); xml.attribute(null, ATTR_ELAPSED_IDLE, Long.toString(history.lastUsedElapsedTime)); xml.attribute(null, ATTR_LAST_USED_BY_USER_ELAPSED, Long.toString(history.lastUsedByUserElapsedTime)); xml.attribute(null, ATTR_SCREEN_IDLE, Long.toString(history.lastUsedScreenTime)); xml.attribute(null, ATTR_LAST_PREDICTED_TIME, Loading @@ -638,6 +703,12 @@ public class AppIdleHistory { xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history .lastJobRunTime)); } if (history.lastRestrictAttemptElapsedTime > 0) { xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, Long.toString(history.lastRestrictAttemptElapsedTime)); } xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON, Integer.toHexString(history.lastRestrictReason)); xml.endTag(null, TAG_PACKAGE); } Loading Loading @@ -672,6 +743,9 @@ public class AppIdleHistory { + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason)); idpw.print(" used="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw); idpw.print(" usedByUser="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedByUserElapsedTime, idpw); idpw.print(" usedScr="); TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw); idpw.print(" lastPred="); Loading @@ -684,6 +758,13 @@ public class AppIdleHistory { idpw); idpw.print(" lastJob="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) { idpw.print(" lastRestrictAttempt="); TimeUtils.formatDuration( totalElapsedTime - appUsageHistory.lastRestrictAttemptElapsedTime, idpw); idpw.print(" lastRestrictReason=" + UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason)); } idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); idpw.println(); } Loading apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +112 −11 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; import static android.app.usage.UsageStatsManager.REASON_SUB_MASK; import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_DOZE; Loading @@ -44,6 +45,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; Loading Loading @@ -73,6 +75,7 @@ import android.net.Network; import android.net.NetworkRequest; import android.net.NetworkScoreManager; import android.os.BatteryStats; import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.IDeviceIdleController; Loading @@ -93,7 +96,9 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Display; 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.IBatteryStats; Loading Loading @@ -124,7 +129,7 @@ import java.util.concurrent.CountDownLatch; public class AppStandbyController implements AppStandbyInternal { private static final String TAG = "AppStandbyController"; static final boolean DEBUG = false; static final boolean DEBUG = true; static final boolean COMPRESS_TIME = false; private static final long ONE_MINUTE = 60 * 1000; Loading Loading @@ -615,6 +620,16 @@ public class AppStandbyController implements AppStandbyInternal { Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); } } if (app.lastRestrictAttemptElapsedTime > app.lastUsedByUserElapsedTime && elapsedTimeAdjusted - app.lastUsedByUserElapsedTime >= mInjector.getRestrictedBucketDelayMs()) { newBucket = STANDBY_BUCKET_RESTRICTED; reason = app.lastRestrictReason; if (DEBUG) { Slog.d(TAG, "Bringing down to RESTRICTED due to timeout"); } } if (DEBUG) { Slog.d(TAG, " Old bucket=" + oldBucket + ", newBucket=" + newBucket); Loading Loading @@ -733,15 +748,16 @@ public class AppStandbyController implements AppStandbyInternal { elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis); nextCheckTime = mStrongUsageTimeoutMillis; } mHandler.sendMessageDelayed(mHandler.obtainMessage (MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkg), if (appHistory.currentBucket != prevBucket) { mHandler.sendMessageDelayed( mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkg), nextCheckTime); final boolean userStartedInteracting = appHistory.currentBucket == STANDBY_BUCKET_ACTIVE && prevBucket != appHistory.currentBucket && (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE; appHistory.currentBucket == STANDBY_BUCKET_ACTIVE && (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE; maybeInformListeners(pkg, userId, elapsedRealtime, appHistory.currentBucket, reason, userStartedInteracting); } if (previouslyIdle) { notifyBatteryStats(pkg, userId, false); Loading Loading @@ -923,6 +939,15 @@ public class AppStandbyController implements AppStandbyInternal { } } static boolean isUserUsage(int reason) { if ((reason & REASON_MAIN_MASK) == REASON_MAIN_USAGE) { final int subReason = reason & REASON_SUB_MASK; return subReason == REASON_SUB_USAGE_USER_INTERACTION || subReason == REASON_SUB_USAGE_MOVE_TO_FOREGROUND; } return false; } @Override public int[] getIdleUidsForUser(int userId) { if (!mAppIdleEnabled) { Loading Loading @@ -1016,6 +1041,20 @@ public class AppStandbyController implements AppStandbyInternal { } } @Override public void restrictApp(@NonNull String packageName, int userId, int restrictReason) { // If the package is not installed, don't allow the bucket to be set. if (!mInjector.isPackageInstalled(packageName, 0, userId)) { Slog.e(TAG, "Tried to restrict uninstalled app: " + packageName); return; } final int reason = REASON_MAIN_FORCED_BY_SYSTEM | (REASON_SUB_MASK & restrictReason); final long nowElapsed = mInjector.elapsedRealtime(); setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RESTRICTED, reason, nowElapsed, false); } @Override public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, int callingPid) { Loading Loading @@ -1080,6 +1119,7 @@ public class AppStandbyController implements AppStandbyInternal { synchronized (mAppIdleLock) { // If the package is not installed, don't allow the bucket to be set. if (!mInjector.isPackageInstalled(packageName, 0, userId)) { Slog.e(TAG, "Tried to set bucket of uninstalled app: " + packageName); return; } AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName, Loading @@ -1089,8 +1129,9 @@ public class AppStandbyController implements AppStandbyInternal { // Don't allow changing bucket if higher than ACTIVE if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return; // Don't allow prediction to change from/to NEVER // Don't allow prediction to change from/to NEVER or from RESTRICTED. if ((app.currentBucket == STANDBY_BUCKET_NEVER || app.currentBucket == STANDBY_BUCKET_RESTRICTED || newBucket == STANDBY_BUCKET_NEVER) && predicted) { return; Loading @@ -1103,6 +1144,50 @@ public class AppStandbyController implements AppStandbyInternal { return; } final boolean isForcedByUser = (reason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER; // If the current bucket is RESTRICTED, only user force or usage should bring it out. if (app.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage(reason) && !isForcedByUser) { return; } if (newBucket == STANDBY_BUCKET_RESTRICTED) { mAppIdleHistory .noteRestrictionAttempt(packageName, userId, elapsedRealtime, reason); if (isForcedByUser) { // Only user force can bypass the delay restriction. If the user forced the // app into the RESTRICTED bucket, then a toast confirming the action // shouldn't be surprising. if (Build.IS_DEBUGGABLE) { Toast.makeText(mContext, // Since AppStandbyController sits low in the lock hierarchy, // make sure not to call out with the lock held. mHandler.getLooper(), mContext.getResources().getString( R.string.as_app_forced_to_restricted_bucket, packageName), Toast.LENGTH_SHORT) .show(); } else { Slog.i(TAG, packageName + " restricted by user"); } } else { final long timeUntilRestrictPossibleMs = app.lastUsedByUserElapsedTime + mInjector.getRestrictedBucketDelayMs() - elapsedRealtime; if (timeUntilRestrictPossibleMs > 0) { Slog.w(TAG, "Tried to restrict recently used app: " + packageName + " due to " + reason); mHandler.sendMessageDelayed( mHandler.obtainMessage( MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, packageName), timeUntilRestrictPossibleMs); return; } } } // If the bucket is required to stay in a higher state for a specified duration, don't // override unless the duration has passed if (predicted) { Loading Loading @@ -1435,6 +1520,12 @@ public class AppStandbyController implements AppStandbyInternal { private DisplayManager mDisplayManager; private PowerManager mPowerManager; int mBootPhase; /** * The minimum amount of time required since the last user interaction before an app can be * placed in the RESTRICTED bucket. */ // TODO: make configurable via DeviceConfig private long mRestrictedBucketDelayMs = ONE_DAY; Injector(Context context, Looper looper) { mContext = context; Loading @@ -1459,6 +1550,12 @@ public class AppStandbyController implements AppStandbyInternal { mDisplayManager = (DisplayManager) mContext.getSystemService( Context.DISPLAY_SERVICE); mPowerManager = mContext.getSystemService(PowerManager.class); final ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); if (activityManager.isLowRamDevice() || ActivityManager.isSmallBatteryDevice()) { mRestrictedBucketDelayMs = 12 * ONE_HOUR; } } mBootPhase = phase; } Loading Loading @@ -1498,6 +1595,10 @@ public class AppStandbyController implements AppStandbyInternal { return Environment.getDataSystemDirectory(); } long getRestrictedBucketDelayMs() { return mRestrictedBucketDelayMs; } void noteEvent(int event, String packageName, int uid) throws RemoteException { mBatteryStats.noteEvent(event, packageName, uid); } Loading api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -8030,6 +8030,7 @@ package android.app.usage { field public static final int STANDBY_BUCKET_ACTIVE = 10; // 0xa field public static final int STANDBY_BUCKET_FREQUENT = 30; // 0x1e field public static final int STANDBY_BUCKET_RARE = 40; // 0x28 field public static final int STANDBY_BUCKET_RESTRICTED = 45; // 0x2d field public static final int STANDBY_BUCKET_WORKING_SET = 20; // 0x14 } Loading
apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +13 −0 Original line number Diff line number Diff line Loading @@ -103,6 +103,8 @@ public interface AppStandbyInternal { /** * Changes an app's standby bucket to the provided value. The caller can only set the standby * bucket for a different app than itself. * If attempting to automatically place an app in the RESTRICTED bucket, use * {@link #restrictApp(String, int, int)} instead. */ void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, int callingPid); Loading @@ -113,6 +115,17 @@ public interface AppStandbyInternal { void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid, int callingPid); /** * Put the specified app in the * {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED} * bucket. If it has been used by the user recently, the restriction will delayed until an * appropriate time. * * @param restrictReason The restrictReason for restricting the app. Should be one of the * UsageStatsManager.REASON_SUB_RESTRICT_* reasons. */ void restrictApp(@NonNull String packageName, int userId, int restrictReason); void addActiveDeviceAdmin(String adminPkg, int userId); void setActiveAdminApps(Set<String> adminPkgs, int userId); Loading
apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +5 −3 Original line number Diff line number Diff line Loading @@ -277,6 +277,7 @@ public class JobSchedulerService extends com.android.server.SystemService DeviceIdleInternal mLocalDeviceIdleController; AppStateTracker mAppStateTracker; final UsageStatsManagerInternal mUsageStats; private final AppStandbyInternal mAppStandbyInternal; /** * Set to true once we are allowed to run third party apps. Loading Loading @@ -1062,7 +1063,8 @@ public class JobSchedulerService extends com.android.server.SystemService packageName == null ? job.getService().getPackageName() : packageName; if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) { Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times"); // TODO(b/145551233): attempt to restrict app mAppStandbyInternal.restrictApp( pkg, userId, UsageStatsManager.REASON_SUB_RESTRICT_BUGGY); if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION && mPlatformCompat.isChangeEnabledByPackageName( CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) { Loading Loading @@ -1430,8 +1432,8 @@ public class JobSchedulerService extends com.android.server.SystemService mConstants.API_QUOTA_SCHEDULE_COUNT, mConstants.API_QUOTA_SCHEDULE_WINDOW_MS); AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class); appStandby.addListener(mStandbyTracker); mAppStandbyInternal = LocalServices.getService(AppStandbyInternal.class); mAppStandbyInternal.addListener(mStandbyTracker); // The job store needs to call back publishLocalService(JobSchedulerInternal.class, new LocalService()); Loading
apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +96 −15 Original line number Diff line number Diff line Loading @@ -25,8 +25,11 @@ import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACT import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.usage.AppStandbyController.isUserUsage; import android.app.usage.AppStandbyInfo; import android.app.usage.UsageStatsManager; import android.os.SystemClock; Loading Loading @@ -81,6 +84,8 @@ public class AppIdleHistory { private static final String ATTR_SCREEN_IDLE = "screenIdleTime"; // Elapsed timebase time when app was last used private static final String ATTR_ELAPSED_IDLE = "elapsedIdleTime"; // Elapsed timebase time when app was last used by the user private static final String ATTR_LAST_USED_BY_USER_ELAPSED = "lastUsedByUserElapsedTime"; // Elapsed timebase time when the app bucket was last predicted externally private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime"; // The standby bucket for the app Loading @@ -93,6 +98,12 @@ public class AppIdleHistory { private static final String ATTR_BUCKET_ACTIVE_TIMEOUT_TIME = "activeTimeoutTime"; // The time when the forced working_set state can be overridden. private static final String ATTR_BUCKET_WORKING_SET_TIMEOUT_TIME = "workingSetTimeoutTime"; // Elapsed timebase time when the app was last marked for restriction. private static final String ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED = "lastRestrictionAttemptElapsedTime"; // Reason why the app was last marked for restriction. private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON = "lastRestrictionAttemptReason"; // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot) private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration Loading @@ -107,8 +118,10 @@ public class AppIdleHistory { private boolean mScreenOn; static class AppUsageHistory { // Last used time using elapsed timebase // Last used time (including system usage), using elapsed timebase long lastUsedElapsedTime; // Last time the user used the app, using elapsed timebase long lastUsedByUserElapsedTime; // Last used time using screen_on timebase long lastUsedScreenTime; // Last predicted time using elapsed timebase Loading Loading @@ -136,6 +149,10 @@ public class AppIdleHistory { // under any active state timeout, so that it becomes applicable after the active state // timeout expires. long bucketWorkingSetTimeoutTime; // The last time an agent attempted to put the app into the RESTRICTED bucket. long lastRestrictAttemptElapsedTime; // The last reason the app was marked to be put into the RESTRICTED bucket. int lastRestrictReason; } AppIdleHistory(File storageDir, long elapsedRealtime) { Loading Loading @@ -229,6 +246,14 @@ public class AppIdleHistory { */ public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName, int newBucket, int usageReason, long elapsedRealtime, long timeout) { int bucketingReason = REASON_MAIN_USAGE | usageReason; final boolean isUserUsage = isUserUsage(bucketingReason); if (appUsageHistory.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage) { // Only user usage should bring an app out of the RESTRICTED bucket. newBucket = STANDBY_BUCKET_RESTRICTED; bucketingReason = appUsageHistory.bucketingReason; } else { // Set the timeout if applicable if (timeout > elapsedRealtime) { // Convert to elapsed timebase Loading @@ -240,14 +265,18 @@ public class AppIdleHistory { appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime, appUsageHistory.bucketWorkingSetTimeoutTime); } else { throw new IllegalArgumentException("Cannot set a timeout on bucket=" + newBucket); throw new IllegalArgumentException("Cannot set a timeout on bucket=" + newBucket); } } } if (elapsedRealtime != 0) { appUsageHistory.lastUsedElapsedTime = mElapsedDuration + (elapsedRealtime - mElapsedSnapshot); if (isUserUsage) { appUsageHistory.lastUsedByUserElapsedTime = appUsageHistory.lastUsedElapsedTime; } appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime); } Loading @@ -259,7 +288,7 @@ public class AppIdleHistory { + ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason)); } } appUsageHistory.bucketingReason = REASON_MAIN_USAGE | usageReason; appUsageHistory.bucketingReason = bucketingReason; return appUsageHistory; } Loading Loading @@ -385,6 +414,24 @@ public class AppIdleHistory { appUsageHistory.lastJobRunTime = getElapsedTime(elapsedRealtime); } /** * Notes an attempt to put the app in the {@link UsageStatsManager#STANDBY_BUCKET_RESTRICTED} * bucket. * * @param packageName The package name of the app that is being restricted * @param userId The ID of the user in which the app is being restricted * @param elapsedRealtime The time the attempt was made, in the (unadjusted) elapsed realtime * timebase * @param reason The reason for the restriction attempt */ void noteRestrictionAttempt(String packageName, int userId, long elapsedRealtime, int reason) { ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId); AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName, elapsedRealtime, true); appUsageHistory.lastRestrictAttemptElapsedTime = getElapsedTime(elapsedRealtime); appUsageHistory.lastRestrictReason = reason; } /** * Returns the time since the last job was run for this app. This can be larger than the * current elapsedRealtime, in case it happened before boot or a really large value if no jobs Loading Loading @@ -547,6 +594,9 @@ public class AppIdleHistory { AppUsageHistory appUsageHistory = new AppUsageHistory(); appUsageHistory.lastUsedElapsedTime = Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE)); appUsageHistory.lastUsedByUserElapsedTime = getLongValue(parser, ATTR_LAST_USED_BY_USER_ELAPSED, appUsageHistory.lastUsedElapsedTime); appUsageHistory.lastUsedScreenTime = Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE)); appUsageHistory.lastPredictedTime = getLongValue(parser, Loading @@ -570,6 +620,19 @@ public class AppIdleHistory { appUsageHistory.bucketingReason = Integer.parseInt(bucketingReason, 16); } catch (NumberFormatException nfe) { Slog.wtf(TAG, "Unable to read bucketing reason", nfe); } } appUsageHistory.lastRestrictAttemptElapsedTime = getLongValue(parser, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, 0); String lastRestrictReason = parser.getAttributeValue( null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON); if (lastRestrictReason != null) { try { appUsageHistory.lastRestrictReason = Integer.parseInt(lastRestrictReason, 16); } catch (NumberFormatException nfe) { Slog.wtf(TAG, "Unable to read last restrict reason", nfe); } } appUsageHistory.lastInformedBucket = -1; Loading Loading @@ -618,6 +681,8 @@ public class AppIdleHistory { xml.attribute(null, ATTR_NAME, packageName); xml.attribute(null, ATTR_ELAPSED_IDLE, Long.toString(history.lastUsedElapsedTime)); xml.attribute(null, ATTR_LAST_USED_BY_USER_ELAPSED, Long.toString(history.lastUsedByUserElapsedTime)); xml.attribute(null, ATTR_SCREEN_IDLE, Long.toString(history.lastUsedScreenTime)); xml.attribute(null, ATTR_LAST_PREDICTED_TIME, Loading @@ -638,6 +703,12 @@ public class AppIdleHistory { xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history .lastJobRunTime)); } if (history.lastRestrictAttemptElapsedTime > 0) { xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_ELAPSED, Long.toString(history.lastRestrictAttemptElapsedTime)); } xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON, Integer.toHexString(history.lastRestrictReason)); xml.endTag(null, TAG_PACKAGE); } Loading Loading @@ -672,6 +743,9 @@ public class AppIdleHistory { + UsageStatsManager.reasonToString(appUsageHistory.bucketingReason)); idpw.print(" used="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw); idpw.print(" usedByUser="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedByUserElapsedTime, idpw); idpw.print(" usedScr="); TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw); idpw.print(" lastPred="); Loading @@ -684,6 +758,13 @@ public class AppIdleHistory { idpw); idpw.print(" lastJob="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) { idpw.print(" lastRestrictAttempt="); TimeUtils.formatDuration( totalElapsedTime - appUsageHistory.lastRestrictAttemptElapsedTime, idpw); idpw.print(" lastRestrictReason=" + UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason)); } idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n")); idpw.println(); } Loading
apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +112 −11 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK; import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED; import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE; import static android.app.usage.UsageStatsManager.REASON_SUB_MASK; import static android.app.usage.UsageStatsManager.REASON_SUB_PREDICTED_RESTORED; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_ACTIVE_TIMEOUT; import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_DOZE; Loading @@ -44,6 +45,7 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RESTRICTED; import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; Loading Loading @@ -73,6 +75,7 @@ import android.net.Network; import android.net.NetworkRequest; import android.net.NetworkScoreManager; import android.os.BatteryStats; import android.os.Build; import android.os.Environment; import android.os.Handler; import android.os.IDeviceIdleController; Loading @@ -93,7 +96,9 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Display; 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.IBatteryStats; Loading Loading @@ -124,7 +129,7 @@ import java.util.concurrent.CountDownLatch; public class AppStandbyController implements AppStandbyInternal { private static final String TAG = "AppStandbyController"; static final boolean DEBUG = false; static final boolean DEBUG = true; static final boolean COMPRESS_TIME = false; private static final long ONE_MINUTE = 60 * 1000; Loading Loading @@ -615,6 +620,16 @@ public class AppStandbyController implements AppStandbyInternal { Slog.d(TAG, " Keeping at WORKING_SET due to min timeout"); } } if (app.lastRestrictAttemptElapsedTime > app.lastUsedByUserElapsedTime && elapsedTimeAdjusted - app.lastUsedByUserElapsedTime >= mInjector.getRestrictedBucketDelayMs()) { newBucket = STANDBY_BUCKET_RESTRICTED; reason = app.lastRestrictReason; if (DEBUG) { Slog.d(TAG, "Bringing down to RESTRICTED due to timeout"); } } if (DEBUG) { Slog.d(TAG, " Old bucket=" + oldBucket + ", newBucket=" + newBucket); Loading Loading @@ -733,15 +748,16 @@ public class AppStandbyController implements AppStandbyInternal { elapsedRealtime, elapsedRealtime + mStrongUsageTimeoutMillis); nextCheckTime = mStrongUsageTimeoutMillis; } mHandler.sendMessageDelayed(mHandler.obtainMessage (MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkg), if (appHistory.currentBucket != prevBucket) { mHandler.sendMessageDelayed( mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, pkg), nextCheckTime); final boolean userStartedInteracting = appHistory.currentBucket == STANDBY_BUCKET_ACTIVE && prevBucket != appHistory.currentBucket && (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE; appHistory.currentBucket == STANDBY_BUCKET_ACTIVE && (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE; maybeInformListeners(pkg, userId, elapsedRealtime, appHistory.currentBucket, reason, userStartedInteracting); } if (previouslyIdle) { notifyBatteryStats(pkg, userId, false); Loading Loading @@ -923,6 +939,15 @@ public class AppStandbyController implements AppStandbyInternal { } } static boolean isUserUsage(int reason) { if ((reason & REASON_MAIN_MASK) == REASON_MAIN_USAGE) { final int subReason = reason & REASON_SUB_MASK; return subReason == REASON_SUB_USAGE_USER_INTERACTION || subReason == REASON_SUB_USAGE_MOVE_TO_FOREGROUND; } return false; } @Override public int[] getIdleUidsForUser(int userId) { if (!mAppIdleEnabled) { Loading Loading @@ -1016,6 +1041,20 @@ public class AppStandbyController implements AppStandbyInternal { } } @Override public void restrictApp(@NonNull String packageName, int userId, int restrictReason) { // If the package is not installed, don't allow the bucket to be set. if (!mInjector.isPackageInstalled(packageName, 0, userId)) { Slog.e(TAG, "Tried to restrict uninstalled app: " + packageName); return; } final int reason = REASON_MAIN_FORCED_BY_SYSTEM | (REASON_SUB_MASK & restrictReason); final long nowElapsed = mInjector.elapsedRealtime(); setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RESTRICTED, reason, nowElapsed, false); } @Override public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid, int callingPid) { Loading Loading @@ -1080,6 +1119,7 @@ public class AppStandbyController implements AppStandbyInternal { synchronized (mAppIdleLock) { // If the package is not installed, don't allow the bucket to be set. if (!mInjector.isPackageInstalled(packageName, 0, userId)) { Slog.e(TAG, "Tried to set bucket of uninstalled app: " + packageName); return; } AppIdleHistory.AppUsageHistory app = mAppIdleHistory.getAppUsageHistory(packageName, Loading @@ -1089,8 +1129,9 @@ public class AppStandbyController implements AppStandbyInternal { // Don't allow changing bucket if higher than ACTIVE if (app.currentBucket < STANDBY_BUCKET_ACTIVE) return; // Don't allow prediction to change from/to NEVER // Don't allow prediction to change from/to NEVER or from RESTRICTED. if ((app.currentBucket == STANDBY_BUCKET_NEVER || app.currentBucket == STANDBY_BUCKET_RESTRICTED || newBucket == STANDBY_BUCKET_NEVER) && predicted) { return; Loading @@ -1103,6 +1144,50 @@ public class AppStandbyController implements AppStandbyInternal { return; } final boolean isForcedByUser = (reason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER; // If the current bucket is RESTRICTED, only user force or usage should bring it out. if (app.currentBucket == STANDBY_BUCKET_RESTRICTED && !isUserUsage(reason) && !isForcedByUser) { return; } if (newBucket == STANDBY_BUCKET_RESTRICTED) { mAppIdleHistory .noteRestrictionAttempt(packageName, userId, elapsedRealtime, reason); if (isForcedByUser) { // Only user force can bypass the delay restriction. If the user forced the // app into the RESTRICTED bucket, then a toast confirming the action // shouldn't be surprising. if (Build.IS_DEBUGGABLE) { Toast.makeText(mContext, // Since AppStandbyController sits low in the lock hierarchy, // make sure not to call out with the lock held. mHandler.getLooper(), mContext.getResources().getString( R.string.as_app_forced_to_restricted_bucket, packageName), Toast.LENGTH_SHORT) .show(); } else { Slog.i(TAG, packageName + " restricted by user"); } } else { final long timeUntilRestrictPossibleMs = app.lastUsedByUserElapsedTime + mInjector.getRestrictedBucketDelayMs() - elapsedRealtime; if (timeUntilRestrictPossibleMs > 0) { Slog.w(TAG, "Tried to restrict recently used app: " + packageName + " due to " + reason); mHandler.sendMessageDelayed( mHandler.obtainMessage( MSG_CHECK_PACKAGE_IDLE_STATE, userId, -1, packageName), timeUntilRestrictPossibleMs); return; } } } // If the bucket is required to stay in a higher state for a specified duration, don't // override unless the duration has passed if (predicted) { Loading Loading @@ -1435,6 +1520,12 @@ public class AppStandbyController implements AppStandbyInternal { private DisplayManager mDisplayManager; private PowerManager mPowerManager; int mBootPhase; /** * The minimum amount of time required since the last user interaction before an app can be * placed in the RESTRICTED bucket. */ // TODO: make configurable via DeviceConfig private long mRestrictedBucketDelayMs = ONE_DAY; Injector(Context context, Looper looper) { mContext = context; Loading @@ -1459,6 +1550,12 @@ public class AppStandbyController implements AppStandbyInternal { mDisplayManager = (DisplayManager) mContext.getSystemService( Context.DISPLAY_SERVICE); mPowerManager = mContext.getSystemService(PowerManager.class); final ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); if (activityManager.isLowRamDevice() || ActivityManager.isSmallBatteryDevice()) { mRestrictedBucketDelayMs = 12 * ONE_HOUR; } } mBootPhase = phase; } Loading Loading @@ -1498,6 +1595,10 @@ public class AppStandbyController implements AppStandbyInternal { return Environment.getDataSystemDirectory(); } long getRestrictedBucketDelayMs() { return mRestrictedBucketDelayMs; } void noteEvent(int event, String packageName, int uid) throws RemoteException { mBatteryStats.noteEvent(event, packageName, uid); } Loading
api/current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -8030,6 +8030,7 @@ package android.app.usage { field public static final int STANDBY_BUCKET_ACTIVE = 10; // 0xa field public static final int STANDBY_BUCKET_FREQUENT = 30; // 0x1e field public static final int STANDBY_BUCKET_RARE = 40; // 0x28 field public static final int STANDBY_BUCKET_RESTRICTED = 45; // 0x2d field public static final int STANDBY_BUCKET_WORKING_SET = 20; // 0x14 }