BatteryController.java revision 2139276ce8b54aba5faa858ca69ed5f01445c269
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.Context; 23import android.content.Intent; 24import android.content.IntentFilter; 25import android.os.BatteryManager; 26import android.os.BatteryManagerInternal; 27import android.os.SystemClock; 28import android.util.Slog; 29 30import com.android.internal.annotations.VisibleForTesting; 31import com.android.server.LocalServices; 32import com.android.server.job.JobSchedulerService; 33import com.android.server.job.StateChangedListener; 34 35import java.io.PrintWriter; 36import java.util.ArrayList; 37import java.util.Iterator; 38import 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 */ 45public 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 if (taskStatus.hasChargingConstraint()) { 88 final boolean isOnStablePower = mChargeTracker.isOnStablePower(); 89 synchronized (mTrackedTasks) { 90 mTrackedTasks.add(taskStatus); 91 taskStatus.chargingConstraintSatisfied.set(isOnStablePower); 92 } 93 if (isOnStablePower) { 94 mStateChangedListener.onControllerStateChanged(); 95 } 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 if (reportChange) { 123 mStateChangedListener.onControllerStateChanged(); 124 } 125 } 126 127 public class ChargingTracker extends BroadcastReceiver { 128 private final AlarmManager mAlarm; 129 private final PendingIntent mStableChargingTriggerIntent; 130 /** 131 * Track whether we're "charging", where charging means that we're ready to commit to 132 * doing work. 133 */ 134 private boolean mCharging; 135 /** Keep track of whether the battery is charged enough that we want to do work. */ 136 private boolean mBatteryHealthy; 137 138 public ChargingTracker() { 139 mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); 140 Intent intent = new Intent(ACTION_CHARGING_STABLE); 141 mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 142 } 143 144 public void startTracking() { 145 IntentFilter filter = new IntentFilter(); 146 147 // Battery health. 148 filter.addAction(Intent.ACTION_BATTERY_LOW); 149 filter.addAction(Intent.ACTION_BATTERY_OKAY); 150 // Charging/not charging. 151 filter.addAction(Intent.ACTION_POWER_CONNECTED); 152 filter.addAction(Intent.ACTION_POWER_DISCONNECTED); 153 // Charging stable. 154 filter.addAction(ACTION_CHARGING_STABLE); 155 mContext.registerReceiver(this, filter); 156 157 // Initialise tracker state. 158 BatteryManagerInternal batteryManagerInternal = 159 LocalServices.getService(BatteryManagerInternal.class); 160 mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow(); 161 mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); 162 } 163 164 boolean isOnStablePower() { 165 return mCharging && mBatteryHealthy; 166 } 167 168 @Override 169 public void onReceive(Context context, Intent intent) { 170 onReceiveInternal(intent); 171 } 172 173 @VisibleForTesting 174 public void onReceiveInternal(Intent intent) { 175 final String action = intent.getAction(); 176 if (Intent.ACTION_BATTERY_LOW.equals(action)) { 177 if (DEBUG) { 178 Slog.d(TAG, "Battery life too low to do work. @ " 179 + SystemClock.elapsedRealtime()); 180 } 181 // If we get this action, the battery is discharging => it isn't plugged in so 182 // there's no work to cancel. We track this variable for the case where it is 183 // charging, but hasn't been for long enough to be healthy. 184 mBatteryHealthy = false; 185 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { 186 if (DEBUG) { 187 Slog.d(TAG, "Battery life healthy enough to do work. @ " 188 + SystemClock.elapsedRealtime()); 189 } 190 mBatteryHealthy = true; 191 maybeReportNewChargingState(); 192 } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) { 193 if (DEBUG) { 194 Slog.d(TAG, "Received charging intent, setting alarm for " 195 + STABLE_CHARGING_THRESHOLD_MILLIS); 196 } 197 // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks 198 // here if the user unplugs the phone immediately. 199 mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, 200 SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS, 201 mStableChargingTriggerIntent); 202 mCharging = true; 203 } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) { 204 if (DEBUG) { 205 Slog.d(TAG, "Disconnected from power, cancelling any set alarms."); 206 } 207 // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted. 208 mAlarm.cancel(mStableChargingTriggerIntent); 209 mCharging = false; 210 maybeReportNewChargingState(); 211 }else if (ACTION_CHARGING_STABLE.equals(action)) { 212 // Here's where we actually do the notify for a task being ready. 213 if (DEBUG) { 214 Slog.d(TAG, "Battery connected fired @ " + SystemClock.elapsedRealtime() 215 + " charging: " + mCharging); 216 } 217 if (mCharging) { // Should never receive this intent if mCharging is false. 218 maybeReportNewChargingState(); 219 } 220 } 221 } 222 } 223 224 @Override 225 public void dumpControllerState(PrintWriter pw) { 226 pw.println("Batt."); 227 pw.println("Stable power: " + mChargeTracker.isOnStablePower()); 228 synchronized (mTrackedTasks) { 229 Iterator<JobStatus> it = mTrackedTasks.iterator(); 230 if (it.hasNext()) { 231 pw.print(String.valueOf(it.next().hashCode())); 232 } 233 while (it.hasNext()) { 234 pw.print("," + String.valueOf(it.next().hashCode())); 235 } 236 pw.println(); 237 } 238 } 239} 240