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

Commit 4d0d1dd6 authored by Kweku Adams's avatar Kweku Adams
Browse files

Revert "Revert "Add basic launch time prediction.""

This reverts commit 3785977c.

Reason for revert: Fixing

Change-Id: I31425d8086e3f1d1d806b84e9339d0acd3f420d4
parent 3785977c
Loading
Loading
Loading
Loading
+12 −1
Original line number Diff line number Diff line
package com.android.server.usage;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.usage.AppStandbyInfo;
@@ -71,6 +72,16 @@ public interface AppStandbyInternal {

    long getTimeSinceLastJobRun(String packageName, int userId);

    void setEstimatedLaunchTime(String packageName, int userId,
            @CurrentTimeMillisLong long launchTimeMs);

    /**
     * Returns the saved estimated launch time for the app. Will return {@code Long#MAX_VALUE} if no
     * value is saved.
     */
    @CurrentTimeMillisLong
    long getEstimatedLaunchTime(String packageName, int userId);

    /**
     * Returns the time (in milliseconds) since the app was last interacted with by the user.
     * This can be larger than the current elapsedRealtime, in case it happened before boot or
+120 −5
Original line number Diff line number Diff line
@@ -25,8 +25,12 @@ import static com.android.server.job.controllers.Package.packageToString;
import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.annotation.NonNull;
import android.app.usage.UsageStatsManagerInternal;
import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.ArraySet;
@@ -38,7 +42,9 @@ import android.util.TimeUtils;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.utils.AlarmQueue;

@@ -53,6 +59,9 @@ public class PrefetchController extends StateController {
            || Log.isLoggable(TAG, Log.DEBUG);

    private final PcConstants mPcConstants;
    private final PcHandler mHandler;

    private final UsageStatsManagerInternal mUsageStatsManagerInternal;

    @GuardedBy("mLock")
    private final SparseArrayMap<String, ArraySet<JobStatus>> mTrackedJobs = new SparseArrayMap<>();
@@ -72,11 +81,34 @@ public class PrefetchController extends StateController {
    @CurrentTimeMillisLong
    private long mLaunchTimeThresholdMs = PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS;

    @SuppressWarnings("FieldCanBeLocal")
    private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener =
            new EstimatedLaunchTimeChangedListener() {
                @Override
                public void onEstimatedLaunchTimeChanged(int userId, @NonNull String packageName,
                        @CurrentTimeMillisLong long newEstimatedLaunchTime) {
                    final SomeArgs args = SomeArgs.obtain();
                    args.arg1 = packageName;
                    args.argi1 = userId;
                    args.argl1 = newEstimatedLaunchTime;
                    mHandler.obtainMessage(MSG_PROCESS_UPDATED_ESTIMATED_LAUNCH_TIME, args)
                            .sendToTarget();
                }
            };

    private static final int MSG_RETRIEVE_ESTIMATED_LAUNCH_TIME = 0;
    private static final int MSG_PROCESS_UPDATED_ESTIMATED_LAUNCH_TIME = 1;

    public PrefetchController(JobSchedulerService service) {
        super(service);
        mPcConstants = new PcConstants();
        mHandler = new PcHandler(mContext.getMainLooper());
        mThresholdAlarmListener = new ThresholdAlarmListener(
                mContext, JobSchedulerBackgroundThread.get().getLooper());
        mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);

        mUsageStatsManagerInternal
                .registerLaunchTimeChangedListener(mEstimatedLaunchTimeChangedListener);
    }

    @Override
@@ -146,11 +178,14 @@ public class PrefetchController extends StateController {
    @CurrentTimeMillisLong
    private long getNextEstimatedLaunchTimeLocked(int userId, @NonNull String pkgName,
            @CurrentTimeMillisLong long now) {
        Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
        final Long nextEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
        if (nextEstimatedLaunchTime == null || nextEstimatedLaunchTime < now) {
            // TODO(194532703): get estimated time from UsageStats
            nextEstimatedLaunchTime = now + 2 * HOUR_IN_MILLIS;
            mEstimatedLaunchTimes.add(userId, pkgName, nextEstimatedLaunchTime);
            // Don't query usage stats here because it may have to read from disk.
            mHandler.obtainMessage(MSG_RETRIEVE_ESTIMATED_LAUNCH_TIME, userId, 0, pkgName)
                    .sendToTarget();
            // Store something in the cache so we don't keep posting retrieval messages.
            mEstimatedLaunchTimes.add(userId, pkgName, Long.MAX_VALUE);
            return Long.MAX_VALUE;
        }
        return nextEstimatedLaunchTime;
    }
@@ -170,6 +205,42 @@ public class PrefetchController extends StateController {
        return changed;
    }

    private void processUpdatedEstimatedLaunchTime(int userId, @NonNull String pkgName,
            @CurrentTimeMillisLong long newEstimatedLaunchTime) {
        if (DEBUG) {
            Slog.d(TAG, "Estimated launch time for " + packageToString(userId, pkgName)
                    + " changed to " + newEstimatedLaunchTime
                    + " ("
                    + TimeUtils.formatDuration(newEstimatedLaunchTime - sSystemClock.millis())
                    + " from now)");
        }

        synchronized (mLock) {
            final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
            if (jobs == null) {
                if (DEBUG) {
                    Slog.i(TAG,
                            "Not caching launch time since we haven't seen any prefetch"
                                    + " jobs for " + packageToString(userId, pkgName));
                }
            } else {
                // Don't bother caching the value unless the app has scheduled prefetch jobs
                // before. This is based on the assumption that if an app has scheduled a
                // prefetch job before, then it will probably schedule another one again.
                mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime);

                if (!jobs.isEmpty()) {
                    final long now = sSystemClock.millis();
                    final long nowElapsed = sElapsedRealtimeClock.millis();
                    updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
                    if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) {
                        mStateChangedListener.onControllerStateChanged(jobs);
                    }
                }
            }
        }
    }

    @GuardedBy("mLock")
    private boolean updateConstraintLocked(@NonNull JobStatus jobStatus,
            @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
@@ -289,6 +360,49 @@ public class PrefetchController extends StateController {
        }
    }

    private class PcHandler extends Handler {
        PcHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_RETRIEVE_ESTIMATED_LAUNCH_TIME:
                    final int userId = msg.arg1;
                    final String pkgName = (String) msg.obj;
                    // It's okay to get the time without holding the lock since all updates to
                    // the local cache go through the handler (and therefore will be sequential).
                    final long nextEstimatedLaunchTime = mUsageStatsManagerInternal
                            .getEstimatedPackageLaunchTime(pkgName, userId);
                    if (DEBUG) {
                        Slog.d(TAG, "Retrieved launch time for "
                                + packageToString(userId, pkgName)
                                + " of " + nextEstimatedLaunchTime
                                + " (" + TimeUtils.formatDuration(
                                        nextEstimatedLaunchTime - sSystemClock.millis())
                                + " from now)");
                    }
                    synchronized (mLock) {
                        final Long curEstimatedLaunchTime =
                                mEstimatedLaunchTimes.get(userId, pkgName);
                        if (curEstimatedLaunchTime == null
                                || nextEstimatedLaunchTime != curEstimatedLaunchTime) {
                            processUpdatedEstimatedLaunchTime(
                                    userId, pkgName, nextEstimatedLaunchTime);
                        }
                    }
                    break;

                case MSG_PROCESS_UPDATED_ESTIMATED_LAUNCH_TIME:
                    final SomeArgs args = (SomeArgs) msg.obj;
                    processUpdatedEstimatedLaunchTime(args.argi1, (String) args.arg1, args.argl1);
                    args.recycle();
                    break;
            }
        }
    }

    @VisibleForTesting
    class PcConstants {
        private boolean mShouldReevaluateConstraints = false;
@@ -366,7 +480,8 @@ public class PrefetchController extends StateController {
                final String pkgName = mEstimatedLaunchTimes.keyAt(u, p);
                final long estimatedLaunchTime = mEstimatedLaunchTimes.valueAt(u, p);

                pw.print("<" + userId + ">" + pkgName + ": ");
                pw.print(packageToString(userId, pkgName));
                pw.print(": ");
                pw.print(estimatedLaunchTime);
                pw.print(" (");
                TimeUtils.formatDuration(estimatedLaunchTime - now, pw,
+46 −0
Original line number Diff line number Diff line
@@ -32,6 +32,8 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;

import static com.android.server.usage.AppStandbyController.isUserUsage;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.ElapsedRealtimeLong;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageStatsManager;
import android.os.SystemClock;
@@ -115,6 +117,8 @@ public class AppIdleHistory {
    // Reason why the app was last marked for restriction.
    private static final String ATTR_LAST_RESTRICTION_ATTEMPT_REASON =
            "lastRestrictionAttemptReason";
    // The next estimated launch time of the app, in ms since epoch.
    private static final String ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME = "nextEstimatedAppLaunchTime";

    // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
    private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
@@ -151,6 +155,9 @@ public class AppIdleHistory {
        int lastInformedBucket;
        // The last time a job was run for this app, using elapsed timebase
        long lastJobRunTime;
        // The estimated time the app will be launched next, in milliseconds since epoch.
        @CurrentTimeMillisLong
        long nextEstimatedLaunchTime;
        // When should the bucket active state timeout, in elapsed timebase, if greater than
        // lastUsedElapsedTime.
        // This is used to keep the app in a high bucket regardless of other timeouts and
@@ -410,6 +417,17 @@ public class AppIdleHistory {
        app.lastPredictedBucket = bucket;
    }

    /**
     * Marks the next time the app is expected to be launched, in the current millis timebase.
     */
    public void setEstimatedLaunchTime(String packageName, int userId,
            @ElapsedRealtimeLong long nowElapsed, @CurrentTimeMillisLong long launchTime) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory =
                getPackageHistory(userHistory, packageName, nowElapsed, true);
        appUsageHistory.nextEstimatedLaunchTime = launchTime;
    }

    /**
     * Marks the last time a job was run, with the given elapsedRealtime. The time stored is
     * based on the elapsed timebase.
@@ -442,6 +460,23 @@ public class AppIdleHistory {
        appUsageHistory.lastRestrictReason = reason;
    }

    /**
     * Returns the next estimated launch time of this app. Will return {@link Long#MAX_VALUE} if
     * there's no estimated time.
     */
    @CurrentTimeMillisLong
    public long getEstimatedLaunchTime(String packageName, int userId, long nowElapsed) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory =
                getPackageHistory(userHistory, packageName, nowElapsed, false);
        // Don't adjust the default, else it'll wrap around to a positive value
        if (appUsageHistory == null
                || appUsageHistory.nextEstimatedLaunchTime < System.currentTimeMillis()) {
            return Long.MAX_VALUE;
        }
        return appUsageHistory.nextEstimatedLaunchTime;
    }

    /**
     * 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
@@ -671,6 +706,8 @@ public class AppIdleHistory {
                                Slog.wtf(TAG, "Unable to read last restrict reason", nfe);
                            }
                        }
                        appUsageHistory.nextEstimatedLaunchTime = getLongValue(parser,
                                ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME, 0);
                        appUsageHistory.lastInformedBucket = -1;
                        userHistory.put(packageName, appUsageHistory);
                    }
@@ -753,6 +790,10 @@ public class AppIdleHistory {
                }
                xml.attribute(null, ATTR_LAST_RESTRICTION_ATTEMPT_REASON,
                        Integer.toHexString(history.lastRestrictReason));
                if (history.nextEstimatedLaunchTime > 0) {
                    xml.attribute(null, ATTR_NEXT_ESTIMATED_APP_LAUNCH_TIME,
                            Long.toString(history.nextEstimatedLaunchTime));
                }
                xml.endTag(null, TAG_PACKAGE);
            }

@@ -779,6 +820,7 @@ public class AppIdleHistory {
        idpw.println(" App Standby States:");
        idpw.increaseIndent();
        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
        final long now = System.currentTimeMillis();
        final long elapsedRealtime = SystemClock.elapsedRealtime();
        final long totalElapsedTime = getElapsedTime(elapsedRealtime);
        final long screenOnTime = getScreenOnTime(elapsedRealtime);
@@ -819,6 +861,10 @@ public class AppIdleHistory {
                idpw.print(" lastRestrictReason="
                        + UsageStatsManager.reasonToString(appUsageHistory.lastRestrictReason));
            }
            if (appUsageHistory.nextEstimatedLaunchTime > 0) {
                idpw.print(" nextEstimatedLaunchTime=");
                TimeUtils.formatDuration(appUsageHistory.nextEstimatedLaunchTime - now, idpw);
            }
            idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
            idpw.println();
        }
+19 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -1086,6 +1087,24 @@ public class AppStandbyController
        }
    }

    @Override
    public void setEstimatedLaunchTime(String packageName, int userId,
            @CurrentTimeMillisLong long launchTime) {
        final long nowElapsed = mInjector.elapsedRealtime();
        synchronized (mAppIdleLock) {
            mAppIdleHistory.setEstimatedLaunchTime(packageName, userId, nowElapsed, launchTime);
        }
    }

    @Override
    @CurrentTimeMillisLong
    public long getEstimatedLaunchTime(String packageName, int userId) {
        final long elapsedRealtime = mInjector.elapsedRealtime();
        synchronized (mAppIdleLock) {
            return mAppIdleHistory.getEstimatedLaunchTime(packageName, userId, elapsedRealtime);
        }
    }

    @Override
    public long getTimeSinceLastUsedByUser(String packageName, int userId) {
        final long elapsedRealtime = mInjector.elapsedRealtime();
+5 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package android.app.usage;

import android.annotation.CurrentTimeMillisLong;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -584,6 +585,7 @@ public final class UsageEvents implements Parcelable {
         * <p/>
         * See {@link System#currentTimeMillis()}.
         */
        @CurrentTimeMillisLong
        public long getTimeStamp() {
            return mTimeStamp;
        }
@@ -801,6 +803,9 @@ public final class UsageEvents implements Parcelable {
     * @return true if an event was available, false if there are no more events.
     */
    public boolean getNextEvent(Event eventOut) {
        if (eventOut == null) {
            throw new IllegalArgumentException("Given eventOut must not be null");
        }
        if (mIndex >= mEventCount) {
            return false;
        }
Loading