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