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