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.app.AlarmManager; 20 import android.app.PendingIntent; 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.SystemClock; 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 import java.util.ArrayList; 37 import java.util.Iterator; 38 import java.util.List; 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 */ 45 public class BatteryController extends StateController { 46 private static final String TAG = "JobScheduler.Batt"; 47 48 private static final Object sCreationLock = new Object(); 49 private static volatile BatteryController sController; 50 private static final String ACTION_CHARGING_STABLE = 51 "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE"; 52 /** Wait this long after phone is plugged in before doing any work. */ 53 private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes. 54 55 private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); 56 private ChargingTracker mChargeTracker; 57 58 public static BatteryController get(JobSchedulerService taskManagerService) { 59 synchronized (sCreationLock) { 60 if (sController == null) { 61 sController = new BatteryController(taskManagerService, 62 taskManagerService.getContext()); 63 } 64 } 65 return sController; 66 } 67 68 @VisibleForTesting 69 public ChargingTracker getTracker() { 70 return mChargeTracker; 71 } 72 73 @VisibleForTesting 74 public static BatteryController getForTesting(StateChangedListener stateChangedListener, 75 Context context) { 76 return new BatteryController(stateChangedListener, context); 77 } 78 79 private BatteryController(StateChangedListener stateChangedListener, Context context) { 80 super(stateChangedListener, context); 81 mChargeTracker = new ChargingTracker(); 82 mChargeTracker.startTracking(); 83 } 84 85 @Override 86 public void maybeStartTrackingJob(JobStatus taskStatus) { 87 final boolean isOnStablePower = mChargeTracker.isOnStablePower(); 88 if (taskStatus.hasChargingConstraint()) { 89 synchronized (mTrackedTasks) { 90 mTrackedTasks.add(taskStatus); 91 taskStatus.chargingConstraintSatisfied.set(isOnStablePower); 92 } 93 } 94 if (isOnStablePower) { 95 mChargeTracker.setStableChargingAlarm(); 96 } 97 } 98 99 @Override 100 public void maybeStopTrackingJob(JobStatus taskStatus) { 101 if (taskStatus.hasChargingConstraint()) { 102 synchronized (mTrackedTasks) { 103 mTrackedTasks.remove(taskStatus); 104 } 105 } 106 } 107 108 private void maybeReportNewChargingState() { 109 final boolean stablePower = mChargeTracker.isOnStablePower(); 110 if (DEBUG) { 111 Slog.d(TAG, "maybeReportNewChargingState: " + stablePower); 112 } 113 boolean reportChange = false; 114 synchronized (mTrackedTasks) { 115 for (JobStatus ts : mTrackedTasks) { 116 boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower); 117 if (previous != stablePower) { 118 reportChange = true; 119 } 120 } 121 } 122 // Let the scheduler know that state has changed. This may or may not result in an 123 // execution. 124 if (reportChange) { 125 mStateChangedListener.onControllerStateChanged(); 126 } 127 // Also tell the scheduler that any ready jobs should be flushed. 128 if (stablePower) { 129 mStateChangedListener.onRunJobNow(null); 130 } 131 } 132 133 public class ChargingTracker extends BroadcastReceiver { 134 private final AlarmManager mAlarm; 135 private final PendingIntent mStableChargingTriggerIntent; 136 /** 137 * Track whether we're "charging", where charging means that we're ready to commit to 138 * doing work. 139 */ 140 private boolean mCharging; 141 /** Keep track of whether the battery is charged enough that we want to do work. */ 142 private boolean mBatteryHealthy; 143 144 public ChargingTracker() { 145 mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 146 Intent intent = new Intent(ACTION_CHARGING_STABLE); 147 mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 148 } 149 150 public void startTracking() { 151 IntentFilter filter = new IntentFilter(); 152 153 // Battery health. 154 filter.addAction(Intent.ACTION_BATTERY_LOW); 155 filter.addAction(Intent.ACTION_BATTERY_OKAY); 156 // Charging/not charging. 157 filter.addAction(Intent.ACTION_POWER_CONNECTED); 158 filter.addAction(Intent.ACTION_POWER_DISCONNECTED); 159 // Charging stable. 160 filter.addAction(ACTION_CHARGING_STABLE); 161 mContext.registerReceiver(this, filter); 162 163 // Initialise tracker state. 164 BatteryManagerInternal batteryManagerInternal = 165 LocalServices.getService(BatteryManagerInternal.class); 166 mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow(); 167 mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); 168 } 169 170 boolean isOnStablePower() { 171 return mCharging && mBatteryHealthy; 172 } 173 174 @Override 175 public void onReceive(Context context, Intent intent) { 176 onReceiveInternal(intent); 177 } 178 179 @VisibleForTesting 180 public void onReceiveInternal(Intent intent) { 181 final String action = intent.getAction(); 182 if (Intent.ACTION_BATTERY_LOW.equals(action)) { 183 if (DEBUG) { 184 Slog.d(TAG, "Battery life too low to do work. @ " 185 + SystemClock.elapsedRealtime()); 186 } 187 // If we get this action, the battery is discharging => it isn't plugged in so 188 // there's no work to cancel. We track this variable for the case where it is 189 // charging, but hasn't been for long enough to be healthy. 190 mBatteryHealthy = false; 191 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { 192 if (DEBUG) { 193 Slog.d(TAG, "Battery life healthy enough to do work. @ " 194 + SystemClock.elapsedRealtime()); 195 } 196 mBatteryHealthy = true; 197 maybeReportNewChargingState(); 198 } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) { 199 if (DEBUG) { 200 Slog.d(TAG, "Received charging intent, setting alarm for " 201 + STABLE_CHARGING_THRESHOLD_MILLIS); 202 } 203 // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks 204 // here if the user unplugs the phone immediately. 205 setStableChargingAlarm(); 206 mCharging = true; 207 } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) { 208 if (DEBUG) { 209 Slog.d(TAG, "Disconnected from power, cancelling any set alarms."); 210 } 211 // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted. 212 mAlarm.cancel(mStableChargingTriggerIntent); 213 mCharging = false; 214 maybeReportNewChargingState(); 215 }else if (ACTION_CHARGING_STABLE.equals(action)) { 216 // Here's where we actually do the notify for a task being ready. 217 if (DEBUG) { 218 Slog.d(TAG, "Stable charging fired @ " + SystemClock.elapsedRealtime() 219 + " charging: " + mCharging); 220 } 221 if (mCharging) { // Should never receive this intent if mCharging is false. 222 maybeReportNewChargingState(); 223 } 224 } 225 } 226 227 void setStableChargingAlarm() { 228 final long alarmTriggerElapsed = 229 SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS; 230 if (DEBUG) { 231 Slog.d(TAG, "Setting stable alarm to go off in " + 232 (STABLE_CHARGING_THRESHOLD_MILLIS / 1000) + "s"); 233 } 234 mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTriggerElapsed, 235 mStableChargingTriggerIntent); 236 } 237 } 238 239 @Override 240 public void dumpControllerState(PrintWriter pw) { 241 pw.println("Batt."); 242 pw.println("Stable power: " + mChargeTracker.isOnStablePower()); 243 synchronized (mTrackedTasks) { 244 Iterator<JobStatus> it = mTrackedTasks.iterator(); 245 if (it.hasNext()) { 246 pw.print(String.valueOf(it.next().hashCode())); 247 } 248 while (it.hasNext()) { 249 pw.print("," + String.valueOf(it.next().hashCode())); 250 } 251 pw.println(); 252 } 253 } 254 } 255