diff options
9 files changed, 238 insertions, 341 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index 86d7a5a5a62e..3fb1fadba062 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -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()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java index cc202130ab07..27268d267001 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java @@ -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; } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java index 657f4700bd5c..f7338494384d 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java @@ -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); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index 524d68fb72e7..9fb7ab594607 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -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) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index c147ef83dcf0..65e1d49d1510 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -39,15 +39,11 @@ import android.app.IUidObserver; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.BatteryManager; -import android.os.BatteryManagerInternal; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -319,7 +315,6 @@ public final class QuotaController extends StateController { private final SparseLongArray mTopAppGraceCache = new SparseLongArray(); private final AlarmManager mAlarmManager; - private final ChargingTracker mChargeTracker; private final QcHandler mHandler; private final QcConstants mQcConstants; @@ -542,8 +537,6 @@ public final class QuotaController extends StateController { @NonNull ConnectivityController connectivityController) { super(service); mHandler = new QcHandler(mContext.getMainLooper()); - mChargeTracker = new ChargingTracker(); - mChargeTracker.startTracking(); mAlarmManager = mContext.getSystemService(AlarmManager.class); mQcConstants = new QcConstants(); mBackgroundJobsController = backgroundJobsController; @@ -705,6 +698,11 @@ public final class QuotaController extends StateController { mTopAppTrackers.delete(userId); } + @Override + public void onBatteryStateChangedLocked() { + handleNewChargingStateLocked(); + } + /** Drop all historical stats and stop tracking any active sessions for the specified app. */ public void clearAppStatsLocked(int userId, @NonNull String packageName) { mTrackedJobs.delete(userId, packageName); @@ -766,7 +764,7 @@ public final class QuotaController extends StateController { if (!jobStatus.shouldTreatAsExpeditedJob()) { // If quota is currently "free", then the job can run for the full amount of time, // regardless of bucket (hence using charging instead of isQuotaFreeLocked()). - if (mChargeTracker.isChargingLocked() + if (mService.isBatteryCharging() || mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus) || isUidInForeground(jobStatus.getSourceUid())) { @@ -777,7 +775,7 @@ public final class QuotaController extends StateController { } // Expedited job. - if (mChargeTracker.isChargingLocked()) { + if (mService.isBatteryCharging()) { return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; } if (mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus)) { @@ -864,7 +862,7 @@ public final class QuotaController extends StateController { @GuardedBy("mLock") private boolean isQuotaFreeLocked(final int standbyBucket) { // Quota constraint is not enforced while charging. - if (mChargeTracker.isChargingLocked()) { + if (mService.isBatteryCharging()) { // Restricted jobs require additional constraints when charging, so don't immediately // mark quota as free when charging. return standbyBucket != RESTRICTED_INDEX; @@ -1538,15 +1536,19 @@ public final class QuotaController extends StateController { private void handleNewChargingStateLocked() { mTimerChargingUpdateFunctor.setStatus(sElapsedRealtimeClock.millis(), - mChargeTracker.isChargingLocked()); + mService.isBatteryCharging()); if (DEBUG) { - Slog.d(TAG, "handleNewChargingStateLocked: " + mChargeTracker.isChargingLocked()); + Slog.d(TAG, "handleNewChargingStateLocked: " + mService.isBatteryCharging()); } // Deal with Timers first. mEJPkgTimers.forEach(mTimerChargingUpdateFunctor); mPkgTimers.forEach(mTimerChargingUpdateFunctor); - // Now update jobs. - maybeUpdateAllConstraintsLocked(); + // Now update jobs out of band so broadcast processing can proceed. + JobSchedulerBackgroundThread.getHandler().post(() -> { + synchronized (mLock) { + maybeUpdateAllConstraintsLocked(); + } + }); } private void maybeUpdateAllConstraintsLocked() { @@ -1811,58 +1813,6 @@ public final class QuotaController extends StateController { return false; } - private final class ChargingTracker extends BroadcastReceiver { - /** - * Track whether we're charging. This has a slightly different definition than that of - * BatteryController. - */ - @GuardedBy("mLock") - private boolean mCharging; - - ChargingTracker() { - } - - public void startTracking() { - IntentFilter filter = new IntentFilter(); - - // 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); - mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); - } - - @GuardedBy("mLock") - public boolean isChargingLocked() { - return mCharging; - } - - @Override - public void onReceive(Context context, Intent intent) { - synchronized (mLock) { - final String action = intent.getAction(); - if (BatteryManager.ACTION_CHARGING.equals(action)) { - if (DEBUG) { - Slog.d(TAG, "Received charging intent, fired @ " - + sElapsedRealtimeClock.millis()); - } - mCharging = true; - handleNewChargingStateLocked(); - } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { - if (DEBUG) { - Slog.d(TAG, "Disconnected from power."); - } - mCharging = false; - handleNewChargingStateLocked(); - } - } - } - } - @VisibleForTesting static final class TimingSession { // Start timestamp in elapsed realtime timebase. @@ -2127,6 +2077,12 @@ public final class QuotaController extends StateController { final long topAppGracePeriodEndElapsed = mTopAppGraceCache.get(mUid); final boolean hasTopAppExemption = !mRegularJobTimer && (mTopAppCache.get(mUid) || nowElapsed < topAppGracePeriodEndElapsed); + if (DEBUG) { + Slog.d(TAG, "quotaFree=" + isQuotaFreeLocked(standbyBucket) + + " isFG=" + mForegroundUids.get(mUid) + + " tempEx=" + hasTempAllowlistExemption + + " topEx=" + hasTopAppExemption); + } return !isQuotaFreeLocked(standbyBucket) && !mForegroundUids.get(mUid) && !hasTempAllowlistExemption && !hasTopAppExemption; @@ -3938,7 +3894,6 @@ public final class QuotaController extends StateController { public void dumpControllerStateLocked(final IndentingPrintWriter pw, final Predicate<JobStatus> predicate) { pw.println("Is enabled: " + mIsEnabled); - pw.println("Is charging: " + mChargeTracker.isChargingLocked()); pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis()); pw.println(); @@ -4116,7 +4071,7 @@ public final class QuotaController extends StateController { final long mToken = proto.start(StateControllerProto.QUOTA); proto.write(StateControllerProto.QuotaController.IS_CHARGING, - mChargeTracker.isChargingLocked()); + mService.isBatteryCharging()); proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME, sElapsedRealtimeClock.millis()); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java index 5e73f42ff1cb..509fb6963a3c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java @@ -133,6 +133,13 @@ public abstract class StateController { } /** + * Called when the battery status changes. + */ + @GuardedBy("mLock") + public void onBatteryStateChangedLocked() { + } + + /** * Called when a UID's base bias has changed. The more positive the bias, the more * important the UID is. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index 0c3e472b46a8..bdeb2b4fd839 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -122,15 +122,14 @@ public class JobSchedulerServiceTest { .when(() -> LocalServices.getService(ActivityManagerInternal.class)); doReturn(mock(AppStandbyInternal.class)) .when(() -> LocalServices.getService(AppStandbyInternal.class)); + doReturn(mock(BatteryManagerInternal.class)) + .when(() -> LocalServices.getService(BatteryManagerInternal.class)); doReturn(mock(UsageStatsManagerInternal.class)) .when(() -> LocalServices.getService(UsageStatsManagerInternal.class)); when(mContext.getString(anyInt())).thenReturn("some_test_string"); // Called in BackgroundJobsController constructor. doReturn(mock(AppStateTrackerImpl.class)) .when(() -> LocalServices.getService(AppStateTracker.class)); - // Called in BatteryController constructor. - doReturn(mock(BatteryManagerInternal.class)) - .when(() -> LocalServices.getService(BatteryManagerInternal.class)); // Called in ConnectivityController constructor. when(mContext.getSystemService(ConnectivityManager.class)) .thenReturn(mock(ConnectivityManager.class)); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index a9853bf94eeb..f61d6ca750cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -48,18 +48,14 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.job.JobInfo; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.pm.PackageManagerInternal; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkPolicyManager; -import android.os.BatteryManager; -import android.os.BatteryManagerInternal; import android.os.Build; import android.os.Looper; import android.os.SystemClock; @@ -74,7 +70,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @@ -88,8 +83,6 @@ public class ConnectivityControllerTest { @Mock private Context mContext; @Mock - private BatteryManagerInternal mBatteryManagerInternal; - @Mock private ConnectivityManager mConnManager; @Mock private NetworkPolicyManager mNetPolicyManager; @@ -115,9 +108,6 @@ public class ConnectivityControllerTest { LocalServices.removeServiceForTest(NetworkPolicyManagerInternal.class); LocalServices.addService(NetworkPolicyManagerInternal.class, mNetPolicyManagerInternal); - LocalServices.removeServiceForTest(BatteryManagerInternal.class); - LocalServices.addService(BatteryManagerInternal.class, mBatteryManagerInternal); - when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); // Freeze the clocks at this moment in time @@ -158,18 +148,10 @@ public class ConnectivityControllerTest { .setMinimumNetworkChunkBytes(DataUnit.KIBIBYTES.toBytes(100)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); - final ArgumentCaptor<BroadcastReceiver> chargingCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); - when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) - .thenReturn(false); + when(mService.isBatteryCharging()).thenReturn(false); final ConnectivityController controller = new ConnectivityController(mService); - verify(mContext).registerReceiver(chargingCaptor.capture(), - ArgumentMatchers.argThat(filter -> - filter.hasAction(BatteryManager.ACTION_CHARGING) - && filter.hasAction(BatteryManager.ACTION_DISCHARGING))); when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(10 * 60_000L); - final BroadcastReceiver chargingReceiver = chargingCaptor.getValue(); - chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING)); + controller.onBatteryStateChangedLocked(); // Slow network is too slow assertFalse(controller.isSatisfied(createJobStatus(job), net, @@ -225,17 +207,15 @@ public class ConnectivityControllerTest { createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130) .setLinkDownstreamBandwidthKbps(130).build(), mConstants)); // Slow network is too slow, but device is charging and network is unmetered. - when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) - .thenReturn(true); - chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING)); + when(mService.isBatteryCharging()).thenReturn(true); + controller.onBatteryStateChangedLocked(); assertTrue(controller.isSatisfied(createJobStatus(job), net, createCapabilitiesBuilder().addCapability(NET_CAPABILITY_NOT_METERED) .setLinkUpstreamBandwidthKbps(1).setLinkDownstreamBandwidthKbps(1).build(), mConstants)); - when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) - .thenReturn(false); - chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING)); + when(mService.isBatteryCharging()).thenReturn(false); + controller.onBatteryStateChangedLocked(); when(mService.getMaxJobExecutionTimeMs(any())).thenReturn(60_000L); // Slow network is too slow @@ -259,9 +239,8 @@ public class ConnectivityControllerTest { createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130) .setLinkDownstreamBandwidthKbps(130).build(), mConstants)); // Slow network is too slow, but device is charging and network is unmetered. - when(mBatteryManagerInternal.isPowered(eq(BatteryManager.BATTERY_PLUGGED_ANY))) - .thenReturn(true); - chargingReceiver.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING)); + when(mService.isBatteryCharging()).thenReturn(true); + controller.onBatteryStateChangedLocked(); assertTrue(controller.isSatisfied(createJobStatus(job), net, createCapabilitiesBuilder().addCapability(NET_CAPABILITY_NOT_METERED) .setLinkUpstreamBandwidthKbps(1).setLinkDownstreamBandwidthKbps(1).build(), diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index 300f93feed28..cfae9a3d586a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -63,16 +63,13 @@ import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManager; import android.app.usage.UsageStatsManagerInternal; -import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ServiceInfo; -import android.os.BatteryManager; import android.os.BatteryManagerInternal; import android.os.Handler; import android.os.Looper; @@ -126,7 +123,6 @@ public class QuotaControllerTest { private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; private static final int SOURCE_USER_ID = 0; - private BroadcastReceiver mChargingReceiver; private QuotaController mQuotaController; private QuotaController.QcConstants mQcConstants; private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); @@ -225,8 +221,6 @@ public class QuotaControllerTest { // Initialize real objects. // Capture the listeners. - ArgumentCaptor<BroadcastReceiver> receiverCaptor = - ArgumentCaptor.forClass(BroadcastReceiver.class); ArgumentCaptor<IUidObserver> uidObserverCaptor = ArgumentCaptor.forClass(IUidObserver.class); ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor = @@ -236,11 +230,6 @@ public class QuotaControllerTest { mQuotaController = new QuotaController(mJobSchedulerService, mock(BackgroundJobsController.class), mock(ConnectivityController.class)); - verify(mContext).registerReceiver(receiverCaptor.capture(), - ArgumentMatchers.argThat(filter -> - filter.hasAction(BatteryManager.ACTION_CHARGING) - && filter.hasAction(BatteryManager.ACTION_DISCHARGING))); - mChargingReceiver = receiverCaptor.getValue(); verify(mPowerAllowlistInternal) .registerTempAllowlistChangeListener(taChangeCaptor.capture()); mTempAllowlistListener = taChangeCaptor.getValue(); @@ -280,13 +269,17 @@ public class QuotaControllerTest { } private void setCharging() { - Intent intent = new Intent(BatteryManager.ACTION_CHARGING); - mChargingReceiver.onReceive(mContext, intent); + doReturn(true).when(mJobSchedulerService).isBatteryCharging(); + synchronized (mQuotaController.mLock) { + mQuotaController.onBatteryStateChangedLocked(); + } } private void setDischarging() { - Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING); - mChargingReceiver.onReceive(mContext, intent); + doReturn(false).when(mJobSchedulerService).isBatteryCharging(); + synchronized (mQuotaController.mLock) { + mQuotaController.onBatteryStateChangedLocked(); + } } private void setProcessState(int procState) { |