Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License |
| 15 | */ |
| 16 | |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 17 | package com.android.server.job.controllers; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 18 | |
Jeff Sharkey | d0fff2e | 2017-11-07 16:55:06 -0700 | [diff] [blame] | 19 | import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; |
| 20 | |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 21 | import android.content.BroadcastReceiver; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 22 | import android.content.Context; |
| 23 | import android.content.Intent; |
| 24 | import android.content.IntentFilter; |
| 25 | import android.os.BatteryManager; |
Jeff Brown | 2139276 | 2014-06-13 19:00:36 -0700 | [diff] [blame] | 26 | import android.os.BatteryManagerInternal; |
Dianne Hackborn | e9a988c | 2016-05-27 17:59:40 -0700 | [diff] [blame] | 27 | import android.os.UserHandle; |
Dianne Hackborn | f9bac16 | 2017-04-20 17:17:48 -0700 | [diff] [blame] | 28 | import android.util.ArraySet; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 29 | import android.util.Slog; |
Kweku Adams | 85f2fbc | 2017-12-18 12:04:12 -0800 | [diff] [blame] | 30 | import android.util.proto.ProtoOutputStream; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 31 | |
| 32 | import com.android.internal.annotations.VisibleForTesting; |
Jeff Brown | 2139276 | 2014-06-13 19:00:36 -0700 | [diff] [blame] | 33 | import com.android.server.LocalServices; |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 34 | import com.android.server.job.JobSchedulerService; |
| 35 | import com.android.server.job.StateChangedListener; |
Kweku Adams | 85f2fbc | 2017-12-18 12:04:12 -0800 | [diff] [blame] | 36 | import com.android.server.job.StateControllerProto; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 37 | |
Matthew Williams | effacfa | 2014-06-05 20:56:40 -0700 | [diff] [blame] | 38 | import java.io.PrintWriter; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 39 | |
| 40 | /** |
| 41 | * Simple controller that tracks whether the phone is charging or not. The phone is considered to |
| 42 | * be charging when it's been plugged in for more than two minutes, and the system has broadcast |
| 43 | * ACTION_BATTERY_OK. |
| 44 | */ |
Dianne Hackborn | 6466c1c | 2017-06-13 10:33:19 -0700 | [diff] [blame] | 45 | public final class BatteryController extends StateController { |
Matthew Williams | 759275d | 2014-06-10 12:59:29 -0700 | [diff] [blame] | 46 | private static final String TAG = "JobScheduler.Batt"; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 47 | |
| 48 | private static final Object sCreationLock = new Object(); |
| 49 | private static volatile BatteryController sController; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 50 | |
Dianne Hackborn | f9bac16 | 2017-04-20 17:17:48 -0700 | [diff] [blame] | 51 | private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>(); |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 52 | private ChargingTracker mChargeTracker; |
| 53 | |
Christopher Tate | 7060b04 | 2014-06-09 19:50:00 -0700 | [diff] [blame] | 54 | public static BatteryController get(JobSchedulerService taskManagerService) { |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 55 | synchronized (sCreationLock) { |
| 56 | if (sController == null) { |
| 57 | sController = new BatteryController(taskManagerService, |
Dianne Hackborn | 33d31c5 | 2016-02-16 10:30:33 -0800 | [diff] [blame] | 58 | taskManagerService.getContext(), taskManagerService.getLock()); |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 59 | } |
| 60 | } |
| 61 | return sController; |
| 62 | } |
| 63 | |
| 64 | @VisibleForTesting |
| 65 | public ChargingTracker getTracker() { |
| 66 | return mChargeTracker; |
| 67 | } |
| 68 | |
| 69 | @VisibleForTesting |
| 70 | public static BatteryController getForTesting(StateChangedListener stateChangedListener, |
| 71 | Context context) { |
Dianne Hackborn | 33d31c5 | 2016-02-16 10:30:33 -0800 | [diff] [blame] | 72 | return new BatteryController(stateChangedListener, context, new Object()); |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 73 | } |
| 74 | |
Dianne Hackborn | 33d31c5 | 2016-02-16 10:30:33 -0800 | [diff] [blame] | 75 | private BatteryController(StateChangedListener stateChangedListener, Context context, |
| 76 | Object lock) { |
| 77 | super(stateChangedListener, context, lock); |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 78 | mChargeTracker = new ChargingTracker(); |
| 79 | mChargeTracker.startTracking(); |
| 80 | } |
| 81 | |
| 82 | @Override |
Dianne Hackborn | b0001f6 | 2016-02-16 10:30:33 -0800 | [diff] [blame] | 83 | public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) { |
Dianne Hackborn | a06ec6a | 2017-02-13 10:08:42 -0800 | [diff] [blame] | 84 | if (taskStatus.hasPowerConstraint()) { |
Dianne Hackborn | b0001f6 | 2016-02-16 10:30:33 -0800 | [diff] [blame] | 85 | mTrackedTasks.add(taskStatus); |
Dianne Hackborn | f9bac16 | 2017-04-20 17:17:48 -0700 | [diff] [blame] | 86 | taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY); |
Dianne Hackborn | a06ec6a | 2017-02-13 10:08:42 -0800 | [diff] [blame] | 87 | taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower()); |
| 88 | taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow()); |
Matthew Williams | bafeeb9 | 2014-08-08 11:51:06 -0700 | [diff] [blame] | 89 | } |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 90 | } |
| 91 | |
| 92 | @Override |
Dianne Hackborn | 141f11c | 2016-04-05 15:46:12 -0700 | [diff] [blame] | 93 | public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) { |
Dianne Hackborn | f9bac16 | 2017-04-20 17:17:48 -0700 | [diff] [blame] | 94 | if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) { |
Dianne Hackborn | b0001f6 | 2016-02-16 10:30:33 -0800 | [diff] [blame] | 95 | mTrackedTasks.remove(taskStatus); |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 96 | } |
| 97 | } |
| 98 | |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 99 | private void maybeReportNewChargingStateLocked() { |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 100 | final boolean stablePower = mChargeTracker.isOnStablePower(); |
Dianne Hackborn | a06ec6a | 2017-02-13 10:08:42 -0800 | [diff] [blame] | 101 | final boolean batteryNotLow = mChargeTracker.isBatteryNotLow(); |
Matthew Williams | 759275d | 2014-06-10 12:59:29 -0700 | [diff] [blame] | 102 | if (DEBUG) { |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 103 | Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower); |
Matthew Williams | 759275d | 2014-06-10 12:59:29 -0700 | [diff] [blame] | 104 | } |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 105 | boolean reportChange = false; |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 106 | for (int i = mTrackedTasks.size() - 1; i >= 0; i--) { |
| 107 | final JobStatus ts = mTrackedTasks.valueAt(i); |
| 108 | boolean previous = ts.setChargingConstraintSatisfied(stablePower); |
| 109 | if (previous != stablePower) { |
| 110 | reportChange = true; |
| 111 | } |
| 112 | previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow); |
| 113 | if (previous != batteryNotLow) { |
| 114 | reportChange = true; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 115 | } |
| 116 | } |
Dianne Hackborn | 532ea26 | 2017-03-17 17:50:55 -0700 | [diff] [blame] | 117 | if (stablePower || batteryNotLow) { |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 118 | // If one of our conditions has been satisfied, always schedule any newly ready jobs. |
Matthew Williams | bafeeb9 | 2014-08-08 11:51:06 -0700 | [diff] [blame] | 119 | mStateChangedListener.onRunJobNow(null); |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 120 | } else if (reportChange) { |
| 121 | // Otherwise, just let the job scheduler know the state has changed and take care of it |
| 122 | // as it thinks is best. |
| 123 | mStateChangedListener.onControllerStateChanged(); |
Matthew Williams | bafeeb9 | 2014-08-08 11:51:06 -0700 | [diff] [blame] | 124 | } |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 125 | } |
| 126 | |
Dianne Hackborn | 6466c1c | 2017-06-13 10:33:19 -0700 | [diff] [blame] | 127 | public final class ChargingTracker extends BroadcastReceiver { |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 128 | /** |
| 129 | * Track whether we're "charging", where charging means that we're ready to commit to |
| 130 | * doing work. |
| 131 | */ |
| 132 | private boolean mCharging; |
| 133 | /** Keep track of whether the battery is charged enough that we want to do work. */ |
| 134 | private boolean mBatteryHealthy; |
Dianne Hackborn | a06ec6a | 2017-02-13 10:08:42 -0800 | [diff] [blame] | 135 | /** Sequence number of last broadcast. */ |
| 136 | private int mLastBatterySeq = -1; |
| 137 | |
| 138 | private BroadcastReceiver mMonitor; |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 139 | |
| 140 | public ChargingTracker() { |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | public void startTracking() { |
| 144 | IntentFilter filter = new IntentFilter(); |
| 145 | |
| 146 | // Battery health. |
| 147 | filter.addAction(Intent.ACTION_BATTERY_LOW); |
| 148 | filter.addAction(Intent.ACTION_BATTERY_OKAY); |
| 149 | // Charging/not charging. |
Dianne Hackborn | 4870e9d | 2015-04-08 16:55:47 -0700 | [diff] [blame] | 150 | filter.addAction(BatteryManager.ACTION_CHARGING); |
| 151 | filter.addAction(BatteryManager.ACTION_DISCHARGING); |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 152 | mContext.registerReceiver(this, filter); |
| 153 | |
| 154 | // Initialise tracker state. |
Jeff Brown | 2139276 | 2014-06-13 19:00:36 -0700 | [diff] [blame] | 155 | BatteryManagerInternal batteryManagerInternal = |
| 156 | LocalServices.getService(BatteryManagerInternal.class); |
| 157 | mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow(); |
| 158 | mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 159 | } |
| 160 | |
Dianne Hackborn | a06ec6a | 2017-02-13 10:08:42 -0800 | [diff] [blame] | 161 | public void setMonitorBatteryLocked(boolean enabled) { |
| 162 | if (enabled) { |
| 163 | if (mMonitor == null) { |
| 164 | mMonitor = new BroadcastReceiver() { |
| 165 | @Override public void onReceive(Context context, Intent intent) { |
| 166 | ChargingTracker.this.onReceive(context, intent); |
| 167 | } |
| 168 | }; |
| 169 | IntentFilter filter = new IntentFilter(); |
| 170 | filter.addAction(Intent.ACTION_BATTERY_CHANGED); |
| 171 | mContext.registerReceiver(mMonitor, filter); |
| 172 | } |
| 173 | } else { |
| 174 | if (mMonitor != null) { |
| 175 | mContext.unregisterReceiver(mMonitor); |
| 176 | mMonitor = null; |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | public boolean isOnStablePower() { |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 182 | return mCharging && mBatteryHealthy; |
| 183 | } |
| 184 | |
Dianne Hackborn | a06ec6a | 2017-02-13 10:08:42 -0800 | [diff] [blame] | 185 | public boolean isBatteryNotLow() { |
| 186 | return mBatteryHealthy; |
| 187 | } |
| 188 | |
| 189 | public boolean isMonitoring() { |
| 190 | return mMonitor != null; |
| 191 | } |
| 192 | |
| 193 | public int getSeq() { |
| 194 | return mLastBatterySeq; |
| 195 | } |
| 196 | |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 197 | @Override |
| 198 | public void onReceive(Context context, Intent intent) { |
| 199 | onReceiveInternal(intent); |
| 200 | } |
| 201 | |
| 202 | @VisibleForTesting |
| 203 | public void onReceiveInternal(Intent intent) { |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 204 | synchronized (mLock) { |
| 205 | final String action = intent.getAction(); |
| 206 | if (Intent.ACTION_BATTERY_LOW.equals(action)) { |
| 207 | if (DEBUG) { |
| 208 | Slog.d(TAG, "Battery life too low to do work. @ " |
Jeff Sharkey | d0fff2e | 2017-11-07 16:55:06 -0700 | [diff] [blame] | 209 | + sElapsedRealtimeClock.millis()); |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 210 | } |
| 211 | // If we get this action, the battery is discharging => it isn't plugged in so |
| 212 | // there's no work to cancel. We track this variable for the case where it is |
| 213 | // charging, but hasn't been for long enough to be healthy. |
| 214 | mBatteryHealthy = false; |
| 215 | maybeReportNewChargingStateLocked(); |
| 216 | } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { |
| 217 | if (DEBUG) { |
| 218 | Slog.d(TAG, "Battery life healthy enough to do work. @ " |
Jeff Sharkey | d0fff2e | 2017-11-07 16:55:06 -0700 | [diff] [blame] | 219 | + sElapsedRealtimeClock.millis()); |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 220 | } |
| 221 | mBatteryHealthy = true; |
| 222 | maybeReportNewChargingStateLocked(); |
| 223 | } else if (BatteryManager.ACTION_CHARGING.equals(action)) { |
| 224 | if (DEBUG) { |
| 225 | Slog.d(TAG, "Received charging intent, fired @ " |
Jeff Sharkey | d0fff2e | 2017-11-07 16:55:06 -0700 | [diff] [blame] | 226 | + sElapsedRealtimeClock.millis()); |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 227 | } |
| 228 | mCharging = true; |
| 229 | maybeReportNewChargingStateLocked(); |
| 230 | } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { |
| 231 | if (DEBUG) { |
| 232 | Slog.d(TAG, "Disconnected from power."); |
| 233 | } |
| 234 | mCharging = false; |
| 235 | maybeReportNewChargingStateLocked(); |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 236 | } |
Dianne Hackborn | 6d06826 | 2017-05-16 13:14:37 -0700 | [diff] [blame] | 237 | mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE, |
| 238 | mLastBatterySeq); |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 239 | } |
| 240 | } |
| 241 | } |
Matthew Williams | effacfa | 2014-06-05 20:56:40 -0700 | [diff] [blame] | 242 | |
| 243 | @Override |
Dianne Hackborn | ef3aa6e | 2016-04-29 18:18:08 -0700 | [diff] [blame] | 244 | public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { |
Dianne Hackborn | e9a988c | 2016-05-27 17:59:40 -0700 | [diff] [blame] | 245 | pw.print("Battery: stable power = "); |
Dianne Hackborn | a06ec6a | 2017-02-13 10:08:42 -0800 | [diff] [blame] | 246 | pw.print(mChargeTracker.isOnStablePower()); |
| 247 | pw.print(", not low = "); |
| 248 | pw.println(mChargeTracker.isBatteryNotLow()); |
| 249 | if (mChargeTracker.isMonitoring()) { |
| 250 | pw.print("MONITORING: seq="); |
| 251 | pw.println(mChargeTracker.getSeq()); |
| 252 | } |
Dianne Hackborn | e9a988c | 2016-05-27 17:59:40 -0700 | [diff] [blame] | 253 | pw.print("Tracking "); |
| 254 | pw.print(mTrackedTasks.size()); |
| 255 | pw.println(":"); |
| 256 | for (int i = 0; i < mTrackedTasks.size(); i++) { |
Dianne Hackborn | f9bac16 | 2017-04-20 17:17:48 -0700 | [diff] [blame] | 257 | final JobStatus js = mTrackedTasks.valueAt(i); |
Dianne Hackborn | e9a988c | 2016-05-27 17:59:40 -0700 | [diff] [blame] | 258 | if (!js.shouldDump(filterUid)) { |
| 259 | continue; |
Dianne Hackborn | ef3aa6e | 2016-04-29 18:18:08 -0700 | [diff] [blame] | 260 | } |
Dianne Hackborn | e9a988c | 2016-05-27 17:59:40 -0700 | [diff] [blame] | 261 | pw.print(" #"); |
| 262 | js.printUniqueId(pw); |
| 263 | pw.print(" from "); |
| 264 | UserHandle.formatUid(pw, js.getSourceUid()); |
| 265 | pw.println(); |
Matthew Williams | 759275d | 2014-06-10 12:59:29 -0700 | [diff] [blame] | 266 | } |
Matthew Williams | effacfa | 2014-06-05 20:56:40 -0700 | [diff] [blame] | 267 | } |
Kweku Adams | 85f2fbc | 2017-12-18 12:04:12 -0800 | [diff] [blame] | 268 | |
| 269 | @Override |
| 270 | public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) { |
| 271 | final long token = proto.start(fieldId); |
| 272 | final long mToken = proto.start(StateControllerProto.BATTERY); |
| 273 | |
| 274 | proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER, |
| 275 | mChargeTracker.isOnStablePower()); |
| 276 | proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW, |
| 277 | mChargeTracker.isBatteryNotLow()); |
| 278 | |
| 279 | proto.write(StateControllerProto.BatteryController.IS_MONITORING, |
| 280 | mChargeTracker.isMonitoring()); |
| 281 | proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER, |
| 282 | mChargeTracker.getSeq()); |
| 283 | |
| 284 | for (int i = 0; i < mTrackedTasks.size(); i++) { |
| 285 | final JobStatus js = mTrackedTasks.valueAt(i); |
| 286 | if (!js.shouldDump(filterUid)) { |
| 287 | continue; |
| 288 | } |
| 289 | final long jsToken = proto.start(StateControllerProto.BatteryController.TRACKED_JOBS); |
| 290 | js.writeToShortProto(proto, StateControllerProto.BatteryController.TrackedJob.INFO); |
| 291 | proto.write(StateControllerProto.BatteryController.TrackedJob.SOURCE_UID, |
| 292 | js.getSourceUid()); |
| 293 | proto.end(jsToken); |
| 294 | } |
| 295 | |
| 296 | proto.end(mToken); |
| 297 | proto.end(token); |
| 298 | } |
Matthew Williams | 3d86fd2 | 2014-05-16 18:02:17 -0700 | [diff] [blame] | 299 | } |