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

Commit bd7b302f authored by Amith Yamasani's avatar Amith Yamasani
Browse files

Timeout app predictions after 12 hours

Ignore predicted bucket if it's been too long since the
last update, and use internal timeout logic to set
standby buckets.

Keep track of last predicted time and persist it.

Bug: 70219058
Test: atest FrameworksServicesTests:AppStandbyControllerTests

Change-Id: Ib2992201d204b92fdd4a0a00754d81e7bbfc292f
parent 42a24dcc
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.usage;

import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN;
import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
import static android.app.usage.UsageStatsManager.REASON_PREDICTED;
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;
@@ -364,4 +365,29 @@ public class AppStandbyControllerTests {
        reportEvent(controller, NOTIFICATION_SEEN, mInjector.mElapsedRealtime);
        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(controller));
    }

    @Test
    public void testPredictionTimedout() throws Exception {
        AppStandbyController controller = setupController();
        setChargingState(controller, false);
        controller.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
                REASON_PREDICTED + "CTS", 1 * HOUR_MS);

        // Fast forward 12 hours
        mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
        controller.checkIdleStates(USER_ID);
        // Should still be in predicted bucket, since prediction timeout is 1 day since prediction
        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(controller));
        // Fast forward two more hours
        mInjector.mElapsedRealtime += 2 * HOUR_MS;
        controller.checkIdleStates(USER_ID);
        // Should have now applied prediction timeout
        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(controller));

        // Fast forward RARE bucket
        mInjector.mElapsedRealtime += RARE_THRESHOLD;
        controller.checkIdleStates(USER_ID);
        // Should continue to apply prediction timeout
        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(controller));
    }
}
+37 −7
Original line number Diff line number Diff line
@@ -72,14 +72,13 @@ 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 the app bucket was last predicted externally
    private static final String ATTR_LAST_PREDICTED_TIME = "lastPredictedTime";
    // The standby bucket for the app
    private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
    // The reason the app was put in the above bucket
    private static final String ATTR_BUCKETING_REASON = "bucketReason";

    // State that was last informed to listeners, since boot
    private static final int STATE_UNINFORMED = 0;
    private static final int STATE_ACTIVE = 1;
    private static final int STATE_IDLE = 2;

    // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
    private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
    private long mElapsedDuration; // Total device on duration since device was "born"
@@ -92,13 +91,21 @@ public class AppIdleHistory {

    private boolean mScreenOn;

    private static class AppUsageHistory {
    static class AppUsageHistory {
        // Debug
        final byte[] recent = new byte[HISTORY_SIZE];
        // Last used time using elapsed timebase
        long lastUsedElapsedTime;
        // Last used time using screen_on timebase
        long lastUsedScreenTime;
        // Last predicted time using elapsed timebase
        long lastPredictedTime;
        // Standby bucket
        @UsageStatsManager.StandbyBuckets
        int currentBucket;
        // Reason for setting the standby bucket. TODO: Switch to int.
        String bucketingReason;
        // In-memory only, last bucket for which the listeners were informed
        int lastInformedBucket;
    }

@@ -269,6 +276,7 @@ public class AppIdleHistory {
            appUsageHistory = new AppUsageHistory();
            appUsageHistory.lastUsedElapsedTime = getElapsedTime(elapsedRealtime);
            appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
            appUsageHistory.lastPredictedTime = getElapsedTime(0);
            appUsageHistory.currentBucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
            appUsageHistory.bucketingReason = UsageStatsManager.REASON_DEFAULT;
            appUsageHistory.lastInformedBucket = -1;
@@ -295,6 +303,14 @@ public class AppIdleHistory {
        }
    }

    public AppUsageHistory getAppUsageHistory(String packageName, int userId,
            long elapsedRealtime) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory =
                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
        return appUsageHistory;
    }

    public void setAppStandbyBucket(String packageName, int userId, long elapsedRealtime,
            int bucket, String reason) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
@@ -302,6 +318,9 @@ public class AppIdleHistory {
                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
        appUsageHistory.currentBucket = bucket;
        appUsageHistory.bucketingReason = reason;
        if (reason.startsWith(UsageStatsManager.REASON_PREDICTED)) {
            appUsageHistory.lastPredictedTime = getElapsedTime(elapsedRealtime);
        }
        if (DEBUG) {
            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
                    + ", reason=" + appUsageHistory.bucketingReason);
@@ -322,7 +341,7 @@ public class AppIdleHistory {
        return appUsageHistory != null ? appUsageHistory.bucketingReason : null;
    }

    private long getElapsedTime(long elapsedRealtime) {
    public long getElapsedTime(long elapsedRealtime) {
        return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
    }

@@ -431,6 +450,12 @@ public class AppIdleHistory {
                                Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
                        appUsageHistory.lastUsedScreenTime =
                                Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
                        String lastPredictedTimeString = parser.getAttributeValue(null,
                                ATTR_LAST_PREDICTED_TIME);
                        if (lastPredictedTimeString != null) {
                            appUsageHistory.lastPredictedTime =
                                    Long.parseLong(lastPredictedTimeString);
                        }
                        String currentBucketString = parser.getAttributeValue(null,
                                ATTR_CURRENT_BUCKET);
                        appUsageHistory.currentBucket = currentBucketString == null
@@ -441,6 +466,7 @@ public class AppIdleHistory {
                        if (appUsageHistory.bucketingReason == null) {
                            appUsageHistory.bucketingReason = UsageStatsManager.REASON_DEFAULT;
                        }
                        appUsageHistory.lastInformedBucket = -1;
                        userHistory.put(packageName, appUsageHistory);
                    }
                }
@@ -477,6 +503,8 @@ public class AppIdleHistory {
                        Long.toString(history.lastUsedElapsedTime));
                xml.attribute(null, ATTR_SCREEN_IDLE,
                        Long.toString(history.lastUsedScreenTime));
                xml.attribute(null, ATTR_LAST_PREDICTED_TIME,
                        Long.toString(history.lastPredictedTime));
                xml.attribute(null, ATTR_CURRENT_BUCKET,
                        Integer.toString(history.currentBucket));
                xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
@@ -512,6 +540,8 @@ public class AppIdleHistory {
            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastUsedElapsedTime, idpw);
            idpw.print(" lastUsedScreenOn=");
            TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
            idpw.print(" lastPredictedTime=");
            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw);
            idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
            idpw.print(" bucket=" + appUsageHistory.currentBucket
                    + " reason=" + appUsageHistory.bucketingReason);
+22 −8
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server.usage;

import static android.app.usage.UsageStatsManager.REASON_DEFAULT;
import static android.app.usage.UsageStatsManager.REASON_FORCED;
import static android.app.usage.UsageStatsManager.REASON_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_USAGE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
@@ -118,6 +119,9 @@ public class AppStandbyController {
            STANDBY_BUCKET_RARE
    };

    // Expiration time for predicted bucket
    private static final long PREDICTION_TIMEOUT = 12 * ONE_HOUR;

    // To name the lock for stack traces
    static class Lock {}

@@ -388,25 +392,27 @@ public class AppStandbyController {
                            STANDBY_BUCKET_EXEMPTED);
                } else {
                    synchronized (mAppIdleLock) {
                        String bucketingReason = mAppIdleHistory.getAppStandbyReason(packageName,
                        AppIdleHistory.AppUsageHistory app =
                                mAppIdleHistory.getAppUsageHistory(packageName,
                                userId, elapsedRealtime);
                        // If the bucket was forced by the developer, leave it alone
                        if (REASON_FORCED.equals(bucketingReason)) {
                        if (REASON_FORCED.equals(app.bucketingReason)) {
                            continue;
                        }
                        boolean predictionLate = false;
                        // If the bucket was moved up due to usage, let the timeouts apply.
                        if (REASON_DEFAULT.equals(bucketingReason)
                                || REASON_USAGE.equals(bucketingReason)
                                || REASON_TIMEOUT.equals(bucketingReason)) {
                            int oldBucket = mAppIdleHistory.getAppStandbyBucket(packageName, userId,
                                    elapsedRealtime);
                        if (REASON_DEFAULT.equals(app.bucketingReason)
                                || REASON_USAGE.equals(app.bucketingReason)
                                || REASON_TIMEOUT.equals(app.bucketingReason)
                                || (predictionLate = predictionTimedOut(app, elapsedRealtime))) {
                            int oldBucket = app.currentBucket;
                            int newBucket = getBucketForLocked(packageName, userId,
                                    elapsedRealtime);
                            if (DEBUG) {
                                Slog.d(TAG, "     Old bucket=" + oldBucket
                                        + ", newBucket=" + newBucket);
                            }
                            if (oldBucket < newBucket) {
                            if (oldBucket < newBucket || predictionLate) {
                                mAppIdleHistory.setAppStandbyBucket(packageName, userId,
                                        elapsedRealtime, newBucket, REASON_TIMEOUT);
                                maybeInformListeners(packageName, userId, elapsedRealtime,
@@ -424,6 +430,14 @@ public class AppStandbyController {
        return true;
    }

    private boolean predictionTimedOut(AppIdleHistory.AppUsageHistory app, long elapsedRealtime) {
        return app.bucketingReason != null
                && app.bucketingReason.startsWith(REASON_PREDICTED)
                && app.lastPredictedTime > 0
                && mAppIdleHistory.getElapsedTime(elapsedRealtime)
                    - app.lastPredictedTime > PREDICTION_TIMEOUT;
    }

    private void maybeInformListeners(String packageName, int userId,
            long elapsedRealtime, int bucket) {
        synchronized (mAppIdleLock) {