Merge "Partially exempt headless system apps from app standby." into rvc-dev
This commit is contained in:
@@ -79,6 +79,12 @@ public class AppIdleHistory {
|
||||
|
||||
private static final int STANDBY_BUCKET_UNKNOWN = -1;
|
||||
|
||||
/**
|
||||
* The bucket beyond which apps are considered idle. Any apps in this bucket or lower are
|
||||
* considered idle while those in higher buckets are not considered idle.
|
||||
*/
|
||||
static final int IDLE_BUCKET_CUTOFF = STANDBY_BUCKET_RARE;
|
||||
|
||||
@VisibleForTesting
|
||||
static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
|
||||
private static final String TAG_PACKAGES = "packages";
|
||||
@@ -350,7 +356,7 @@ public class AppIdleHistory {
|
||||
ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
|
||||
AppUsageHistory appUsageHistory =
|
||||
getPackageHistory(userHistory, packageName, elapsedRealtime, true);
|
||||
return appUsageHistory.currentBucket >= STANDBY_BUCKET_RARE;
|
||||
return appUsageHistory.currentBucket >= IDLE_BUCKET_CUTOFF;
|
||||
}
|
||||
|
||||
public AppUsageHistory getAppUsageHistory(String packageName, int userId,
|
||||
@@ -487,7 +493,7 @@ public class AppIdleHistory {
|
||||
final int newBucket;
|
||||
final int reason;
|
||||
if (idle) {
|
||||
newBucket = STANDBY_BUCKET_RARE;
|
||||
newBucket = IDLE_BUCKET_CUTOFF;
|
||||
reason = REASON_MAIN_FORCED_BY_USER;
|
||||
} else {
|
||||
newBucket = STANDBY_BUCKET_ACTIVE;
|
||||
|
||||
@@ -54,6 +54,7 @@ import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
|
||||
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AppGlobals;
|
||||
@@ -92,6 +93,7 @@ import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings.Global;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.KeyValueListParser;
|
||||
import android.util.Slog;
|
||||
@@ -227,6 +229,13 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
@GuardedBy("mActiveAdminApps")
|
||||
private final SparseArray<Set<String>> mActiveAdminApps = new SparseArray<>();
|
||||
|
||||
/**
|
||||
* Set of system apps that are headless (don't have any declared activities, enabled or
|
||||
* disabled). Presence in this map indicates that the app is a headless system app.
|
||||
*/
|
||||
@GuardedBy("mAppIdleLock")
|
||||
private final ArrayMap<String, Boolean> mHeadlessSystemApps = new ArrayMap<>();
|
||||
|
||||
private final CountDownLatch mAdminDataAvailableLatch = new CountDownLatch(1);
|
||||
|
||||
// Messages for the handler
|
||||
@@ -667,20 +676,22 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
return;
|
||||
}
|
||||
}
|
||||
final boolean isSpecial = isAppSpecial(packageName,
|
||||
final int minBucket = getAppMinBucket(packageName,
|
||||
UserHandle.getAppId(uid),
|
||||
userId);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, " Checking idle state for " + packageName + " special=" +
|
||||
isSpecial);
|
||||
Slog.d(TAG, " Checking idle state for " + packageName
|
||||
+ " minBucket=" + minBucket);
|
||||
}
|
||||
if (isSpecial) {
|
||||
if (minBucket <= STANDBY_BUCKET_ACTIVE) {
|
||||
// No extra processing needed for ACTIVE or higher since apps can't drop into lower
|
||||
// buckets.
|
||||
synchronized (mAppIdleLock) {
|
||||
mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime,
|
||||
STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT);
|
||||
minBucket, REASON_MAIN_DEFAULT);
|
||||
}
|
||||
maybeInformListeners(packageName, userId, elapsedRealtime,
|
||||
STANDBY_BUCKET_EXEMPTED, REASON_MAIN_DEFAULT, false);
|
||||
minBucket, REASON_MAIN_DEFAULT, false);
|
||||
} else {
|
||||
synchronized (mAppIdleLock) {
|
||||
final AppIdleHistory.AppUsageHistory app =
|
||||
@@ -761,6 +772,14 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
Slog.d(TAG, "Bringing up from RESTRICTED to RARE due to off switch");
|
||||
}
|
||||
}
|
||||
if (newBucket > minBucket) {
|
||||
newBucket = minBucket;
|
||||
// Leave the reason alone.
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Bringing up from " + newBucket + " to " + minBucket
|
||||
+ " due to min bucketing");
|
||||
}
|
||||
}
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, " Old bucket=" + oldBucket
|
||||
+ ", newBucket=" + newBucket);
|
||||
@@ -1027,20 +1046,35 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
|
||||
}
|
||||
|
||||
private boolean isAppSpecial(String packageName, int appId, int userId) {
|
||||
if (packageName == null) return false;
|
||||
@StandbyBuckets
|
||||
private int getAppMinBucket(String packageName, int userId) {
|
||||
try {
|
||||
final int uid = mPackageManager.getPackageUidAsUser(packageName, userId);
|
||||
return getAppMinBucket(packageName, UserHandle.getAppId(uid), userId);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Not a valid package for this user, nothing to do
|
||||
return STANDBY_BUCKET_NEVER;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the lowest bucket this app should ever enter.
|
||||
*/
|
||||
@StandbyBuckets
|
||||
private int getAppMinBucket(String packageName, int appId, int userId) {
|
||||
if (packageName == null) return STANDBY_BUCKET_NEVER;
|
||||
// If not enabled at all, of course nobody is ever idle.
|
||||
if (!mAppIdleEnabled) {
|
||||
return true;
|
||||
return STANDBY_BUCKET_EXEMPTED;
|
||||
}
|
||||
if (appId < Process.FIRST_APPLICATION_UID) {
|
||||
// System uids never go idle.
|
||||
return true;
|
||||
return STANDBY_BUCKET_EXEMPTED;
|
||||
}
|
||||
if (packageName.equals("android")) {
|
||||
// Nor does the framework (which should be redundant with the above, but for MR1 we will
|
||||
// retain this for safety).
|
||||
return true;
|
||||
return STANDBY_BUCKET_EXEMPTED;
|
||||
}
|
||||
if (mSystemServicesReady) {
|
||||
try {
|
||||
@@ -1048,42 +1082,51 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
// for idle mode, because app idle (aka app standby) is really not as big an issue
|
||||
// for controlling who participates vs. doze mode.
|
||||
if (mInjector.isNonIdleWhitelisted(packageName)) {
|
||||
return true;
|
||||
return STANDBY_BUCKET_EXEMPTED;
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
throw re.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
if (isActiveDeviceAdmin(packageName, userId)) {
|
||||
return true;
|
||||
return STANDBY_BUCKET_EXEMPTED;
|
||||
}
|
||||
|
||||
if (isActiveNetworkScorer(packageName)) {
|
||||
return true;
|
||||
return STANDBY_BUCKET_EXEMPTED;
|
||||
}
|
||||
|
||||
if (mAppWidgetManager != null
|
||||
&& mInjector.isBoundWidgetPackage(mAppWidgetManager, packageName, userId)) {
|
||||
return true;
|
||||
// TODO: consider lowering to ACTIVE
|
||||
return STANDBY_BUCKET_EXEMPTED;
|
||||
}
|
||||
|
||||
if (isDeviceProvisioningPackage(packageName)) {
|
||||
return true;
|
||||
return STANDBY_BUCKET_EXEMPTED;
|
||||
}
|
||||
}
|
||||
|
||||
// Check this last, as it can be the most expensive check
|
||||
if (isCarrierApp(packageName)) {
|
||||
return true;
|
||||
return STANDBY_BUCKET_EXEMPTED;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (isHeadlessSystemApp(packageName)) {
|
||||
return STANDBY_BUCKET_ACTIVE;
|
||||
}
|
||||
|
||||
return STANDBY_BUCKET_NEVER;
|
||||
}
|
||||
|
||||
private boolean isHeadlessSystemApp(String packageName) {
|
||||
return mHeadlessSystemApps.containsKey(packageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAppIdleFiltered(String packageName, int appId, int userId,
|
||||
long elapsedRealtime) {
|
||||
if (isAppSpecial(packageName, appId, userId)) {
|
||||
if (getAppMinBucket(packageName, appId, userId) < AppIdleHistory.IDLE_BUCKET_CUTOFF) {
|
||||
return false;
|
||||
} else {
|
||||
synchronized (mAppIdleLock) {
|
||||
@@ -1423,6 +1466,8 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we don't put the app in a lower bucket than it's supposed to be in.
|
||||
newBucket = Math.min(newBucket, getAppMinBucket(packageName, userId));
|
||||
mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
|
||||
reason, resetTimeout);
|
||||
}
|
||||
@@ -1617,14 +1662,16 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
final String action = intent.getAction();
|
||||
final String pkgName = intent.getData().getSchemeSpecificPart();
|
||||
final int userId = getSendingUserId();
|
||||
if (Intent.ACTION_PACKAGE_ADDED.equals(action)
|
||||
|| Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
|
||||
clearCarrierPrivilegedApps();
|
||||
// ACTION_PACKAGE_ADDED is called even for system app downgrades.
|
||||
evaluateSystemAppException(pkgName, userId);
|
||||
}
|
||||
if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
|
||||
Intent.ACTION_PACKAGE_ADDED.equals(action))) {
|
||||
final String pkgName = intent.getData().getSchemeSpecificPart();
|
||||
final int userId = getSendingUserId();
|
||||
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
|
||||
maybeUnrestrictBuggyApp(pkgName, userId);
|
||||
} else {
|
||||
@@ -1634,6 +1681,34 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluateSystemAppException(String packageName, int userId) {
|
||||
if (!mSystemServicesReady) {
|
||||
// The app will be evaluated in initializeDefaultsForSystemApps() when possible.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
PackageInfo pi = mPackageManager.getPackageInfoAsUser(packageName,
|
||||
PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS,
|
||||
userId);
|
||||
evaluateSystemAppException(pi);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
mHeadlessSystemApps.remove(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluateSystemAppException(@Nullable PackageInfo pkgInfo) {
|
||||
if (pkgInfo.applicationInfo != null && pkgInfo.applicationInfo.isSystemApp()) {
|
||||
synchronized (mAppIdleLock) {
|
||||
if (pkgInfo.activities == null || pkgInfo.activities.length == 0) {
|
||||
// Headless system app.
|
||||
mHeadlessSystemApps.put(pkgInfo.packageName, true);
|
||||
} else {
|
||||
mHeadlessSystemApps.remove(pkgInfo.packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeDefaultsForSystemApps(int userId) {
|
||||
if (!mSystemServicesReady) {
|
||||
@@ -1645,7 +1720,7 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
+ "appIdleEnabled=" + mAppIdleEnabled);
|
||||
final long elapsedRealtime = mInjector.elapsedRealtime();
|
||||
List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
|
||||
PackageManager.MATCH_DISABLED_COMPONENTS,
|
||||
PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS,
|
||||
userId);
|
||||
final int packageCount = packages.size();
|
||||
synchronized (mAppIdleLock) {
|
||||
@@ -1658,6 +1733,8 @@ public class AppStandbyController implements AppStandbyInternal {
|
||||
mAppIdleHistory.reportUsage(packageName, userId, STANDBY_BUCKET_ACTIVE,
|
||||
REASON_SUB_USAGE_SYSTEM_UPDATE, 0,
|
||||
elapsedRealtime + mSystemUpdateUsageTimeoutMillis);
|
||||
|
||||
evaluateSystemAppException(pi);
|
||||
}
|
||||
}
|
||||
// Immediately persist defaults to disk
|
||||
|
||||
@@ -63,6 +63,7 @@ import android.app.usage.UsageEvents;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -107,6 +108,10 @@ public class AppStandbyControllerTests {
|
||||
private static final int UID_1 = 10000;
|
||||
private static final String PACKAGE_EXEMPTED_1 = "com.android.exempted";
|
||||
private static final int UID_EXEMPTED_1 = 10001;
|
||||
private static final String PACKAGE_SYSTEM_HEADFULL = "com.example.system.headfull";
|
||||
private static final int UID_SYSTEM_HEADFULL = 10002;
|
||||
private static final String PACKAGE_SYSTEM_HEADLESS = "com.example.system.headless";
|
||||
private static final int UID_SYSTEM_HEADLESS = 10003;
|
||||
private static final int USER_ID = 0;
|
||||
private static final int USER_ID2 = 10;
|
||||
private static final UserHandle USER_HANDLE_USER2 = new UserHandle(USER_ID2);
|
||||
@@ -305,18 +310,33 @@ public class AppStandbyControllerTests {
|
||||
pie.packageName = PACKAGE_EXEMPTED_1;
|
||||
packages.add(pie);
|
||||
|
||||
PackageInfo pis = new PackageInfo();
|
||||
pis.activities = new ActivityInfo[]{mock(ActivityInfo.class)};
|
||||
pis.applicationInfo = new ApplicationInfo();
|
||||
pis.applicationInfo.uid = UID_SYSTEM_HEADFULL;
|
||||
pis.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||
pis.packageName = PACKAGE_SYSTEM_HEADFULL;
|
||||
packages.add(pis);
|
||||
|
||||
PackageInfo pish = new PackageInfo();
|
||||
pish.applicationInfo = new ApplicationInfo();
|
||||
pish.applicationInfo.uid = UID_SYSTEM_HEADLESS;
|
||||
pish.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
|
||||
pish.packageName = PACKAGE_SYSTEM_HEADLESS;
|
||||
packages.add(pish);
|
||||
|
||||
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),
|
||||
anyInt());
|
||||
doReturn(pie.applicationInfo).when(mockPm).getApplicationInfo(eq(pie.packageName),
|
||||
anyInt());
|
||||
for (int i = 0; i < packages.size(); ++i) {
|
||||
PackageInfo pkg = packages.get(i);
|
||||
|
||||
doReturn(pkg.applicationInfo.uid).when(mockPm)
|
||||
.getPackageUidAsUser(eq(pkg.packageName), anyInt());
|
||||
doReturn(pkg.applicationInfo.uid).when(mockPm)
|
||||
.getPackageUidAsUser(eq(pkg.packageName), anyInt(), anyInt());
|
||||
doReturn(pkg.applicationInfo).when(mockPm)
|
||||
.getApplicationInfo(eq(pkg.packageName), anyInt());
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException nnfe) {}
|
||||
}
|
||||
|
||||
@@ -514,7 +534,11 @@ public class AppStandbyControllerTests {
|
||||
}
|
||||
|
||||
private void assertBucket(int bucket) {
|
||||
assertEquals(bucket, getStandbyBucket(mController, PACKAGE_1));
|
||||
assertBucket(bucket, PACKAGE_1);
|
||||
}
|
||||
|
||||
private void assertBucket(int bucket, String pkg) {
|
||||
assertEquals(bucket, getStandbyBucket(mController, pkg));
|
||||
}
|
||||
|
||||
private void assertNotBucket(int bucket) {
|
||||
@@ -1462,6 +1486,32 @@ public class AppStandbyControllerTests {
|
||||
assertBucket(STANDBY_BUCKET_RESTRICTED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSystemHeadlessAppElevated() {
|
||||
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime, PACKAGE_1);
|
||||
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime,
|
||||
PACKAGE_SYSTEM_HEADFULL);
|
||||
reportEvent(mController, USER_INTERACTION, mInjector.mElapsedRealtime,
|
||||
PACKAGE_SYSTEM_HEADLESS);
|
||||
mInjector.mElapsedRealtime += RESTRICTED_THRESHOLD;
|
||||
|
||||
|
||||
mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADFULL, USER_ID, STANDBY_BUCKET_RARE,
|
||||
REASON_MAIN_TIMEOUT);
|
||||
assertBucket(STANDBY_BUCKET_RARE, PACKAGE_SYSTEM_HEADFULL);
|
||||
|
||||
// Make sure headless system apps don't get lowered.
|
||||
mController.setAppStandbyBucket(PACKAGE_SYSTEM_HEADLESS, USER_ID, STANDBY_BUCKET_RARE,
|
||||
REASON_MAIN_TIMEOUT);
|
||||
assertBucket(STANDBY_BUCKET_ACTIVE, PACKAGE_SYSTEM_HEADLESS);
|
||||
|
||||
// Package 1 doesn't have activities and is headless, but is not a system app, so it can
|
||||
// be lowered.
|
||||
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
|
||||
REASON_MAIN_TIMEOUT);
|
||||
assertBucket(STANDBY_BUCKET_RARE, PACKAGE_1);
|
||||
}
|
||||
|
||||
private String getAdminAppsStr(int userId) {
|
||||
return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user