Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 69e01568 authored by Kweku Adams's avatar Kweku Adams Committed by Automerger Merge Worker
Browse files

Merge "Partially exempt headless system apps from app standby." into rvc-dev am: 84e2f0c5

Change-Id: I7e29e85bcbf2c25eb1492c902df461847e977686
parents 72ac5bdb 84e2f0c5
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -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;
+99 −22
Original line number Diff line number Diff line
@@ -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
+61 −11
Original line number Diff line number Diff line
@@ -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));
    }