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

Commit 13d30d9a authored by Alex Bianchi's avatar Alex Bianchi Committed by Android (Google) Code Review
Browse files

Merge "Handle Prefetch Constraints with Flexibility Controller"

parents de3da8b9 7e5df32e
Loading
Loading
Loading
Loading
+4 −3
Original line number Original line Diff line number Diff line
@@ -1556,7 +1556,10 @@ public class JobSchedulerService extends com.android.server.SystemService


        // Create the controllers.
        // Create the controllers.
        mControllers = new ArrayList<StateController>();
        mControllers = new ArrayList<StateController>();
        final FlexibilityController flexibilityController = new FlexibilityController(this);
        mPrefetchController = new PrefetchController(this);
        mControllers.add(mPrefetchController);
        final FlexibilityController flexibilityController =
                new FlexibilityController(this, mPrefetchController);
        mControllers.add(flexibilityController);
        mControllers.add(flexibilityController);
        final ConnectivityController connectivityController =
        final ConnectivityController connectivityController =
                new ConnectivityController(this, flexibilityController);
                new ConnectivityController(this, flexibilityController);
@@ -1575,8 +1578,6 @@ public class JobSchedulerService extends com.android.server.SystemService
        mControllers.add(new ContentObserverController(this));
        mControllers.add(new ContentObserverController(this));
        mDeviceIdleJobsController = new DeviceIdleJobsController(this);
        mDeviceIdleJobsController = new DeviceIdleJobsController(this);
        mControllers.add(mDeviceIdleJobsController);
        mControllers.add(mDeviceIdleJobsController);
        mPrefetchController = new PrefetchController(this);
        mControllers.add(mPrefetchController);
        mQuotaController =
        mQuotaController =
                new QuotaController(this, backgroundJobsController, connectivityController);
                new QuotaController(this, backgroundJobsController, connectivityController);
        mControllers.add(mQuotaController);
        mControllers.add(mQuotaController);
+204 −21
Original line number Original line Diff line number Diff line
@@ -23,6 +23,7 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;
import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE;


import android.annotation.ElapsedRealtimeLong;
import android.annotation.ElapsedRealtimeLong;
@@ -36,6 +37,7 @@ import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import android.util.Slog;
import android.util.SparseArrayMap;


import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
@@ -50,8 +52,6 @@ import java.util.function.Predicate;
/**
/**
 * Controller that tracks the number of flexible constraints being actively satisfied.
 * Controller that tracks the number of flexible constraints being actively satisfied.
 * Drops constraint for TOP apps and lowers number of required constraints with time.
 * Drops constraint for TOP apps and lowers number of required constraints with time.
 *
 * TODO(b/238887951): handle prefetch
 */
 */
public final class FlexibilityController extends StateController {
public final class FlexibilityController extends StateController {
    private static final String TAG = "JobScheduler.Flexibility";
    private static final String TAG = "JobScheduler.Flexibility";
@@ -78,6 +78,8 @@ public final class FlexibilityController extends StateController {
    @VisibleForTesting
    @VisibleForTesting
    static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);
    static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS);


    private static final long NO_LIFECYCLE_END = Long.MAX_VALUE;

    /** Hard cutoff to remove flexible constraints. */
    /** Hard cutoff to remove flexible constraints. */
    private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
    private static final long DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;


@@ -85,7 +87,8 @@ public final class FlexibilityController extends StateController {
     * The default deadline that all flexible constraints should be dropped by if a job lacks
     * The default deadline that all flexible constraints should be dropped by if a job lacks
     * a deadline.
     * a deadline.
     */
     */
    private static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS;
    @VisibleForTesting
    static final long DEFAULT_FLEXIBILITY_DEADLINE = 72 * HOUR_IN_MILLIS;


    /**
    /**
     * Keeps track of what flexible constraints are satisfied at the moment.
     * Keeps track of what flexible constraints are satisfied at the moment.
@@ -102,6 +105,10 @@ public final class FlexibilityController extends StateController {
    final FlexibilityTracker mFlexibilityTracker;
    final FlexibilityTracker mFlexibilityTracker;
    private final FcConstants mFcConstants;
    private final FcConstants mFcConstants;


    @VisibleForTesting
    final PrefetchController mPrefetchController;

    @GuardedBy("mLock")
    private final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
    private final FlexibilityAlarmQueue mFlexibilityAlarmQueue;
    private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS;
    private static final long MIN_TIME_BETWEEN_ALARMS_MS = MINUTE_IN_MILLIS;


@@ -112,12 +119,58 @@ public final class FlexibilityController extends StateController {
     */
     */
    private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80};
    private static final int[] PERCENT_TO_DROP_CONSTRAINTS = {50, 60, 70, 80};


    public FlexibilityController(JobSchedulerService service) {
    /**
     * Stores the beginning of prefetch jobs lifecycle per app as a maximum of
     * the last time the app was used and the last time the launch time was updated.
     */
    @VisibleForTesting
    @GuardedBy("mLock")
    final SparseArrayMap<String, Long> mPrefetchLifeCycleStart = new SparseArrayMap<>();

    @VisibleForTesting
    final PrefetchController.PrefetchChangedListener mPrefetchChangedListener =
            new PrefetchController.PrefetchChangedListener() {
                @Override
                public void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId,
                        String pkgName, long prevEstimatedLaunchTime, long newEstimatedLaunchTime) {
                    synchronized (mLock) {
                        final long nowElapsed = sElapsedRealtimeClock.millis();
                        final long prefetchThreshold =
                                mPrefetchController.getLaunchTimeThresholdMs();
                        boolean jobWasInPrefetchWindow  = prevEstimatedLaunchTime
                                - prefetchThreshold < nowElapsed;
                        boolean jobIsInPrefetchWindow  = newEstimatedLaunchTime
                                - prefetchThreshold < nowElapsed;
                        if (jobIsInPrefetchWindow != jobWasInPrefetchWindow) {
                            // If the job was in the window previously then changing the start
                            // of the lifecycle to the current moment without a large change in the
                            // end would squeeze the window too tight fail to drop constraints.
                            mPrefetchLifeCycleStart.add(userId, pkgName, Math.max(nowElapsed,
                                    mPrefetchLifeCycleStart.getOrDefault(userId, pkgName, 0L)));
                        }
                        for (int i = 0; i < jobs.size(); i++) {
                            JobStatus js = jobs.valueAt(i);
                            if (!js.hasFlexibilityConstraint()) {
                                continue;
                            }
                            mFlexibilityTracker.resetJobNumDroppedConstraints(js);
                            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
                        }
                    }
                }
            };

    public FlexibilityController(
            JobSchedulerService service, PrefetchController prefetchController) {
        super(service);
        super(service);
        mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
        mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
        mFcConstants = new FcConstants();
        mFcConstants = new FcConstants();
        mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
        mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
                mContext, JobSchedulerBackgroundThread.get().getLooper());
                mContext, JobSchedulerBackgroundThread.get().getLooper());
        mPrefetchController = prefetchController;
        if (mFlexibilityEnabled) {
            mPrefetchController.registerPrefetchChangedListener(mPrefetchChangedListener);
        }
    }
    }


    /**
    /**
@@ -131,7 +184,7 @@ public final class FlexibilityController extends StateController {
            js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
            js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY);
            final long nowElapsed = sElapsedRealtimeClock.millis();
            final long nowElapsed = sElapsedRealtimeClock.millis();
            js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
            js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
            mFlexibilityAlarmQueue.addAlarm(js, getNextConstraintDropTimeElapsed(js));
            mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js);
        }
        }
    }
    }


@@ -144,6 +197,19 @@ public final class FlexibilityController extends StateController {
        }
        }
    }
    }


    @Override
    @GuardedBy("mLock")
    public void onAppRemovedLocked(String packageName, int uid) {
        final int userId = UserHandle.getUserId(uid);
        mPrefetchLifeCycleStart.delete(userId, packageName);
    }

    @Override
    @GuardedBy("mLock")
    public void onUserRemovedLocked(int userId) {
        mPrefetchLifeCycleStart.delete(userId);
    }

    /** Checks if the flexibility constraint is actively satisfied for a given job. */
    /** Checks if the flexibility constraint is actively satisfied for a given job. */
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    boolean isFlexibilitySatisfiedLocked(JobStatus js) {
    boolean isFlexibilitySatisfiedLocked(JobStatus js) {
@@ -165,6 +231,7 @@ public final class FlexibilityController extends StateController {
     * Sets the controller's constraint to a given state.
     * Sets the controller's constraint to a given state.
     * Changes flexibility constraint satisfaction for affected jobs.
     * Changes flexibility constraint satisfaction for affected jobs.
     */
     */
    @VisibleForTesting
    void setConstraintSatisfied(int constraint, boolean state) {
    void setConstraintSatisfied(int constraint, boolean state) {
        synchronized (mLock) {
        synchronized (mLock) {
            final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
            final boolean old = (mSatisfiedFlexibleConstraints & constraint) != 0;
@@ -187,21 +254,20 @@ public final class FlexibilityController extends StateController {
            // of satisfied system-wide constraints and iterate to the max number of potentially
            // of satisfied system-wide constraints and iterate to the max number of potentially
            // satisfied constraints, determined by how many job-specific constraints exist.
            // satisfied constraints, determined by how many job-specific constraints exist.
            for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) {
            for (int j = 0; j <= NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS; j++) {
                final ArraySet<JobStatus> jobs = mFlexibilityTracker
                final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker
                        .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j);
                        .getJobsByNumRequiredConstraints(numConstraintsToUpdate + j);


                if (jobs == null) {
                if (jobsByNumConstraints == null) {
                    // If there are no more jobs to iterate through we can just return.
                    // If there are no more jobs to iterate through we can just return.
                    return;
                    return;
                }
                }


                for (int i = 0; i < jobs.size(); i++) {
                for (int i = 0; i < jobsByNumConstraints.size(); i++) {
                    JobStatus js = jobs.valueAt(i);
                    JobStatus js = jobsByNumConstraints.valueAt(i);
                    js.setFlexibilityConstraintSatisfied(
                    js.setFlexibilityConstraintSatisfied(
                            nowElapsed, isFlexibilitySatisfiedLocked(js));
                            nowElapsed, isFlexibilitySatisfiedLocked(js));
                }
                }
            }
            }

        }
        }
    }
    }


@@ -211,15 +277,77 @@ public final class FlexibilityController extends StateController {
        return (mSatisfiedFlexibleConstraints & constraint) != 0;
        return (mSatisfiedFlexibleConstraints & constraint) != 0;
    }
    }


    /** The elapsed time that marks when the next constraint should be dropped. */
    @VisibleForTesting
    @VisibleForTesting
    @ElapsedRealtimeLong
    @GuardedBy("mLock")
    long getNextConstraintDropTimeElapsed(JobStatus js) {
    long getLifeCycleBeginningElapsedLocked(JobStatus js) {
        final long earliest = js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
        if (js.getJob().isPrefetch()) {
            final long earliestRuntime = Math.max(js.enqueueTime, js.getEarliestRunTime());
            final long estimatedLaunchTime =
                    mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
            long prefetchWindowStart = mPrefetchLifeCycleStart.getOrDefault(
                    js.getSourceUserId(), js.getSourcePackageName(), 0L);
            if (estimatedLaunchTime != Long.MAX_VALUE) {
                prefetchWindowStart = Math.max(prefetchWindowStart,
                        estimatedLaunchTime - mPrefetchController.getLaunchTimeThresholdMs());
            }
            return Math.max(prefetchWindowStart, earliestRuntime);
        }
        return js.getEarliestRunTime() == JobStatus.NO_EARLIEST_RUNTIME
                ? js.enqueueTime : js.getEarliestRunTime();
                ? js.enqueueTime : js.getEarliestRunTime();
        final long latest = js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
    }

    @VisibleForTesting
    @GuardedBy("mLock")
    long getLifeCycleEndElapsedLocked(JobStatus js, long earliest) {
        if (js.getJob().isPrefetch()) {
            final long estimatedLaunchTime =
                    mPrefetchController.getNextEstimatedLaunchTimeLocked(js);
            // Prefetch jobs aren't supposed to have deadlines after T.
            // But some legacy apps might still schedule them with deadlines.
            if (js.getLatestRunTimeElapsed() != JobStatus.NO_LATEST_RUNTIME) {
                // If there is a deadline, the earliest time is the end of the lifecycle.
                return Math.min(
                        estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
                        js.getLatestRunTimeElapsed());
            }
            if (estimatedLaunchTime != Long.MAX_VALUE) {
                return estimatedLaunchTime - mConstants.PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS;
            }
            // There is no deadline and no estimated launch time.
            return NO_LIFECYCLE_END;
        }
        return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
                ? earliest + DEFAULT_FLEXIBILITY_DEADLINE
                ? earliest + DEFAULT_FLEXIBILITY_DEADLINE
                : js.getLatestRunTimeElapsed();
                : js.getLatestRunTimeElapsed();
    }

    @VisibleForTesting
    @GuardedBy("mLock")
    int getCurPercentOfLifecycleLocked(JobStatus js) {
        final long earliest = getLifeCycleBeginningElapsedLocked(js);
        final long latest = getLifeCycleEndElapsedLocked(js, earliest);
        final long nowElapsed = sElapsedRealtimeClock.millis();
        if (latest == NO_LIFECYCLE_END || earliest > nowElapsed) {
            return 0;
        }
        if (nowElapsed > latest || latest == earliest) {
            return 100;
        }
        final int percentInTime = (int) ((nowElapsed - earliest) * 100 / (latest - earliest));
        return percentInTime;
    }

    /** The elapsed time that marks when the next constraint should be dropped. */
    @VisibleForTesting
    @ElapsedRealtimeLong
    @GuardedBy("mLock")
    long getNextConstraintDropTimeElapsedLocked(JobStatus js) {
        final long earliest = getLifeCycleBeginningElapsedLocked(js);
        final long latest = getLifeCycleEndElapsedLocked(js, earliest);
        if (latest == NO_LIFECYCLE_END
                || js.getNumDroppedFlexibleConstraints() == PERCENT_TO_DROP_CONSTRAINTS.length) {
            return NO_LIFECYCLE_END;
        }
        final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()];
        final int percent = PERCENT_TO_DROP_CONSTRAINTS[js.getNumDroppedFlexibleConstraints()];
        final long percentInTime = ((latest - earliest) * percent) / 100;
        final long percentInTime = ((latest - earliest) * percent) / 100;
        return earliest + percentInTime;
        return earliest + percentInTime;
@@ -233,10 +361,28 @@ public final class FlexibilityController extends StateController {
        }
        }
        final long nowElapsed = sElapsedRealtimeClock.millis();
        final long nowElapsed = sElapsedRealtimeClock.millis();
        List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid);
        List<JobStatus> jobsByUid = mService.getJobStore().getJobsByUid(uid);
        boolean hasPrefetch = false;
        for (int i = 0; i < jobsByUid.size(); i++) {
        for (int i = 0; i < jobsByUid.size(); i++) {
            JobStatus js = jobsByUid.get(i);
            JobStatus js = jobsByUid.get(i);
            if (js.hasFlexibilityConstraint()) {
            if (js.hasFlexibilityConstraint()) {
                js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
                js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js));
                hasPrefetch |= js.getJob().isPrefetch();
            }
        }

        // Prefetch jobs can't run when the app is TOP, so it should not be included in their
        // lifecycle, and marks the beginning of a new lifecycle.
        if (hasPrefetch && prevBias == JobInfo.BIAS_TOP_APP) {
            final int userId = UserHandle.getUserId(uid);
            final ArraySet<String> pkgs = mService.getPackagesForUidLocked(uid);
            if (pkgs == null) {
                return;
            }
            for (int i = 0; i < pkgs.size(); i++) {
                String pkg = pkgs.valueAt(i);
                mPrefetchLifeCycleStart.add(userId, pkg,
                        Math.max(mPrefetchLifeCycleStart.getOrDefault(userId, pkg, 0L),
                                nowElapsed));
            }
            }
        }
        }
    }
    }
@@ -281,7 +427,7 @@ public final class FlexibilityController extends StateController {


        FlexibilityTracker(int numFlexibleConstraints) {
        FlexibilityTracker(int numFlexibleConstraints) {
            mTrackedJobs = new ArrayList<>();
            mTrackedJobs = new ArrayList<>();
            for (int i = 0; i <= numFlexibleConstraints; i++) {
            for (int i = 0; i < numFlexibleConstraints; i++) {
                mTrackedJobs.add(new ArraySet<JobStatus>());
                mTrackedJobs.add(new ArraySet<JobStatus>());
            }
            }
        }
        }
@@ -312,6 +458,17 @@ public final class FlexibilityController extends StateController {
            mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js);
            mTrackedJobs.get(js.getNumRequiredFlexibleConstraints() - 1).remove(js);
        }
        }


        public void resetJobNumDroppedConstraints(JobStatus js) {
            final int curPercent = getCurPercentOfLifecycleLocked(js);
            int toDrop = 0;
            for (int i = 0; i < PERCENT_TO_DROP_CONSTRAINTS.length; i++) {
                if (curPercent >= PERCENT_TO_DROP_CONSTRAINTS[i]) {
                    toDrop++;
                }
            }
            adjustJobsRequiredConstraints(js, js.getNumDroppedFlexibleConstraints() - toDrop);
        }

        /** Returns all tracked jobs. */
        /** Returns all tracked jobs. */
        public ArrayList<ArraySet<JobStatus>> getArrayList() {
        public ArrayList<ArraySet<JobStatus>> getArrayList() {
            return mTrackedJobs;
            return mTrackedJobs;
@@ -369,20 +526,39 @@ public final class FlexibilityController extends StateController {
            return js.getSourceUserId() == userId;
            return js.getSourceUserId() == userId;
        }
        }


        protected void scheduleDropNumConstraintsAlarm(JobStatus js) {
            long nextTimeElapsed;
            synchronized (mLock) {
                nextTimeElapsed = getNextConstraintDropTimeElapsedLocked(js);
                if (nextTimeElapsed == NO_LIFECYCLE_END) {
                    // There is no known or estimated next time to drop a constraint.
                    removeAlarmForKey(js);
                    return;
                }
                mFlexibilityAlarmQueue.addAlarm(js, nextTimeElapsed);
            }
        }

        @Override
        @Override
        protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) {
        protected void processExpiredAlarms(@NonNull ArraySet<JobStatus> expired) {
            synchronized (mLock) {
            synchronized (mLock) {
                JobStatus js;
                ArraySet<JobStatus> changedJobs = new ArraySet<>();
                for (int i = 0; i < expired.size(); i++) {
                for (int i = 0; i < expired.size(); i++) {
                    js = expired.valueAt(i);
                    JobStatus js = expired.valueAt(i);
                    long time = getNextConstraintDropTimeElapsed(js);
                    boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE);
                    long time = getNextConstraintDropTimeElapsedLocked(js);
                    int toDecrease =
                    int toDecrease =
                            js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS
                            js.getLatestRunTimeElapsed() - time < DEADLINE_PROXIMITY_LIMIT_MS
                                    ? -js.getNumRequiredFlexibleConstraints() : -1;
                                    ? -js.getNumRequiredFlexibleConstraints() : -1;
                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)) {
                    if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, toDecrease)
                            && time != NO_LIFECYCLE_END) {
                        mFlexibilityAlarmQueue.addAlarm(js, time);
                        mFlexibilityAlarmQueue.addAlarm(js, time);
                    }
                    }
                    if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) {
                        changedJobs.add(js);
                    }
                }
                }
                mStateChangedListener.onControllerStateChanged(changedJobs);
            }
            }
        }
        }
    }
    }
@@ -410,6 +586,13 @@ public final class FlexibilityController extends StateController {
                    if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) {
                    if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) {
                        mFlexibilityEnabled = FLEXIBILITY_ENABLED;
                        mFlexibilityEnabled = FLEXIBILITY_ENABLED;
                        mShouldReevaluateConstraints = true;
                        mShouldReevaluateConstraints = true;
                        if (mFlexibilityEnabled) {
                            mPrefetchController
                                    .registerPrefetchChangedListener(mPrefetchChangedListener);
                        } else {
                            mPrefetchController
                                    .unRegisterPrefetchChangedListener(mPrefetchChangedListener);
                        }
                    }
                    }
                    break;
                    break;
            }
            }
+0 −1
Original line number Original line Diff line number Diff line
@@ -574,7 +574,6 @@ public final class JobStatus {


        if (!isRequestedExpeditedJob()
        if (!isRequestedExpeditedJob()
                && satisfiesMinWindowException
                && satisfiesMinWindowException
                && !job.isPrefetch()
                && lacksSomeFlexibleConstraints) {
                && lacksSomeFlexibleConstraints) {
            mNumRequiredFlexibleConstraints =
            mNumRequiredFlexibleConstraints =
                    NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
                    NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
+26 −0
Original line number Original line Diff line number Diff line
@@ -81,6 +81,8 @@ public class PrefetchController extends StateController {
     */
     */
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
    private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
    @GuardedBy("mLock")
    private final ArraySet<PrefetchChangedListener> mPrefetchChangedListeners = new ArraySet<>();
    private final ThresholdAlarmListener mThresholdAlarmListener;
    private final ThresholdAlarmListener mThresholdAlarmListener;


    /**
    /**
@@ -99,6 +101,13 @@ public class PrefetchController extends StateController {
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private long mLaunchTimeAllowanceMs = PcConstants.DEFAULT_LAUNCH_TIME_ALLOWANCE_MS;
    private long mLaunchTimeAllowanceMs = PcConstants.DEFAULT_LAUNCH_TIME_ALLOWANCE_MS;


    /** Called by Prefetch Controller after local cache has been updated */
    public interface PrefetchChangedListener {
        /** Callback to inform listeners when estimated launch times change. */
        void onPrefetchCacheUpdated(ArraySet<JobStatus> jobs, int userId, String pkgName,
                long prevEstimatedLaunchTime, long newEstimatedLaunchTime);
    }

    @SuppressWarnings("FieldCanBeLocal")
    @SuppressWarnings("FieldCanBeLocal")
    private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener =
    private final EstimatedLaunchTimeChangedListener mEstimatedLaunchTimeChangedListener =
            new EstimatedLaunchTimeChangedListener() {
            new EstimatedLaunchTimeChangedListener() {
@@ -291,12 +300,17 @@ public class PrefetchController extends StateController {
                // Don't bother caching the value unless the app has scheduled prefetch jobs
                // 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
                // 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.
                // prefetch job before, then it will probably schedule another one again.
                final long prevEstimatedLaunchTime = mEstimatedLaunchTimes.get(userId, pkgName);
                mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime);
                mEstimatedLaunchTimes.add(userId, pkgName, newEstimatedLaunchTime);


                if (!jobs.isEmpty()) {
                if (!jobs.isEmpty()) {
                    final long now = sSystemClock.millis();
                    final long now = sSystemClock.millis();
                    final long nowElapsed = sElapsedRealtimeClock.millis();
                    final long nowElapsed = sElapsedRealtimeClock.millis();
                    updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
                    updateThresholdAlarmLocked(userId, pkgName, now, nowElapsed);
                    for (int i = 0; i < mPrefetchChangedListeners.size(); i++) {
                        mPrefetchChangedListeners.valueAt(i).onPrefetchCacheUpdated(jobs,
                                userId, pkgName, prevEstimatedLaunchTime, newEstimatedLaunchTime);
                    }
                    if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) {
                    if (maybeUpdateConstraintForPkgLocked(now, nowElapsed, userId, pkgName)) {
                        mStateChangedListener.onControllerStateChanged(jobs);
                        mStateChangedListener.onControllerStateChanged(jobs);
                    }
                    }
@@ -448,6 +462,18 @@ public class PrefetchController extends StateController {
        }
        }
    }
    }


    void registerPrefetchChangedListener(PrefetchChangedListener listener) {
        synchronized (mLock) {
            mPrefetchChangedListeners.add(listener);
        }
    }

    void unRegisterPrefetchChangedListener(PrefetchChangedListener listener) {
        synchronized (mLock) {
            mPrefetchChangedListeners.remove(listener);
        }
    }

    private class PcHandler extends Handler {
    private class PcHandler extends Handler {
        PcHandler(Looper looper) {
        PcHandler(Looper looper) {
            super(looper);
            super(looper);
+3 −1
Original line number Original line Diff line number Diff line
@@ -100,7 +100,9 @@ public class BatteryControllerTest {
        // Capture the listeners.
        // Capture the listeners.
        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
                ArgumentCaptor.forClass(BroadcastReceiver.class);
                ArgumentCaptor.forClass(BroadcastReceiver.class);
        mFlexibilityController = new FlexibilityController(mJobSchedulerService);

        mFlexibilityController =
                new FlexibilityController(mJobSchedulerService, mock(PrefetchController.class));
        mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);
        mBatteryController = new BatteryController(mJobSchedulerService, mFlexibilityController);


        verify(mContext).registerReceiver(receiverCaptor.capture(),
        verify(mContext).registerReceiver(receiverCaptor.capture(),
Loading