From fd4fd81677aa3582b492f786ffa2f1cbe1c29f87 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Thu, 3 Mar 2022 16:06:52 -0800 Subject: [PATCH] Extend "headless system" app-standby exemption Originally we only exempted system apps with zero activities from app-standby. Now we exempt system apps with no "front door" activities -- i.e. system apps with no launcher icons. The diff is available here: https://diff.googleplex.com/#key=JUzxUtk7VRJy Bug: 222111596 Bug: 186261675 Test: atest AppStandbyControllerTests Test: manual inspection of `dumpsys usagestats | sed -n '/^mHeadlessSystemApps/,/^\]/p'` Test: Run `adb install -r -g $OUT/product/priv-app/PixelLiveWallpaperPrebuilt/PixelLiveWallpaperPrebuilt.apk` and make sure `com.google.pixel.livewallpaper` is still in the above dumpsys list Change-Id: I8cd5cbd5f5e6ec42668f64df02fa718c0ac915e4 --- .../server/usage/AppStandbyController.java | 65 +++++++++++++++---- .../usage/AppStandbyControllerTests.java | 31 ++++++++- 2 files changed, 82 insertions(+), 14 deletions(-) diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 849354bd1d972..04f96de08acd0 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -78,6 +78,7 @@ import android.content.pm.CrossProfileAppsInternal; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.ResolveInfo; import android.database.ContentObserver; import android.hardware.display.DisplayManager; import android.net.NetworkScoreManager; @@ -219,7 +220,8 @@ public class AppStandbyController private static final int HEADLESS_APP_CHECK_FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE - | PackageManager.GET_ACTIVITIES | PackageManager.MATCH_DISABLED_COMPONENTS; + | PackageManager.MATCH_DISABLED_COMPONENTS + | PackageManager.MATCH_SYSTEM_ONLY; // To name the lock for stack traces static class Lock {} @@ -253,7 +255,7 @@ public class AppStandbyController private final SparseArray> mActiveAdminApps = new SparseArray<>(); /** - * Set of system apps that are headless (don't have any declared activities, enabled or + * Set of system apps that are headless (don't have any "front door" activities, enabled or * disabled). Presence in this map indicates that the app is a headless system app. */ @GuardedBy("mHeadlessSystemApps") @@ -1942,7 +1944,7 @@ public class AppStandbyController try { PackageInfo pi = mPackageManager.getPackageInfoAsUser( packageName, HEADLESS_APP_CHECK_FLAGS, userId); - evaluateSystemAppException(pi); + maybeUpdateHeadlessSystemAppCache(pi); } catch (PackageManager.NameNotFoundException e) { synchronized (mHeadlessSystemApps) { mHeadlessSystemApps.remove(packageName); @@ -1950,19 +1952,31 @@ public class AppStandbyController } } - /** Returns true if the exception status changed. */ - private boolean evaluateSystemAppException(@Nullable PackageInfo pkgInfo) { + /** + * Update the "headless system app" cache. + * + * @return true if the cache is updated. + */ + private boolean maybeUpdateHeadlessSystemAppCache(@Nullable PackageInfo pkgInfo) { if (pkgInfo == null || pkgInfo.applicationInfo == null || (!pkgInfo.applicationInfo.isSystemApp() && !pkgInfo.applicationInfo.isUpdatedSystemApp())) { return false; } + final Intent frontDoorActivityIntent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_LAUNCHER) + .setPackage(pkgInfo.packageName); + List res = mPackageManager.queryIntentActivitiesAsUser(frontDoorActivityIntent, + HEADLESS_APP_CHECK_FLAGS, UserHandle.USER_SYSTEM); + return updateHeadlessSystemAppCache(pkgInfo.packageName, ArrayUtils.isEmpty(res)); + } + + private boolean updateHeadlessSystemAppCache(String packageName, boolean add) { synchronized (mHeadlessSystemApps) { - if (pkgInfo.activities == null || pkgInfo.activities.length == 0) { - // Headless system app. - return mHeadlessSystemApps.add(pkgInfo.packageName); + if (add) { + return mHeadlessSystemApps.add(packageName); } else { - return mHeadlessSystemApps.remove(pkgInfo.packageName); + return mHeadlessSystemApps.remove(packageName); } } } @@ -1999,20 +2013,45 @@ public class AppStandbyController } } + /** Returns the packages that have launcher icons. */ + private Set getSystemPackagesWithLauncherActivities() { + final Intent intent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_LAUNCHER); + List activities = mPackageManager.queryIntentActivitiesAsUser(intent, + HEADLESS_APP_CHECK_FLAGS, UserHandle.USER_SYSTEM); + final ArraySet ret = new ArraySet<>(); + for (ResolveInfo ri : activities) { + ret.add(ri.activityInfo.packageName); + } + return ret; + } + /** Call on system boot to get the initial set of headless system apps. */ private void loadHeadlessSystemAppCache() { - Slog.d(TAG, "Loading headless system app cache. appIdleEnabled=" + mAppIdleEnabled); + final long start = SystemClock.uptimeMillis(); final List packages = mPackageManager.getInstalledPackagesAsUser( HEADLESS_APP_CHECK_FLAGS, UserHandle.USER_SYSTEM); + + final Set systemLauncherActivities = getSystemPackagesWithLauncherActivities(); + final int packageCount = packages.size(); for (int i = 0; i < packageCount; i++) { - PackageInfo pkgInfo = packages.get(i); - if (pkgInfo != null && evaluateSystemAppException(pkgInfo)) { + final PackageInfo pkgInfo = packages.get(i); + if (pkgInfo == null) { + continue; + } + final String pkg = pkgInfo.packageName; + final boolean isHeadLess = !systemLauncherActivities.contains(pkg); + + if (updateHeadlessSystemAppCache(pkg, isHeadLess)) { mHandler.obtainMessage(MSG_CHECK_PACKAGE_IDLE_STATE, - UserHandle.USER_SYSTEM, -1, pkgInfo.packageName) + UserHandle.USER_SYSTEM, -1, pkg) .sendToTarget(); } } + final long end = SystemClock.uptimeMillis(); + Slog.d(TAG, "Loaded headless system app cache in " + (end - start) + " ms:" + + " appIdleEnabled=" + mAppIdleEnabled); } @Override diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 18d3f3d0e8056..3fe0898b792d2 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -62,7 +62,10 @@ import static org.mockito.AdditionalMatchers.not; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.intThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -76,11 +79,13 @@ import android.app.usage.UsageStatsManagerInternal; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.ContextWrapper; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; +import android.content.pm.ResolveInfo; import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.Looper; @@ -118,6 +123,7 @@ import java.util.Random; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * Unit test for AppStandbyController. @@ -421,8 +427,31 @@ public class AppStandbyControllerTests { pib.packageName = PACKAGE_BACKGROUND_LOCATION; packages.add(pib); - doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt()); + // Set up getInstalledPackagesAsUser(). + doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), + anyInt()); + + // Set up getInstalledPackagesAsUser() for "MATCH_ONLY_SYSTEM" + doReturn( + packages.stream().filter(pinfo -> pinfo.applicationInfo.isSystemApp()) + .collect(Collectors.toList()) + ).when(mockPm).getInstalledPackagesAsUser( + intThat(i -> (i & PackageManager.MATCH_SYSTEM_ONLY) != 0), + anyInt()); + + // Set up queryIntentActivitiesAsUser() + final ArrayList systemFrontDoorActivities = new ArrayList<>(); + final ResolveInfo frontDoorActivity = new ResolveInfo(); + frontDoorActivity.activityInfo = new ActivityInfo(); + frontDoorActivity.activityInfo.packageName = pis.packageName; + systemFrontDoorActivities.add(frontDoorActivity); + doReturn(systemFrontDoorActivities).when(mockPm) + .queryIntentActivitiesAsUser(any(Intent.class), + intThat(i -> (i & PackageManager.MATCH_SYSTEM_ONLY) != 0), + anyInt()); + + // Set up other APIs. try { for (int i = 0; i < packages.size(); ++i) { PackageInfo pkg = packages.get(i);