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

Commit 9bbddb73 authored by Kweku Adams's avatar Kweku Adams
Browse files

Satisfy charging constraint earlier for top apps.

Several releases ago, we delayed the CHARGING broadcast to ~15 minutes
after the device was plugged in. This means that any job that specifies
a charging constraint won't have the constraint satisfied until 15 minutes
after the device is plugged in. If such a job's setting is explicitly set
by the user and the user plugs in their device while the app is on top,
they will see that the work doesn't start immediately. This would be
confusing for the user. We now the top app's charging constraint jobs
as satisfied when the device is plugged in to avoid this confusion. If
any TOP app's charging constraint jobs start while the app is on top,
the job will be allowed to continue running after the user leaves the
app as long as the device is plugged in. Any jobs that didn't start will
be treated regularly and will have to wait for the CHARGING broadcast.

Bug: 198624541
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job
Test: atest CtsJobSchedulerTestCases
Change-Id: I914176d7bf9fd4a8998c05d411483bd4607589f2
parent 16e5a4a4
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -1556,7 +1556,7 @@ public class JobSchedulerService extends com.android.server.SystemService
                    Slog.d(TAG, "UID " + uid + " bias changed from " + prevBias + " to " + newBias);
                }
                for (int c = 0; c < mControllers.size(); ++c) {
                    mControllers.get(c).onUidBiasChangedLocked(uid, newBias);
                    mControllers.get(c).onUidBiasChangedLocked(uid, prevBias, newBias);
                }
            }
        }
+154 −8
Original line number Diff line number Diff line
@@ -18,6 +18,14 @@ package com.android.server.job.controllers;

import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;

import android.annotation.NonNull;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -27,6 +35,7 @@ import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateControllerProto;

@@ -42,10 +51,26 @@ public final class BatteryController extends RestrictingController {
    private static final boolean DEBUG = JobSchedulerService.DEBUG
            || Log.isLoggable(TAG, Log.DEBUG);

    @GuardedBy("mLock")
    private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
    /**
     * List of jobs that started while the UID was in the TOP state.
     */
    @GuardedBy("mLock")
    private final ArraySet<JobStatus> mTopStartedJobs = new ArraySet<>();

    private final PowerTracker mPowerTracker;

    /**
     * Helper set to avoid too much GC churn from frequent calls to
     * {@link #maybeReportNewChargingStateLocked()}.
     */
    private final ArraySet<JobStatus> mChangedJobs = new ArraySet<>();

    public BatteryController(JobSchedulerService service) {
        super(service);
        mPowerTracker = new PowerTracker();
        mPowerTracker.startTracking();
    }

    @Override
@@ -54,8 +79,15 @@ public final class BatteryController extends RestrictingController {
            final long nowElapsed = sElapsedRealtimeClock.millis();
            mTrackedTasks.add(taskStatus);
            taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
            if (taskStatus.hasChargingConstraint()) {
                if (hasTopExemptionLocked(taskStatus)) {
                    taskStatus.setChargingConstraintSatisfied(nowElapsed,
                            mPowerTracker.isPowerConnected());
                } else {
                    taskStatus.setChargingConstraintSatisfied(nowElapsed,
                            mService.isBatteryCharging() && mService.isBatteryNotLow());
                }
            }
            taskStatus.setBatteryNotLowConstraintSatisfied(nowElapsed, mService.isBatteryNotLow());
        }
    }
@@ -65,10 +97,33 @@ public final class BatteryController extends RestrictingController {
        maybeStartTrackingJobLocked(jobStatus, null);
    }

    @Override
    @GuardedBy("mLock")
    public void prepareForExecutionLocked(JobStatus jobStatus) {
        if (DEBUG) {
            Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
        }

        final int uid = jobStatus.getSourceUid();
        if (mService.getUidBias(uid) == JobInfo.BIAS_TOP_APP) {
            if (DEBUG) {
                Slog.d(TAG, jobStatus.toShortString() + " is top started job");
            }
            mTopStartedJobs.add(jobStatus);
        }
    }

    @Override
    @GuardedBy("mLock")
    public void unprepareFromExecutionLocked(JobStatus jobStatus) {
        mTopStartedJobs.remove(jobStatus);
    }

    @Override
    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
        if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
            mTrackedTasks.remove(taskStatus);
            mTopStartedJobs.remove(taskStatus);
        }
    }

@@ -90,33 +145,124 @@ public final class BatteryController extends RestrictingController {
        });
    }

    @Override
    @GuardedBy("mLock")
    public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
        if (prevBias == JobInfo.BIAS_TOP_APP || newBias == JobInfo.BIAS_TOP_APP) {
            maybeReportNewChargingStateLocked();
        }
    }

    @GuardedBy("mLock")
    private boolean hasTopExemptionLocked(@NonNull JobStatus taskStatus) {
        return mService.getUidBias(taskStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP
                || mTopStartedJobs.contains(taskStatus);
    }

    @GuardedBy("mLock")
    private void maybeReportNewChargingStateLocked() {
        final boolean powerConnected = mPowerTracker.isPowerConnected();
        final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow();
        final boolean batteryNotLow = mService.isBatteryNotLow();
        if (DEBUG) {
            Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
            Slog.d(TAG, "maybeReportNewChargingStateLocked: "
                    + powerConnected + "/" + stablePower + "/" + batteryNotLow);
        }
        final long nowElapsed = sElapsedRealtimeClock.millis();
        boolean reportChange = false;
        for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
            final JobStatus ts = mTrackedTasks.valueAt(i);
            reportChange |= ts.setChargingConstraintSatisfied(nowElapsed, stablePower);
            reportChange |= ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow);
            if (ts.hasChargingConstraint()) {
                if (hasTopExemptionLocked(ts)
                        && ts.getEffectivePriority() >= JobInfo.PRIORITY_DEFAULT) {
                    // If the job started while the app was on top or the app is currently on top,
                    // let the job run as long as there's power connected, even if the device isn't
                    // officially charging.
                    // For user requested/initiated jobs, users may be confused when the task stops
                    // running even though the device is plugged in.
                    // Low priority jobs don't need to be exempted.
                    if (ts.setChargingConstraintSatisfied(nowElapsed, powerConnected)) {
                        mChangedJobs.add(ts);
                    }
                } else if (ts.setChargingConstraintSatisfied(nowElapsed, stablePower)) {
                    mChangedJobs.add(ts);
                }
            }
            if (ts.hasBatteryNotLowConstraint()
                    && ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow)) {
                mChangedJobs.add(ts);
            }
        }
        if (stablePower || batteryNotLow) {
            // If one of our conditions has been satisfied, always schedule any newly ready jobs.
            mStateChangedListener.onRunJobNow(null);
        } else if (reportChange) {
        } else if (mChangedJobs.size() > 0) {
            // Otherwise, just let the job scheduler know the state has changed and take care of it
            // as it thinks is best.
            mStateChangedListener.onControllerStateChanged(mTrackedTasks);
            mStateChangedListener.onControllerStateChanged(mChangedJobs);
        }
        mChangedJobs.clear();
    }

    private final class PowerTracker extends BroadcastReceiver {
        /**
         * Track whether there is power connected. It doesn't mean the device is charging.
         * Use {@link JobSchedulerService#isBatteryCharging()} to determine if the device is
         * charging.
         */
        private boolean mPowerConnected;

        PowerTracker() {
        }

        void startTracking() {
            IntentFilter filter = new IntentFilter();

            filter.addAction(Intent.ACTION_POWER_CONNECTED);
            filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
            mContext.registerReceiver(this, filter);

            // Initialize tracker state.
            BatteryManagerInternal batteryManagerInternal =
                    LocalServices.getService(BatteryManagerInternal.class);
            mPowerConnected = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
        }

        boolean isPowerConnected() {
            return mPowerConnected;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            synchronized (mLock) {
                final String action = intent.getAction();

                if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Power connected @ " + sElapsedRealtimeClock.millis());
                    }
                    if (mPowerConnected) {
                        return;
                    }
                    mPowerConnected = true;
                } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Power disconnected @ " + sElapsedRealtimeClock.millis());
                    }
                    if (!mPowerConnected) {
                        return;
                    }
                    mPowerConnected = false;
                }

                maybeReportNewChargingStateLocked();
            }
        }
    }

    @Override
    public void dumpControllerStateLocked(IndentingPrintWriter pw,
            Predicate<JobStatus> predicate) {
        pw.println("Power connected: " + mPowerTracker.isPowerConnected());
        pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow()));
        pw.println("Not low: " + mService.isBatteryNotLow());

+1 −1
Original line number Diff line number Diff line
@@ -517,7 +517,7 @@ public final class ConnectivityController extends RestrictingController implemen

    @GuardedBy("mLock")
    @Override
    public void onUidBiasChangedLocked(int uid, int newBias) {
    public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
        UidStats uidStats = mUidStats.get(uid);
        if (uidStats != null && uidStats.baseBias != newBias) {
            uidStats.baseBias = newBias;
+4 −13
Original line number Diff line number Diff line
@@ -40,7 +40,6 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArrayMap;
import android.util.SparseBooleanArray;
import android.util.TimeUtils;

import com.android.internal.annotations.GuardedBy;
@@ -81,9 +80,6 @@ public class PrefetchController extends StateController {
     */
    @GuardedBy("mLock")
    private final SparseArrayMap<String, Long> mEstimatedLaunchTimes = new SparseArrayMap<>();
    /** Cached list of UIDs in the TOP state. */
    @GuardedBy("mLock")
    private final SparseBooleanArray mTopUids = new SparseBooleanArray();
    private final ThresholdAlarmListener mThresholdAlarmListener;

    /**
@@ -186,15 +182,9 @@ public class PrefetchController extends StateController {

    @GuardedBy("mLock")
    @Override
    public void onUidBiasChangedLocked(int uid, int newBias) {
    public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
        final boolean isNowTop = newBias == JobInfo.BIAS_TOP_APP;
        final boolean wasTop = mTopUids.get(uid);
        if (isNowTop) {
            mTopUids.put(uid, true);
        } else {
            // Delete entries of non-top apps so the set doesn't get too large.
            mTopUids.delete(uid);
        }
        final boolean wasTop = prevBias == JobInfo.BIAS_TOP_APP;
        if (isNowTop != wasTop) {
            mHandler.obtainMessage(MSG_PROCESS_TOP_STATE_CHANGE, uid, 0).sendToTarget();
        }
@@ -314,7 +304,8 @@ public class PrefetchController extends StateController {
        //   3. The app is not open but has an active widget (we can't tell if a widget displays
        //      status/data, so this assumes the prefetch job is to update the data displayed on
        //      the widget).
        final boolean appIsOpen = mTopUids.get(jobStatus.getSourceUid());
        final boolean appIsOpen =
                mService.getUidBias(jobStatus.getSourceUid()) == JobInfo.BIAS_TOP_APP;
        final boolean satisfied;
        if (!appIsOpen) {
            final int userId = jobStatus.getSourceUserId();
+1 −1
Original line number Diff line number Diff line
@@ -144,7 +144,7 @@ public abstract class StateController {
     * important the UID is.
     */
    @GuardedBy("mLock")
    public void onUidBiasChangedLocked(int uid, int newBias) {
    public void onUidBiasChangedLocked(int uid, int prevBias, int newBias) {
    }

    protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) {
Loading