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