Merge "Creating new RESTRICTED bucket."

This commit is contained in:
Kweku Adams
2020-01-22 21:21:09 +00:00
committed by Android (Google) Code Review
11 changed files with 453 additions and 42 deletions

View File

@@ -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);
@@ -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);

View File

@@ -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.
@@ -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)) {
@@ -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());

View File

@@ -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;
@@ -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
@@ -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
@@ -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
@@ -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) {
@@ -229,25 +246,37 @@ public class AppIdleHistory {
*/
public AppUsageHistory reportUsage(AppUsageHistory appUsageHistory, String packageName,
int newBucket, int usageReason, long elapsedRealtime, long timeout) {
// Set the timeout if applicable
if (timeout > elapsedRealtime) {
// Convert to elapsed timebase
final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
if (newBucket == STANDBY_BUCKET_ACTIVE) {
appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime,
appUsageHistory.bucketActiveTimeoutTime);
} else if (newBucket == STANDBY_BUCKET_WORKING_SET) {
appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime,
appUsageHistory.bucketWorkingSetTimeoutTime);
} else {
throw new IllegalArgumentException("Cannot set a timeout on bucket=" +
newBucket);
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
final long timeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
if (newBucket == STANDBY_BUCKET_ACTIVE) {
appUsageHistory.bucketActiveTimeoutTime = Math.max(timeoutTime,
appUsageHistory.bucketActiveTimeoutTime);
} else if (newBucket == STANDBY_BUCKET_WORKING_SET) {
appUsageHistory.bucketWorkingSetTimeoutTime = Math.max(timeoutTime,
appUsageHistory.bucketWorkingSetTimeoutTime);
} else {
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);
}
@@ -259,7 +288,7 @@ public class AppIdleHistory {
+ ", reason=0x0" + Integer.toHexString(appUsageHistory.bucketingReason));
}
}
appUsageHistory.bucketingReason = REASON_MAIN_USAGE | usageReason;
appUsageHistory.bucketingReason = bucketingReason;
return appUsageHistory;
}
@@ -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
@@ -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,
@@ -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;
@@ -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,
@@ -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);
}
@@ -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=");
@@ -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();
}

View File

@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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);
@@ -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),
nextCheckTime);
final boolean userStartedInteracting =
appHistory.currentBucket == STANDBY_BUCKET_ACTIVE &&
prevBucket != appHistory.currentBucket &&
(prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE;
maybeInformListeners(pkg, userId, elapsedRealtime,
appHistory.currentBucket, reason, userStartedInteracting);
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
&& (prevBucketReason & REASON_MAIN_MASK) != REASON_MAIN_USAGE;
maybeInformListeners(pkg, userId, elapsedRealtime,
appHistory.currentBucket, reason, userStartedInteracting);
}
if (previouslyIdle) {
notifyBatteryStats(pkg, userId, false);
@@ -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) {
@@ -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) {
@@ -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,
@@ -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;
@@ -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) {
@@ -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;
@@ -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;
}
@@ -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);
}

View File

@@ -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
}

View File

@@ -142,13 +142,22 @@ public final class UsageStatsManager {
/**
* The app has not be used for several days and/or is unlikely to be used for several days.
* Apps in this bucket will have the most restrictions, including network restrictions, except
* Apps in this bucket will have more restrictions, including network restrictions, except
* during certain short periods (at a minimum, once a day) when they are allowed to execute
* jobs, access the network, etc.
* @see #getAppStandbyBucket()
*/
public static final int STANDBY_BUCKET_RARE = 40;
/**
* The app has not be used for several days, is unlikely to be used for several days, and has
* been misbehaving in some manner.
* Apps in this bucket will have the most restrictions, including network restrictions and
* additional restrictions on jobs.
* @see #getAppStandbyBucket()
*/
public static final int STANDBY_BUCKET_RESTRICTED = 45;
/**
* The app has never been used.
* {@hide}
@@ -278,6 +287,26 @@ public final class UsageStatsManager {
* @hide
*/
public static final int REASON_SUB_PREDICTED_RESTORED = 0x0001;
/**
* The reason for restricting the app is unknown or undefined.
* @hide
*/
public static final int REASON_SUB_RESTRICT_UNDEFINED = 0x0000;
/**
* The app was unnecessarily using system resources (battery, memory, etc) in the background.
* @hide
*/
public static final int REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE = 0x0001;
/**
* The app was deemed to be intentionally abusive.
* @hide
*/
public static final int REASON_SUB_RESTRICT_ABUSE = 0x0002;
/**
* The app was displaying buggy behavior.
* @hide
*/
public static final int REASON_SUB_RESTRICT_BUGGY = 0x0003;
/** @hide */
@@ -287,6 +316,7 @@ public final class UsageStatsManager {
STANDBY_BUCKET_WORKING_SET,
STANDBY_BUCKET_FREQUENT,
STANDBY_BUCKET_RARE,
STANDBY_BUCKET_RESTRICTED,
STANDBY_BUCKET_NEVER,
})
@Retention(RetentionPolicy.SOURCE)
@@ -598,7 +628,7 @@ public final class UsageStatsManager {
* state of the app based on app usage patterns. Standby buckets determine how much an app will
* be restricted from running background tasks such as jobs and alarms.
* <p>Restrictions increase progressively from {@link #STANDBY_BUCKET_ACTIVE} to
* {@link #STANDBY_BUCKET_RARE}, with {@link #STANDBY_BUCKET_ACTIVE} being the least
* {@link #STANDBY_BUCKET_RESTRICTED}, with {@link #STANDBY_BUCKET_ACTIVE} being the least
* restrictive. The battery level of the device might also affect the restrictions.
* <p>Apps in buckets &le; {@link #STANDBY_BUCKET_ACTIVE} have no standby restrictions imposed.
* Apps in buckets &gt; {@link #STANDBY_BUCKET_FREQUENT} may have network access restricted when
@@ -642,7 +672,8 @@ public final class UsageStatsManager {
/**
* {@hide}
* Changes an app's standby bucket to the provided value. The caller can only set the standby
* bucket for a different app than itself.
* bucket for a different app than itself. The caller will not be able to change an app's
* standby bucket if that app is in the {@link #STANDBY_BUCKET_RESTRICTED} bucket.
* @param packageName the package name of the app to set the bucket for. A SecurityException
* will be thrown if the package name is that of the caller.
* @param bucket the standby bucket to set it to, which should be one of STANDBY_BUCKET_*.
@@ -688,7 +719,8 @@ public final class UsageStatsManager {
/**
* {@hide}
* Changes the app standby bucket for multiple apps at once. The Map is keyed by the package
* name and the value is one of STANDBY_BUCKET_*.
* name and the value is one of STANDBY_BUCKET_*. The caller will not be able to change an
* app's standby bucket if that app is in the {@link #STANDBY_BUCKET_RESTRICTED} bucket.
* @param appBuckets a map of package name to bucket value.
*/
@SystemApi
@@ -1027,6 +1059,20 @@ public final class UsageStatsManager {
break;
case REASON_MAIN_FORCED_BY_SYSTEM:
sb.append("s");
switch (standbyReason & REASON_SUB_MASK) {
case REASON_SUB_RESTRICT_ABUSE:
sb.append("-ra");
break;
case REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE:
sb.append("-rbru");
break;
case REASON_SUB_RESTRICT_BUGGY:
sb.append("-rb");
break;
case REASON_SUB_RESTRICT_UNDEFINED:
sb.append("-r");
break;
}
break;
case REASON_MAIN_FORCED_BY_USER:
sb.append("f");

View File

@@ -5315,4 +5315,8 @@
<!-- Accessibility description of caption view -->
<string name="accessibility_freeform_caption">Caption bar of <xliff:g id="app_name">%1$s</xliff:g>.</string>
<!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] -->
<string name="as_app_forced_to_restricted_bucket">
<xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string>
</resources>

View File

@@ -3810,6 +3810,9 @@
<java-symbol type="string" name="config_rawContactsLocalAccountName" />
<java-symbol type="string" name="config_rawContactsLocalAccountType" />
<!-- For App Standby -->
<java-symbol type="string" name="as_app_forced_to_restricted_bucket" />
<!-- Assistant handles -->
<java-symbol type="dimen" name="assist_handle_shadow_radius" />

View File

@@ -2391,6 +2391,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
return UsageStatsManager.STANDBY_BUCKET_FREQUENT;
} else if (lower.startsWith("ra")) {
return UsageStatsManager.STANDBY_BUCKET_RARE;
} else if (lower.startsWith("re")) {
return UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
} else if (lower.startsWith("ne")) {
return UsageStatsManager.STANDBY_BUCKET_NEVER;
} else {

View File

@@ -16,18 +16,18 @@
package com.android.server.usage;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
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_RESTRICT_BACKGROUND_RESOURCE_USAGE;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.os.FileUtils;
import android.test.AndroidTestCase;
@@ -37,10 +37,12 @@ public class AppIdleHistoryTests extends AndroidTestCase {
File mStorageDir;
final static String PACKAGE_1 = "com.android.testpackage1";
final static String PACKAGE_2 = "com.android.testpackage2";
private static final String PACKAGE_1 = "com.android.testpackage1";
private static final String PACKAGE_2 = "com.android.testpackage2";
private static final String PACKAGE_3 = "com.android.testpackage3";
private static final String PACKAGE_4 = "com.android.testpackage4";
final static int USER_ID = 0;
private static final int USER_ID = 0;
@Override
protected void setUp() throws Exception {
@@ -100,16 +102,27 @@ public class AppIdleHistoryTests extends AndroidTestCase {
aih.setAppStandbyBucket(PACKAGE_2, USER_ID, 2000, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_USAGE);
aih.setAppStandbyBucket(PACKAGE_3, USER_ID, 2500, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE);
aih.setAppStandbyBucket(PACKAGE_4, USER_ID, 2750, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
aih.setAppStandbyBucket(PACKAGE_1, USER_ID, 3000, STANDBY_BUCKET_RARE,
REASON_MAIN_TIMEOUT);
assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 3000), STANDBY_BUCKET_RARE);
assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 3000), STANDBY_BUCKET_ACTIVE);
assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 3000), REASON_MAIN_TIMEOUT);
assertEquals(aih.getAppStandbyBucket(PACKAGE_3, USER_ID, 3000), STANDBY_BUCKET_RESTRICTED);
assertEquals(aih.getAppStandbyReason(PACKAGE_3, USER_ID, 3000),
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE);
assertEquals(aih.getAppStandbyReason(PACKAGE_4, USER_ID, 3000),
REASON_MAIN_FORCED_BY_USER);
// RARE is considered idle
// RARE and RESTRICTED are considered idle
assertTrue(aih.isIdle(PACKAGE_1, USER_ID, 3000));
assertFalse(aih.isIdle(PACKAGE_2, USER_ID, 3000));
assertTrue(aih.isIdle(PACKAGE_3, USER_ID, 3000));
assertTrue(aih.isIdle(PACKAGE_4, USER_ID, 3000));
// Check persistence
aih.writeAppIdleDurations();
@@ -118,6 +131,11 @@ public class AppIdleHistoryTests extends AndroidTestCase {
assertEquals(aih.getAppStandbyBucket(PACKAGE_1, USER_ID, 5000), STANDBY_BUCKET_RARE);
assertEquals(aih.getAppStandbyBucket(PACKAGE_2, USER_ID, 5000), STANDBY_BUCKET_ACTIVE);
assertEquals(aih.getAppStandbyReason(PACKAGE_1, USER_ID, 5000), REASON_MAIN_TIMEOUT);
assertEquals(aih.getAppStandbyBucket(PACKAGE_3, USER_ID, 3000), STANDBY_BUCKET_RESTRICTED);
assertEquals(aih.getAppStandbyReason(PACKAGE_3, USER_ID, 3000),
REASON_MAIN_FORCED_BY_SYSTEM | REASON_SUB_RESTRICT_BACKGROUND_RESOURCE_USAGE);
assertEquals(aih.getAppStandbyReason(PACKAGE_4, USER_ID, 3000),
REASON_MAIN_FORCED_BY_USER);
assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));

View File

@@ -28,11 +28,17 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
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_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_MOVE_TO_FOREGROUND;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYNC_ADAPTER;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_SYSTEM_INTERACTION;
import static android.app.usage.UsageStatsManager.REASON_SUB_USAGE_USER_INTERACTION;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
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 org.junit.Assert.assertEquals;
@@ -46,6 +52,8 @@ import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
import android.appwidget.AppWidgetManager;
@@ -124,6 +132,13 @@ public class AppStandbyControllerTests {
public PackageManager getPackageManager() {
return mockPm;
}
public Object getSystemService(@NonNull String name) {
if (Context.ACTIVITY_SERVICE.equals(name)) {
return mock(ActivityManager.class);
}
return super.getSystemService(name);
}
}
static class MyInjector extends AppStandbyController.Injector {
@@ -253,7 +268,10 @@ public class AppStandbyControllerTests {
doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt());
try {
doReturn(UID_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_1), anyInt());
doReturn(UID_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_1), anyInt(), anyInt());
doReturn(UID_EXEMPTED_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_EXEMPTED_1),
anyInt());
doReturn(UID_EXEMPTED_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_EXEMPTED_1),
anyInt(), anyInt());
doReturn(pi.applicationInfo).when(mockPm).getApplicationInfo(eq(pi.packageName),
@@ -468,7 +486,7 @@ public class AppStandbyControllerTests {
}
@Test
public void testPredictionTimedout() throws Exception {
public void testPredictionTimedOut() throws Exception {
// Set it to timeout or usage, so that prediction can override it
mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
@@ -532,6 +550,79 @@ public class AppStandbyControllerTests {
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
REASON_MAIN_PREDICTED);
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
// Prediction can't remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_PREDICTED);
assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
// Force from user can remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_USER);
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
// Force from system can remove from RESTRICTED if it was put it in due to system
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_PREDICTED);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_FORCED_BY_SYSTEM);
assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
// Non-user usage can't remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE);
assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_SYSTEM_INTERACTION);
assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_SYNC_ADAPTER);
assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_EXEMPTED_SYNC_SCHEDULED_NON_DOZE);
assertEquals(STANDBY_BUCKET_RESTRICTED, getStandbyBucket(mController, PACKAGE_1));
// Explicit user usage can remove from RESTRICTED
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_USER);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
REASON_MAIN_USAGE | REASON_SUB_USAGE_USER_INTERACTION);
assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController, PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
REASON_MAIN_USAGE | REASON_SUB_USAGE_MOVE_TO_FOREGROUND);
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
}
@Test
@@ -556,6 +647,55 @@ public class AppStandbyControllerTests {
assertBucket(STANDBY_BUCKET_RARE);
}
/**
* Test that setAppStandbyBucket to RESTRICTED doesn't change the bucket until the usage
* timeout has passed.
*/
@Test
public void testTimeoutBeforeRestricted() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
assertBucket(STANDBY_BUCKET_ACTIVE);
mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
assertBucket(STANDBY_BUCKET_ACTIVE);
// bucketing works after timeout
mInjector.mElapsedRealtime += DAY_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
assertBucket(STANDBY_BUCKET_RESTRICTED);
// Way past all timeouts. Make sure timeout processing doesn't raise bucket.
mInjector.mElapsedRealtime += RARE_THRESHOLD * 4;
mController.checkIdleStates(USER_ID);
assertBucket(STANDBY_BUCKET_RESTRICTED);
}
/**
* Test that an app is put into the RESTRICTED bucket after enough time has passed.
*/
@Test
public void testRestrictedDelay() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
assertBucket(STANDBY_BUCKET_ACTIVE);
mInjector.mElapsedRealtime += mInjector.getRestrictedBucketDelayMs() - 5000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RESTRICTED,
REASON_MAIN_FORCED_BY_SYSTEM);
// Bucket shouldn't change
assertBucket(STANDBY_BUCKET_ACTIVE);
// bucketing works after timeout
mInjector.mElapsedRealtime += 6000;
Thread.sleep(6000);
// Enough time has passed. The app should automatically be put into the RESTRICTED bucket.
assertBucket(STANDBY_BUCKET_RESTRICTED);
}
@Test
public void testCascadingTimeouts() throws Exception {
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);