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

Commit b32b6aed authored by Kweku Adams's avatar Kweku Adams
Browse files

Combine battery trackers.

Combine the separate Controller battery/charging trackers into one so
that JobScheduler only has to receive the battery change broadcasts once
to update all of the state. This also makes testing easier since the
test can be sure that once the battery sequence is updated, all of JS
has processed the battery state change.

Bug: 141645789
Bug: 213277873
Test: atest --rerun-until-failure 25 CtsJobSchedulerTestCases:android.jobscheduler.cts.JobThrottlingTest#testRestrictingStopReason_Quota
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: I7151798f17c177eb5e86a6ec51c2d545768a4fcd
parent da94cf36
Loading
Loading
Loading
Loading
+152 −15
Original line number Diff line number Diff line
@@ -54,6 +54,8 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Binder;
@@ -250,8 +252,6 @@ public class JobSchedulerService extends com.android.server.SystemService
     */
    private final List<RestrictingController> mRestrictiveControllers;
    /** Need direct access to this for testing. */
    private final BatteryController mBatteryController;
    /** Need direct access to this for testing. */
    private final StorageController mStorageController;
    /** Need directly for sending uid state changes */
    private final DeviceIdleJobsController mDeviceIdleJobsController;
@@ -268,6 +268,9 @@ public class JobSchedulerService extends com.android.server.SystemService
     */
    private final List<JobRestriction> mJobRestrictions;

    @GuardedBy("mLock")
    private final BatteryStateTracker mBatteryStateTracker;

    @NonNull
    private final String mSystemGalleryPackage;

@@ -1697,6 +1700,9 @@ public class JobSchedulerService extends com.android.server.SystemService
        // Initialize the job store and set up any persisted jobs
        mJobs = JobStore.initAndGet(this);

        mBatteryStateTracker = new BatteryStateTracker();
        mBatteryStateTracker.startTracking();

        // Create the controllers.
        mControllers = new ArrayList<StateController>();
        final ConnectivityController connectivityController = new ConnectivityController(this);
@@ -1704,8 +1710,8 @@ public class JobSchedulerService extends com.android.server.SystemService
        mControllers.add(new TimeController(this));
        final IdleController idleController = new IdleController(this);
        mControllers.add(idleController);
        mBatteryController = new BatteryController(this);
        mControllers.add(mBatteryController);
        final BatteryController batteryController = new BatteryController(this);
        mControllers.add(batteryController);
        mStorageController = new StorageController(this);
        mControllers.add(mStorageController);
        final BackgroundJobsController backgroundJobsController =
@@ -1725,7 +1731,7 @@ public class JobSchedulerService extends com.android.server.SystemService
        mControllers.add(mTareController);

        mRestrictiveControllers = new ArrayList<>();
        mRestrictiveControllers.add(mBatteryController);
        mRestrictiveControllers.add(batteryController);
        mRestrictiveControllers.add(connectivityController);
        mRestrictiveControllers.add(idleController);

@@ -2818,6 +2824,129 @@ public class JobSchedulerService extends com.android.server.SystemService
        return adjustJobBias(bias, job);
    }

    private final class BatteryStateTracker extends BroadcastReceiver {
        /**
         * Track whether we're "charging", where charging means that we're ready to commit to
         * doing work.
         */
        private boolean mCharging;
        /** Keep track of whether the battery is charged enough that we want to do work. */
        private boolean mBatteryNotLow;
        /** Sequence number of last broadcast. */
        private int mLastBatterySeq = -1;

        private BroadcastReceiver mMonitor;

        BatteryStateTracker() {
        }

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

            // Battery health.
            filter.addAction(Intent.ACTION_BATTERY_LOW);
            filter.addAction(Intent.ACTION_BATTERY_OKAY);
            // Charging/not charging.
            filter.addAction(BatteryManager.ACTION_CHARGING);
            filter.addAction(BatteryManager.ACTION_DISCHARGING);
            getTestableContext().registerReceiver(this, filter);

            // Initialise tracker state.
            BatteryManagerInternal batteryManagerInternal =
                    LocalServices.getService(BatteryManagerInternal.class);
            mBatteryNotLow = !batteryManagerInternal.getBatteryLevelLow();
            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
        }

        public void setMonitorBatteryLocked(boolean enabled) {
            if (enabled) {
                if (mMonitor == null) {
                    mMonitor = new BroadcastReceiver() {
                        @Override
                        public void onReceive(Context context, Intent intent) {
                            onReceiveInternal(intent);
                        }
                    };
                    IntentFilter filter = new IntentFilter();
                    filter.addAction(Intent.ACTION_BATTERY_CHANGED);
                    getTestableContext().registerReceiver(mMonitor, filter);
                }
            } else if (mMonitor != null) {
                getTestableContext().unregisterReceiver(mMonitor);
                mMonitor = null;
            }
        }

        public boolean isCharging() {
            return mCharging;
        }

        public boolean isBatteryNotLow() {
            return mBatteryNotLow;
        }

        public boolean isMonitoring() {
            return mMonitor != null;
        }

        public int getSeq() {
            return mLastBatterySeq;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            onReceiveInternal(intent);
        }

        @VisibleForTesting
        public void onReceiveInternal(Intent intent) {
            synchronized (mLock) {
                final String action = intent.getAction();
                boolean changed = false;
                if (Intent.ACTION_BATTERY_LOW.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Battery life too low @ " + sElapsedRealtimeClock.millis());
                    }
                    if (mBatteryNotLow) {
                        mBatteryNotLow = false;
                        changed = true;
                    }
                } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Battery high enough @ " + sElapsedRealtimeClock.millis());
                    }
                    if (!mBatteryNotLow) {
                        mBatteryNotLow = true;
                        changed = true;
                    }
                } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Battery charging @ " + sElapsedRealtimeClock.millis());
                    }
                    if (!mCharging) {
                        mCharging = true;
                        changed = true;
                    }
                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Disconnected from power @ " + sElapsedRealtimeClock.millis());
                    }
                    if (mCharging) {
                        mCharging = false;
                        changed = true;
                    }
                }
                mLastBatterySeq =
                        intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE, mLastBatterySeq);
                if (changed) {
                    for (int c = mControllers.size() - 1; c >= 0; --c) {
                        mControllers.get(c).onBatteryStateChangedLocked();
                    }
                }
            }
        }
    }

    final class LocalService implements JobSchedulerInternal {

        /**
@@ -3417,29 +3546,27 @@ public class JobSchedulerService extends com.android.server.SystemService

    void setMonitorBattery(boolean enabled) {
        synchronized (mLock) {
            if (mBatteryController != null) {
                mBatteryController.getTracker().setMonitorBatteryLocked(enabled);
            }
            mBatteryStateTracker.setMonitorBatteryLocked(enabled);
        }
    }

    int getBatterySeq() {
        synchronized (mLock) {
            return mBatteryController != null ? mBatteryController.getTracker().getSeq() : -1;
            return mBatteryStateTracker.getSeq();
        }
    }

    boolean getBatteryCharging() {
    /** Return {@code true} if the device is currently charging. */
    public boolean isBatteryCharging() {
        synchronized (mLock) {
            return mBatteryController != null
                    ? mBatteryController.getTracker().isOnStablePower() : false;
            return mBatteryStateTracker.isCharging();
        }
    }

    boolean getBatteryNotLow() {
    /** Return {@code true} if the battery is not low. */
    public boolean isBatteryNotLow() {
        synchronized (mLock) {
            return mBatteryController != null
                    ? mBatteryController.getTracker().isBatteryNotLow() : false;
            return mBatteryStateTracker.isBatteryNotLow();
        }
    }

@@ -3614,6 +3741,16 @@ public class JobSchedulerService extends com.android.server.SystemService
            mQuotaTracker.dump(pw);
            pw.println();

            pw.print("Battery charging: ");
            pw.println(mBatteryStateTracker.isCharging());
            pw.print("Battery not low: ");
            pw.println(mBatteryStateTracker.isBatteryNotLow());
            if (mBatteryStateTracker.isMonitoring()) {
                pw.print("MONITORING: seq=");
                pw.println(mBatteryStateTracker.getSeq());
            }
            pw.println();

            pw.println("Started users: " + Arrays.toString(mStartedUsers));
            pw.print("Registered ");
            pw.print(mJobs.size());
+2 −2
Original line number Diff line number Diff line
@@ -293,13 +293,13 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
    }

    private int getBatteryCharging(PrintWriter pw) {
        boolean val = mInternal.getBatteryCharging();
        boolean val = mInternal.isBatteryCharging();
        pw.println(val);
        return 0;
    }

    private int getBatteryNotLow(PrintWriter pw) {
        boolean val = mInternal.getBatteryNotLow();
        boolean val = mInternal.isBatteryNotLow();
        pw.println(val);
        return 0;
    }
+23 −152
Original line number Diff line number Diff line
@@ -18,12 +18,6 @@ package com.android.server.job.controllers;

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

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;
@@ -31,8 +25,8 @@ import android.util.Log;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

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

@@ -49,17 +43,9 @@ public final class BatteryController extends RestrictingController {
            || Log.isLoggable(TAG, Log.DEBUG);

    private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
    private ChargingTracker mChargeTracker;

    @VisibleForTesting
    public ChargingTracker getTracker() {
        return mChargeTracker;
    }

    public BatteryController(JobSchedulerService service) {
        super(service);
        mChargeTracker = new ChargingTracker();
        mChargeTracker.startTracking();
    }

    @Override
@@ -68,9 +54,9 @@ public final class BatteryController extends RestrictingController {
            final long nowElapsed = sElapsedRealtimeClock.millis();
            mTrackedTasks.add(taskStatus);
            taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
            taskStatus.setChargingConstraintSatisfied(nowElapsed, mChargeTracker.isOnStablePower());
            taskStatus.setBatteryNotLowConstraintSatisfied(
                    nowElapsed, mChargeTracker.isBatteryNotLow());
            taskStatus.setChargingConstraintSatisfied(nowElapsed,
                    mService.isBatteryCharging() && mService.isBatteryNotLow());
            taskStatus.setBatteryNotLowConstraintSatisfied(nowElapsed, mService.isBatteryNotLow());
        }
    }

@@ -93,9 +79,21 @@ public final class BatteryController extends RestrictingController {
        }
    }

    @Override
    @GuardedBy("mLock")
    public void onBatteryStateChangedLocked() {
        // Update job bookkeeping out of band.
        JobSchedulerBackgroundThread.getHandler().post(() -> {
            synchronized (mLock) {
                maybeReportNewChargingStateLocked();
            }
        });
    }

    @GuardedBy("mLock")
    private void maybeReportNewChargingStateLocked() {
        final boolean stablePower = mChargeTracker.isOnStablePower();
        final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
        final boolean stablePower = mService.isBatteryCharging() && mService.isBatteryNotLow();
        final boolean batteryNotLow = mService.isBatteryNotLow();
        if (DEBUG) {
            Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
        }
@@ -116,133 +114,11 @@ public final class BatteryController extends RestrictingController {
        }
    }

    public final class ChargingTracker extends BroadcastReceiver {
        /**
         * Track whether we're "charging", where charging means that we're ready to commit to
         * doing work.
         */
        private boolean mCharging;
        /** Keep track of whether the battery is charged enough that we want to do work. */
        private boolean mBatteryHealthy;
        /** Sequence number of last broadcast. */
        private int mLastBatterySeq = -1;

        private BroadcastReceiver mMonitor;

        public ChargingTracker() {
        }

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

            // Battery health.
            filter.addAction(Intent.ACTION_BATTERY_LOW);
            filter.addAction(Intent.ACTION_BATTERY_OKAY);
            // Charging/not charging.
            filter.addAction(BatteryManager.ACTION_CHARGING);
            filter.addAction(BatteryManager.ACTION_DISCHARGING);
            mContext.registerReceiver(this, filter);

            // Initialise tracker state.
            BatteryManagerInternal batteryManagerInternal =
                    LocalServices.getService(BatteryManagerInternal.class);
            mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
        }

        public void setMonitorBatteryLocked(boolean enabled) {
            if (enabled) {
                if (mMonitor == null) {
                    mMonitor = new BroadcastReceiver() {
                        @Override public void onReceive(Context context, Intent intent) {
                            ChargingTracker.this.onReceive(context, intent);
                        }
                    };
                    IntentFilter filter = new IntentFilter();
                    filter.addAction(Intent.ACTION_BATTERY_CHANGED);
                    mContext.registerReceiver(mMonitor, filter);
                }
            } else {
                if (mMonitor != null) {
                    mContext.unregisterReceiver(mMonitor);
                    mMonitor = null;
                }
            }
        }

        public boolean isOnStablePower() {
            return mCharging && mBatteryHealthy;
        }

        public boolean isBatteryNotLow() {
            return mBatteryHealthy;
        }

        public boolean isMonitoring() {
            return mMonitor != null;
        }

        public int getSeq() {
            return mLastBatterySeq;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            onReceiveInternal(intent);
        }

        @VisibleForTesting
        public void onReceiveInternal(Intent intent) {
            synchronized (mLock) {
                final String action = intent.getAction();
                if (Intent.ACTION_BATTERY_LOW.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Battery life too low to do work. @ "
                                + sElapsedRealtimeClock.millis());
                    }
                    // If we get this action, the battery is discharging => it isn't plugged in so
                    // there's no work to cancel. We track this variable for the case where it is
                    // charging, but hasn't been for long enough to be healthy.
                    mBatteryHealthy = false;
                    maybeReportNewChargingStateLocked();
                } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Battery life healthy enough to do work. @ "
                                + sElapsedRealtimeClock.millis());
                    }
                    mBatteryHealthy = true;
                    maybeReportNewChargingStateLocked();
                } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Received charging intent, fired @ "
                                + sElapsedRealtimeClock.millis());
                    }
                    mCharging = true;
                    maybeReportNewChargingStateLocked();
                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
                    if (DEBUG) {
                        Slog.d(TAG, "Disconnected from power.");
                    }
                    mCharging = false;
                    maybeReportNewChargingStateLocked();
                }
                mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE,
                        mLastBatterySeq);
            }
        }
    }

    @Override
    public void dumpControllerStateLocked(IndentingPrintWriter pw,
            Predicate<JobStatus> predicate) {
        pw.println("Stable power: " + mChargeTracker.isOnStablePower());
        pw.println("Not low: " + mChargeTracker.isBatteryNotLow());

        if (mChargeTracker.isMonitoring()) {
            pw.print("MONITORING: seq=");
            pw.println(mChargeTracker.getSeq());
        }
        pw.println();
        pw.println("Stable power: " + (mService.isBatteryCharging() && mService.isBatteryNotLow()));
        pw.println("Not low: " + mService.isBatteryNotLow());

        for (int i = 0; i < mTrackedTasks.size(); i++) {
            final JobStatus js = mTrackedTasks.valueAt(i);
@@ -264,14 +140,9 @@ public final class BatteryController extends RestrictingController {
        final long mToken = proto.start(StateControllerProto.BATTERY);

        proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER,
                mChargeTracker.isOnStablePower());
                mService.isBatteryCharging() && mService.isBatteryNotLow());
        proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW,
                mChargeTracker.isBatteryNotLow());

        proto.write(StateControllerProto.BatteryController.IS_MONITORING,
                mChargeTracker.isMonitoring());
        proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER,
                mChargeTracker.getSeq());
                mService.isBatteryNotLow());

        for (int i = 0; i < mTrackedTasks.size(); i++) {
            final JobStatus js = mTrackedTasks.valueAt(i);
+13 −57
Original line number Diff line number Diff line
@@ -25,18 +25,12 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
import android.net.NetworkRequest;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -55,6 +49,7 @@ import android.util.proto.ProtoOutputStream;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
import com.android.server.job.JobSchedulerService.Constants;
@@ -107,8 +102,6 @@ public final class ConnectivityController extends RestrictingController implemen
    private final ConnectivityManager mConnManager;
    private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;

    private final ChargingTracker mChargingTracker;

    /** List of tracked jobs keyed by source UID. */
    @GuardedBy("mLock")
    private final SparseArray<ArraySet<JobStatus>> mTrackedJobs = new SparseArray<>();
@@ -237,9 +230,6 @@ public final class ConnectivityController extends RestrictingController implemen
        // network changes against the active network for each UID with jobs.
        final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
        mConnManager.registerNetworkCallback(request, mNetworkCallback);

        mChargingTracker = new ChargingTracker();
        mChargingTracker.startTracking();
    }

    @GuardedBy("mLock")
@@ -535,6 +525,17 @@ public final class ConnectivityController extends RestrictingController implemen
        }
    }

    @Override
    @GuardedBy("mLock")
    public void onBatteryStateChangedLocked() {
        // Update job bookkeeping out of band to avoid blocking broadcast progress.
        JobSchedulerBackgroundThread.getHandler().post(() -> {
            synchronized (mLock) {
                updateTrackedJobsLocked(-1, null);
            }
        });
    }

    private boolean isUsable(NetworkCapabilities capabilities) {
        return capabilities != null
                && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
@@ -591,7 +592,7 @@ public final class ConnectivityController extends RestrictingController implemen
        // Minimum chunk size isn't defined. Check using the estimated upload/download sizes.

        if (capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
                && mChargingTracker.isCharging()) {
                && mService.isBatteryCharging()) {
            // We're charging and on an unmetered network. We don't have to be as conservative about
            // making sure the job will run within its max execution time. Let's just hope the app
            // supports interruptible work.
@@ -1072,51 +1073,6 @@ public final class ConnectivityController extends RestrictingController implemen
        }
    }

    private final class ChargingTracker extends BroadcastReceiver {
        /**
         * Track whether we're "charging", where charging means that we're ready to commit to
         * doing work.
         */
        private boolean mCharging;

        ChargingTracker() {}

        public void startTracking() {
            IntentFilter filter = new IntentFilter();
            filter.addAction(BatteryManager.ACTION_CHARGING);
            filter.addAction(BatteryManager.ACTION_DISCHARGING);
            mContext.registerReceiver(this, filter);

            // Initialise tracker state.
            final BatteryManagerInternal batteryManagerInternal =
                    LocalServices.getService(BatteryManagerInternal.class);
            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
        }

        public boolean isCharging() {
            return mCharging;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            synchronized (mLock) {
                final String action = intent.getAction();
                if (BatteryManager.ACTION_CHARGING.equals(action)) {
                    if (mCharging) {
                        return;
                    }
                    mCharging = true;
                } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
                    if (!mCharging) {
                        return;
                    }
                    mCharging = false;
                }
                updateTrackedJobsLocked(-1, null);
            }
        }
    }

    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
+23 −68

File changed.

Preview size limit exceeded, changes collapsed.

Loading