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.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.os.BatteryManager; 24 import android.os.BatteryManagerInternal; 25 import android.os.SystemClock; 26 import android.os.UserHandle; 27 import android.util.Slog; 28 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.server.LocalServices; 31 import com.android.server.job.JobSchedulerService; 32 import com.android.server.job.StateChangedListener; 33 34 import java.io.PrintWriter; 35 import java.util.ArrayList; 36 import java.util.Iterator; 37 import java.util.List; 38 39 /** 40 * Simple controller that tracks whether the phone is charging or not. The phone is considered to 41 * be charging when it's been plugged in for more than two minutes, and the system has broadcast 42 * ACTION_BATTERY_OK. 43 */ 44 public class BatteryController extends StateController { 45 private static final String TAG = "JobScheduler.Batt"; 46 47 private static final Object sCreationLock = new Object(); 48 private static volatile BatteryController sController; 49 50 private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>(); 51 private ChargingTracker mChargeTracker; 52 53 public static BatteryController get(JobSchedulerService taskManagerService) { 54 synchronized (sCreationLock) { 55 if (sController == null) { 56 sController = new BatteryController(taskManagerService, 57 taskManagerService.getContext(), taskManagerService.getLock()); 58 } 59 } 60 return sController; 61 } 62 63 @VisibleForTesting 64 public ChargingTracker getTracker() { 65 return mChargeTracker; 66 } 67 68 @VisibleForTesting 69 public static BatteryController getForTesting(StateChangedListener stateChangedListener, 70 Context context) { 71 return new BatteryController(stateChangedListener, context, new Object()); 72 } 73 74 private BatteryController(StateChangedListener stateChangedListener, Context context, 75 Object lock) { 76 super(stateChangedListener, context, lock); 77 mChargeTracker = new ChargingTracker(); 78 mChargeTracker.startTracking(); 79 } 80 81 @Override 82 public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) { 83 final boolean isOnStablePower = mChargeTracker.isOnStablePower(); 84 if (taskStatus.hasChargingConstraint()) { 85 mTrackedTasks.add(taskStatus); 86 taskStatus.setChargingConstraintSatisfied(isOnStablePower); 87 } 88 } 89 90 @Override 91 public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) { 92 if (taskStatus.hasChargingConstraint()) { 93 mTrackedTasks.remove(taskStatus); 94 } 95 } 96 97 private void maybeReportNewChargingState() { 98 final boolean stablePower = mChargeTracker.isOnStablePower(); 99 if (DEBUG) { 100 Slog.d(TAG, "maybeReportNewChargingState: " + stablePower); 101 } 102 boolean reportChange = false; 103 synchronized (mLock) { 104 for (JobStatus ts : mTrackedTasks) { 105 boolean previous = ts.setChargingConstraintSatisfied(stablePower); 106 if (previous != stablePower) { 107 reportChange = true; 108 } 109 } 110 } 111 // Let the scheduler know that state has changed. This may or may not result in an 112 // execution. 113 if (reportChange) { 114 mStateChangedListener.onControllerStateChanged(); 115 } 116 // Also tell the scheduler that any ready jobs should be flushed. 117 if (stablePower) { 118 mStateChangedListener.onRunJobNow(null); 119 } 120 } 121 122 public class ChargingTracker extends BroadcastReceiver { 123 /** 124 * Track whether we're "charging", where charging means that we're ready to commit to 125 * doing work. 126 */ 127 private boolean mCharging; 128 /** Keep track of whether the battery is charged enough that we want to do work. */ 129 private boolean mBatteryHealthy; 130 131 public ChargingTracker() { 132 } 133 134 public void startTracking() { 135 IntentFilter filter = new IntentFilter(); 136 137 // Battery health. 138 filter.addAction(Intent.ACTION_BATTERY_LOW); 139 filter.addAction(Intent.ACTION_BATTERY_OKAY); 140 // Charging/not charging. 141 filter.addAction(BatteryManager.ACTION_CHARGING); 142 filter.addAction(BatteryManager.ACTION_DISCHARGING); 143 mContext.registerReceiver(this, filter); 144 145 // Initialise tracker state. 146 BatteryManagerInternal batteryManagerInternal = 147 LocalServices.getService(BatteryManagerInternal.class); 148 mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow(); 149 mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY); 150 } 151 152 boolean isOnStablePower() { 153 return mCharging && mBatteryHealthy; 154 } 155 156 @Override 157 public void onReceive(Context context, Intent intent) { 158 onReceiveInternal(intent); 159 } 160 161 @VisibleForTesting 162 public void onReceiveInternal(Intent intent) { 163 final String action = intent.getAction(); 164 if (Intent.ACTION_BATTERY_LOW.equals(action)) { 165 if (DEBUG) { 166 Slog.d(TAG, "Battery life too low to do work. @ " 167 + SystemClock.elapsedRealtime()); 168 } 169 // If we get this action, the battery is discharging => it isn't plugged in so 170 // there's no work to cancel. We track this variable for the case where it is 171 // charging, but hasn't been for long enough to be healthy. 172 mBatteryHealthy = false; 173 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) { 174 if (DEBUG) { 175 Slog.d(TAG, "Battery life healthy enough to do work. @ " 176 + SystemClock.elapsedRealtime()); 177 } 178 mBatteryHealthy = true; 179 maybeReportNewChargingState(); 180 } else if (BatteryManager.ACTION_CHARGING.equals(action)) { 181 if (DEBUG) { 182 Slog.d(TAG, "Received charging intent, fired @ " 183 + SystemClock.elapsedRealtime()); 184 } 185 mCharging = true; 186 maybeReportNewChargingState(); 187 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) { 188 if (DEBUG) { 189 Slog.d(TAG, "Disconnected from power."); 190 } 191 mCharging = false; 192 maybeReportNewChargingState(); 193 } 194 } 195 } 196 197 @Override 198 public void dumpControllerStateLocked(PrintWriter pw, int filterUid) { 199 pw.print("Battery: stable power = "); 200 pw.println(mChargeTracker.isOnStablePower()); 201 pw.print("Tracking "); 202 pw.print(mTrackedTasks.size()); 203 pw.println(":"); 204 for (int i = 0; i < mTrackedTasks.size(); i++) { 205 final JobStatus js = mTrackedTasks.get(i); 206 if (!js.shouldDump(filterUid)) { 207 continue; 208 } 209 pw.print(" #"); 210 js.printUniqueId(pw); 211 pw.print(" from "); 212 UserHandle.formatUid(pw, js.getSourceUid()); 213 pw.println(); 214 } 215 } 216 } 217