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 51 private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); 52 private ChargingTracker mChargeTracker; 53 54 public static BatteryController get(JobSchedulerService taskManagerService) { 55 synchronized (sCreationLock) { 56 if (sController == null) { 57 sController = new BatteryController(taskManagerService, 58 taskManagerService.getContext()); 59 } 60 } 61 return sController; 62 } 63 64 @VisibleForTesting 65 public ChargingTracker getTracker() { 66 return mChargeTracker; 67 } 68 69 @VisibleForTesting 70 public static BatteryController getForTesting(StateChangedListener stateChangedListener, 71 Context context) { 72 return new BatteryController(stateChangedListener, context); 73 } 74 75 private BatteryController(StateChangedListener stateChangedListener, Context context) { 76 super(stateChangedListener, context); 77 mChargeTracker = new ChargingTracker(); 78 mChargeTracker.startTracking(); 79 } 80 81 @Override 82 public void maybeStartTrackingJob(JobStatus taskStatus) { 83 final boolean isOnStablePower = mChargeTracker.isOnStablePower(); 84 if (taskStatus.hasChargingConstraint()) { 85 synchronized (mTrackedTasks) { 86 mTrackedTasks.add(taskStatus); 87 taskStatus.chargingConstraintSatisfied.set(isOnStablePower); 88 } 89 } 90 } 91 92 @Override 93 public void maybeStopTrackingJob(JobStatus taskStatus) { 94 if (taskStatus.hasChargingConstraint()) { 95 synchronized (mTrackedTasks) { 96 mTrackedTasks.remove(taskStatus); 97 } 98 } 99 } 100 101 private void maybeReportNewChargingState() { 102 final boolean stablePower = mChargeTracker.isOnStablePower(); 103 if (DEBUG) { 104 Slog.d(TAG, "maybeReportNewChargingState: " + stablePower); 105 } 106 boolean reportChange = false; 107 synchronized (mTrackedTasks) { 108 for (JobStatus ts : mTrackedTasks) { 109 boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower); 110 if (previous != stablePower) { 111 reportChange = true; 112 } 113 } 114 } 115 // Let the scheduler know that state has changed. This may or may not result in an 116 // execution. 117 if (reportChange) { 118 mStateChangedListener.onControllerStateChanged(); 119 } 120 // Also tell the scheduler that any ready jobs should be flushed. 121 if (stablePower) { 122 mStateChangedListener.onRunJobNow(null); 123 } 124 } 125 126 public class ChargingTracker extends BroadcastReceiver { 127 /** 128 * Track whether we're "charging", where charging means that we're ready to commit to 129 * doing work. 130 */ 131 private boolean mCharging; 132 /** Keep track of whether the battery is charged enough that we want to do work. */ 133 private boolean mBatteryHealthy; 134 135 public ChargingTracker() { 136 } 137 138 public void startTracking() { 139 IntentFilter filter = new IntentFilter(); 140 141 // Battery health. 142 filter.addAction(Intent.ACTION_BATTERY_LOW); 143 filter.addAction(Intent.ACTION_BATTERY_OKAY); 144 // Charging/not charging. 145 filter.addAction(BatteryManager.ACTION_CHARGING); 146 filter.addAction(BatteryManager.ACTION_DISCHARGING); 147 mContext.registerReceiver(this, filter); 148 149 // Initialise tracker state. 150 BatteryManagerInternal batteryManagerInternal = 151 LocalServices.getService(BatteryManagerInternal.class); 152 mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow(); 153 mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); 154 } 155 156 boolean isOnStablePower() { 157 return mCharging && mBatteryHealthy; 158 } 159 160 @Override 161 public void onReceive(Context context, Intent intent) { 162 onReceiveInternal(intent); 163 } 164 165 @VisibleForTesting 166 public void onReceiveInternal(Intent intent) { 167 final String action = intent.getAction(); 168 if (Intent.ACTION_BATTERY_LOW.equals(action)) { 169 if (DEBUG) { 170 Slog.d(TAG, "Battery life too low to do work. @ " 171 + SystemClock.elapsedRealtime()); 172 } 173 // If we get this action, the battery is discharging => it isn't plugged in so 174 // there's no work to cancel. We track this variable for the case where it is 175 // charging, but hasn't been for long enough to be healthy. 176 mBatteryHealthy = false; 177 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { 178 if (DEBUG) { 179 Slog.d(TAG, "Battery life healthy enough to do work. @ " 180 + SystemClock.elapsedRealtime()); 181 } 182 mBatteryHealthy = true; 183 maybeReportNewChargingState(); 184 } else if (BatteryManager.ACTION_CHARGING.equals(action)) { 185 if (DEBUG) { 186 Slog.d(TAG, "Received charging intent, fired @ " 187 + SystemClock.elapsedRealtime()); 188 } 189 mCharging = true; 190 maybeReportNewChargingState(); 191 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { 192 if (DEBUG) { 193 Slog.d(TAG, "Disconnected from power."); 194 } 195 mCharging = false; 196 maybeReportNewChargingState(); 197 } 198 } 199 } 200 201 @Override 202 public void dumpControllerState(PrintWriter pw) { 203 pw.println("Batt."); 204 pw.println("Stable power: " + mChargeTracker.isOnStablePower()); 205 synchronized (mTrackedTasks) { 206 Iterator<JobStatus> it = mTrackedTasks.iterator(); 207 if (it.hasNext()) { 208 pw.print(String.valueOf(it.next().hashCode())); 209 } 210 while (it.hasNext()) { 211 pw.print("," + String.valueOf(it.next().hashCode())); 212 } 213 pw.println(); 214 } 215 } 216 } 217