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 android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.os.BatteryManager; 24 import android.os.BatteryManagerInternal; 25 import android.os.SystemClock; 26 import android.os.UserHandle; 27 import android.util.ArraySet; 28 import android.util.Slog; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.server.LocalServices; 32 import com.android.server.job.JobSchedulerService; 33 import com.android.server.job.StateChangedListener; 34 35 import java.io.PrintWriter; 36 37 /** 38 * Simple controller that tracks whether the phone is charging or not. The phone is considered to 39 * be charging when it's been plugged in for more than two minutes, and the system has broadcast 40 * ACTION_BATTERY_OK. 41 */ 42 public final class BatteryController extends StateController { 43 private static final String TAG = "JobScheduler.Batt"; 44 45 private static final Object sCreationLock = new Object(); 46 private static volatile BatteryController sController; 47 48 private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>(); 49 private ChargingTracker mChargeTracker; 50 get(JobSchedulerService taskManagerService)51 public static BatteryController get(JobSchedulerService taskManagerService) { 52 synchronized (sCreationLock) { 53 if (sController == null) { 54 sController = new BatteryController(taskManagerService, 55 taskManagerService.getContext(), taskManagerService.getLock()); 56 } 57 } 58 return sController; 59 } 60 61 @VisibleForTesting getTracker()62 public ChargingTracker getTracker() { 63 return mChargeTracker; 64 } 65 66 @VisibleForTesting getForTesting(StateChangedListener stateChangedListener, Context context)67 public static BatteryController getForTesting(StateChangedListener stateChangedListener, 68 Context context) { 69 return new BatteryController(stateChangedListener, context, new Object()); 70 } 71 BatteryController(StateChangedListener stateChangedListener, Context context, Object lock)72 private BatteryController(StateChangedListener stateChangedListener, Context context, 73 Object lock) { 74 super(stateChangedListener, context, lock); 75 mChargeTracker = new ChargingTracker(); 76 mChargeTracker.startTracking(); 77 } 78 79 @Override maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob)80 public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) { 81 if (taskStatus.hasPowerConstraint()) { 82 mTrackedTasks.add(taskStatus); 83 taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY); 84 taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower()); 85 taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow()); 86 } 87 } 88 89 @Override maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate)90 public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) { 91 if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) { 92 mTrackedTasks.remove(taskStatus); 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 boolean reportChange = false; 103 for (int i = mTrackedTasks.size() - 1; i >= 0; i--) { 104 final JobStatus ts = mTrackedTasks.valueAt(i); 105 boolean previous = ts.setChargingConstraintSatisfied(stablePower); 106 if (previous != stablePower) { 107 reportChange = true; 108 } 109 previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow); 110 if (previous != batteryNotLow) { 111 reportChange = true; 112 } 113 } 114 if (stablePower || batteryNotLow) { 115 // If one of our conditions has been satisfied, always schedule any newly ready jobs. 116 mStateChangedListener.onRunJobNow(null); 117 } else if (reportChange) { 118 // Otherwise, just let the job scheduler know the state has changed and take care of it 119 // as it thinks is best. 120 mStateChangedListener.onControllerStateChanged(); 121 } 122 } 123 124 public final class ChargingTracker extends BroadcastReceiver { 125 /** 126 * Track whether we're "charging", where charging means that we're ready to commit to 127 * doing work. 128 */ 129 private boolean mCharging; 130 /** Keep track of whether the battery is charged enough that we want to do work. */ 131 private boolean mBatteryHealthy; 132 /** Sequence number of last broadcast. */ 133 private int mLastBatterySeq = -1; 134 135 private BroadcastReceiver mMonitor; 136 ChargingTracker()137 public ChargingTracker() { 138 } 139 startTracking()140 public void startTracking() { 141 IntentFilter filter = new IntentFilter(); 142 143 // Battery health. 144 filter.addAction(Intent.ACTION_BATTERY_LOW); 145 filter.addAction(Intent.ACTION_BATTERY_OKAY); 146 // Charging/not charging. 147 filter.addAction(BatteryManager.ACTION_CHARGING); 148 filter.addAction(BatteryManager.ACTION_DISCHARGING); 149 mContext.registerReceiver(this, filter); 150 151 // Initialise tracker state. 152 BatteryManagerInternal batteryManagerInternal = 153 LocalServices.getService(BatteryManagerInternal.class); 154 mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow(); 155 mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); 156 } 157 setMonitorBatteryLocked(boolean enabled)158 public void setMonitorBatteryLocked(boolean enabled) { 159 if (enabled) { 160 if (mMonitor == null) { 161 mMonitor = new BroadcastReceiver() { 162 @Override public void onReceive(Context context, Intent intent) { 163 ChargingTracker.this.onReceive(context, intent); 164 } 165 }; 166 IntentFilter filter = new IntentFilter(); 167 filter.addAction(Intent.ACTION_BATTERY_CHANGED); 168 mContext.registerReceiver(mMonitor, filter); 169 } 170 } else { 171 if (mMonitor != null) { 172 mContext.unregisterReceiver(mMonitor); 173 mMonitor = null; 174 } 175 } 176 } 177 isOnStablePower()178 public boolean isOnStablePower() { 179 return mCharging && mBatteryHealthy; 180 } 181 isBatteryNotLow()182 public boolean isBatteryNotLow() { 183 return mBatteryHealthy; 184 } 185 isMonitoring()186 public boolean isMonitoring() { 187 return mMonitor != null; 188 } 189 getSeq()190 public int getSeq() { 191 return mLastBatterySeq; 192 } 193 194 @Override onReceive(Context context, Intent intent)195 public void onReceive(Context context, Intent intent) { 196 onReceiveInternal(intent); 197 } 198 199 @VisibleForTesting onReceiveInternal(Intent intent)200 public void onReceiveInternal(Intent intent) { 201 synchronized (mLock) { 202 final String action = intent.getAction(); 203 if (Intent.ACTION_BATTERY_LOW.equals(action)) { 204 if (DEBUG) { 205 Slog.d(TAG, "Battery life too low to do work. @ " 206 + SystemClock.elapsedRealtime()); 207 } 208 // If we get this action, the battery is discharging => it isn't plugged in so 209 // there's no work to cancel. We track this variable for the case where it is 210 // charging, but hasn't been for long enough to be healthy. 211 mBatteryHealthy = false; 212 maybeReportNewChargingStateLocked(); 213 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { 214 if (DEBUG) { 215 Slog.d(TAG, "Battery life healthy enough to do work. @ " 216 + SystemClock.elapsedRealtime()); 217 } 218 mBatteryHealthy = true; 219 maybeReportNewChargingStateLocked(); 220 } else if (BatteryManager.ACTION_CHARGING.equals(action)) { 221 if (DEBUG) { 222 Slog.d(TAG, "Received charging intent, fired @ " 223 + SystemClock.elapsedRealtime()); 224 } 225 mCharging = true; 226 maybeReportNewChargingStateLocked(); 227 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { 228 if (DEBUG) { 229 Slog.d(TAG, "Disconnected from power."); 230 } 231 mCharging = false; 232 maybeReportNewChargingStateLocked(); 233 } 234 mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE, 235 mLastBatterySeq); 236 } 237 } 238 } 239 240 @Override dumpControllerStateLocked(PrintWriter pw, int filterUid)241 public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { 242 pw.print("Battery: stable power = "); 243 pw.print(mChargeTracker.isOnStablePower()); 244 pw.print(", not low = "); 245 pw.println(mChargeTracker.isBatteryNotLow()); 246 if (mChargeTracker.isMonitoring()) { 247 pw.print("MONITORING: seq="); 248 pw.println(mChargeTracker.getSeq()); 249 } 250 pw.print("Tracking "); 251 pw.print(mTrackedTasks.size()); 252 pw.println(":"); 253 for (int i = 0; i < mTrackedTasks.size(); i++) { 254 final JobStatus js = mTrackedTasks.valueAt(i); 255 if (!js.shouldDump(filterUid)) { 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