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 17 package com.android.server.job.controllers; 18 19 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 20 21 import android.content.BroadcastReceiver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentFilter; 25 import android.os.BatteryManager; 26 import android.os.BatteryManagerInternal; 27 import android.os.UserHandle; 28 import android.util.ArraySet; 29 import android.util.IndentingPrintWriter; 30 import android.util.Log; 31 import android.util.Slog; 32 import android.util.proto.ProtoOutputStream; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.server.LocalServices; 36 import com.android.server.job.JobSchedulerService; 37 import com.android.server.job.StateControllerProto; 38 39 import java.util.function.Predicate; 40 41 /** 42 * Simple controller that tracks whether the phone is charging or not. The phone is considered to 43 * be charging when it's been plugged in for more than two minutes, and the system has broadcast 44 * ACTION_BATTERY_OK. 45 */ 46 public final class BatteryController extends RestrictingController { 47 private static final String TAG = "JobScheduler.Battery"; 48 private static final boolean DEBUG = JobSchedulerService.DEBUG 49 || Log.isLoggable(TAG, Log.DEBUG); 50 51 private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>(); 52 private ChargingTracker mChargeTracker; 53 54 @VisibleForTesting getTracker()55 public ChargingTracker getTracker() { 56 return mChargeTracker; 57 } 58 BatteryController(JobSchedulerService service)59 public BatteryController(JobSchedulerService service) { 60 super(service); 61 mChargeTracker = new ChargingTracker(); 62 mChargeTracker.startTracking(); 63 } 64 65 @Override maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob)66 public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) { 67 if (taskStatus.hasPowerConstraint()) { 68 final long nowElapsed = sElapsedRealtimeClock.millis(); 69 mTrackedTasks.add(taskStatus); 70 taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY); 71 taskStatus.setChargingConstraintSatisfied(nowElapsed, mChargeTracker.isOnStablePower()); 72 taskStatus.setBatteryNotLowConstraintSatisfied( 73 nowElapsed, mChargeTracker.isBatteryNotLow()); 74 } 75 } 76 77 @Override startTrackingRestrictedJobLocked(JobStatus jobStatus)78 public void startTrackingRestrictedJobLocked(JobStatus jobStatus) { 79 maybeStartTrackingJobLocked(jobStatus, null); 80 } 81 82 @Override maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate)83 public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) { 84 if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) { 85 mTrackedTasks.remove(taskStatus); 86 } 87 } 88 89 @Override stopTrackingRestrictedJobLocked(JobStatus jobStatus)90 public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) { 91 if (!jobStatus.hasPowerConstraint()) { 92 maybeStopTrackingJobLocked(jobStatus, null, false); 93 } 94 } 95 maybeReportNewChargingStateLocked()96 private void maybeReportNewChargingStateLocked() { 97 final boolean stablePower = mChargeTracker.isOnStablePower(); 98 final boolean batteryNotLow = mChargeTracker.isBatteryNotLow(); 99 if (DEBUG) { 100 Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower); 101 } 102 final long nowElapsed = sElapsedRealtimeClock.millis(); 103 boolean reportChange = false; 104 for (int i = mTrackedTasks.size() - 1; i >= 0; i--) { 105 final JobStatus ts = mTrackedTasks.valueAt(i); 106 boolean previous = ts.setChargingConstraintSatisfied(nowElapsed, stablePower); 107 if (previous != stablePower) { 108 reportChange = true; 109 } 110 previous = ts.setBatteryNotLowConstraintSatisfied(nowElapsed, batteryNotLow); 111 if (previous != batteryNotLow) { 112 reportChange = true; 113 } 114 } 115 if (stablePower || batteryNotLow) { 116 // If one of our conditions has been satisfied, always schedule any newly ready jobs. 117 mStateChangedListener.onRunJobNow(null); 118 } else if (reportChange) { 119 // Otherwise, just let the job scheduler know the state has changed and take care of it 120 // as it thinks is best. 121 mStateChangedListener.onControllerStateChanged(); 122 } 123 } 124 125 public final class ChargingTracker extends BroadcastReceiver { 126 /** 127 * Track whether we're "charging", where charging means that we're ready to commit to 128 * doing work. 129 */ 130 private boolean mCharging; 131 /** Keep track of whether the battery is charged enough that we want to do work. */ 132 private boolean mBatteryHealthy; 133 /** Sequence number of last broadcast. */ 134 private int mLastBatterySeq = -1; 135 136 private BroadcastReceiver mMonitor; 137 ChargingTracker()138 public ChargingTracker() { 139 } 140 startTracking()141 public void startTracking() { 142 IntentFilter filter = new IntentFilter(); 143 144 // Battery health. 145 filter.addAction(Intent.ACTION_BATTERY_LOW); 146 filter.addAction(Intent.ACTION_BATTERY_OKAY); 147 // Charging/not charging. 148 filter.addAction(BatteryManager.ACTION_CHARGING); 149 filter.addAction(BatteryManager.ACTION_DISCHARGING); 150 mContext.registerReceiver(this, filter); 151 152 // Initialise tracker state. 153 BatteryManagerInternal batteryManagerInternal = 154 LocalServices.getService(BatteryManagerInternal.class); 155 mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow(); 156 mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); 157 } 158 setMonitorBatteryLocked(boolean enabled)159 public void setMonitorBatteryLocked(boolean enabled) { 160 if (enabled) { 161 if (mMonitor == null) { 162 mMonitor = new BroadcastReceiver() { 163 @Override public void onReceive(Context context, Intent intent) { 164 ChargingTracker.this.onReceive(context, intent); 165 } 166 }; 167 IntentFilter filter = new IntentFilter(); 168 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 169 mContext.registerReceiver(mMonitor, filter); 170 } 171 } else { 172 if (mMonitor != null) { 173 mContext.unregisterReceiver(mMonitor); 174 mMonitor = null; 175 } 176 } 177 } 178 isOnStablePower()179 public boolean isOnStablePower() { 180 return mCharging && mBatteryHealthy; 181 } 182 isBatteryNotLow()183 public boolean isBatteryNotLow() { 184 return mBatteryHealthy; 185 } 186 isMonitoring()187 public boolean isMonitoring() { 188 return mMonitor != null; 189 } 190 getSeq()191 public int getSeq() { 192 return mLastBatterySeq; 193 } 194 195 @Override onReceive(Context context, Intent intent)196 public void onReceive(Context context, Intent intent) { 197 onReceiveInternal(intent); 198 } 199 200 @VisibleForTesting onReceiveInternal(Intent intent)201 public void onReceiveInternal(Intent intent) { 202 synchronized (mLock) { 203 final String action = intent.getAction(); 204 if (Intent.ACTION_BATTERY_LOW.equals(action)) { 205 if (DEBUG) { 206 Slog.d(TAG, "Battery life too low to do work. @ " 207 + sElapsedRealtimeClock.millis()); 208 } 209 // If we get this action, the battery is discharging => it isn't plugged in so 210 // there's no work to cancel. We track this variable for the case where it is 211 // charging, but hasn't been for long enough to be healthy. 212 mBatteryHealthy = false; 213 maybeReportNewChargingStateLocked(); 214 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { 215 if (DEBUG) { 216 Slog.d(TAG, "Battery life healthy enough to do work. @ " 217 + sElapsedRealtimeClock.millis()); 218 } 219 mBatteryHealthy = true; 220 maybeReportNewChargingStateLocked(); 221 } else if (BatteryManager.ACTION_CHARGING.equals(action)) { 222 if (DEBUG) { 223 Slog.d(TAG, "Received charging intent, fired @ " 224 + sElapsedRealtimeClock.millis()); 225 } 226 mCharging = true; 227 maybeReportNewChargingStateLocked(); 228 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { 229 if (DEBUG) { 230 Slog.d(TAG, "Disconnected from power."); 231 } 232 mCharging = false; 233 maybeReportNewChargingStateLocked(); 234 } 235 mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE, 236 mLastBatterySeq); 237 } 238 } 239 } 240 241 @Override dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate)242 public void dumpControllerStateLocked(IndentingPrintWriter pw, 243 Predicate<JobStatus> predicate) { 244 pw.println("Stable power: " + mChargeTracker.isOnStablePower()); 245 pw.println("Not low: " + mChargeTracker.isBatteryNotLow()); 246 247 if (mChargeTracker.isMonitoring()) { 248 pw.print("MONITORING: seq="); 249 pw.println(mChargeTracker.getSeq()); 250 } 251 pw.println(); 252 253 for (int i = 0; i < mTrackedTasks.size(); i++) { 254 final JobStatus js = mTrackedTasks.valueAt(i); 255 if (!predicate.test(js)) { 256 continue; 257 } 258 pw.print("#"); 259 js.printUniqueId(pw); 260 pw.print(" from "); 261 UserHandle.formatUid(pw, js.getSourceUid()); 262 pw.println(); 263 } 264 } 265 266 @Override dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)267 public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, 268 Predicate<JobStatus> predicate) { 269 final long token = proto.start(fieldId); 270 final long mToken = proto.start(StateControllerProto.BATTERY); 271 272 proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER, 273 mChargeTracker.isOnStablePower()); 274 proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW, 275 mChargeTracker.isBatteryNotLow()); 276 277 proto.write(StateControllerProto.BatteryController.IS_MONITORING, 278 mChargeTracker.isMonitoring()); 279 proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER, 280 mChargeTracker.getSeq()); 281 282 for (int i = 0; i < mTrackedTasks.size(); i++) { 283 final JobStatus js = mTrackedTasks.valueAt(i); 284 if (!predicate.test(js)) { 285 continue; 286 } 287 final long jsToken = proto.start(StateControllerProto.BatteryController.TRACKED_JOBS); 288 js.writeToShortProto(proto, StateControllerProto.BatteryController.TrackedJob.INFO); 289 proto.write(StateControllerProto.BatteryController.TrackedJob.SOURCE_UID, 290 js.getSourceUid()); 291 proto.end(jsToken); 292 } 293 294 proto.end(mToken); 295 proto.end(token); 296 } 297 } 298