blob: 8d3d116e6631bae725cdab891111143e36333e2b [file] [log] [blame]
Matthew Williams3d86fd22014-05-16 18:02:17 -07001/*
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
Christopher Tate7060b042014-06-09 19:50:00 -070017package com.android.server.job.controllers;
Matthew Williams3d86fd22014-05-16 18:02:17 -070018
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -070019import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
20
Matthew Williams3d86fd22014-05-16 18:02:17 -070021import android.content.BroadcastReceiver;
Matthew Williams3d86fd22014-05-16 18:02:17 -070022import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.os.BatteryManager;
Jeff Brown21392762014-06-13 19:00:36 -070026import android.os.BatteryManagerInternal;
Dianne Hackborne9a988c2016-05-27 17:59:40 -070027import android.os.UserHandle;
Dianne Hackbornf9bac162017-04-20 17:17:48 -070028import android.util.ArraySet;
Matthew Williams3d86fd22014-05-16 18:02:17 -070029import android.util.Slog;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080030import android.util.proto.ProtoOutputStream;
Matthew Williams3d86fd22014-05-16 18:02:17 -070031
32import com.android.internal.annotations.VisibleForTesting;
Jeff Brown21392762014-06-13 19:00:36 -070033import com.android.server.LocalServices;
Christopher Tate7060b042014-06-09 19:50:00 -070034import com.android.server.job.JobSchedulerService;
35import com.android.server.job.StateChangedListener;
Kweku Adams85f2fbc2017-12-18 12:04:12 -080036import com.android.server.job.StateControllerProto;
Matthew Williams3d86fd22014-05-16 18:02:17 -070037
Matthew Williamseffacfa2014-06-05 20:56:40 -070038import java.io.PrintWriter;
Matthew Williams3d86fd22014-05-16 18:02:17 -070039
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 */
Dianne Hackborn6466c1c2017-06-13 10:33:19 -070045public final class BatteryController extends StateController {
Matthew Williams759275d2014-06-10 12:59:29 -070046 private static final String TAG = "JobScheduler.Batt";
Matthew Williams3d86fd22014-05-16 18:02:17 -070047
48 private static final Object sCreationLock = new Object();
49 private static volatile BatteryController sController;
Matthew Williams3d86fd22014-05-16 18:02:17 -070050
Dianne Hackbornf9bac162017-04-20 17:17:48 -070051 private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
Matthew Williams3d86fd22014-05-16 18:02:17 -070052 private ChargingTracker mChargeTracker;
53
Christopher Tate7060b042014-06-09 19:50:00 -070054 public static BatteryController get(JobSchedulerService taskManagerService) {
Matthew Williams3d86fd22014-05-16 18:02:17 -070055 synchronized (sCreationLock) {
56 if (sController == null) {
57 sController = new BatteryController(taskManagerService,
Dianne Hackborn33d31c52016-02-16 10:30:33 -080058 taskManagerService.getContext(), taskManagerService.getLock());
Matthew Williams3d86fd22014-05-16 18:02:17 -070059 }
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) {
Dianne Hackborn33d31c52016-02-16 10:30:33 -080072 return new BatteryController(stateChangedListener, context, new Object());
Matthew Williams3d86fd22014-05-16 18:02:17 -070073 }
74
Dianne Hackborn33d31c52016-02-16 10:30:33 -080075 private BatteryController(StateChangedListener stateChangedListener, Context context,
76 Object lock) {
77 super(stateChangedListener, context, lock);
Matthew Williams3d86fd22014-05-16 18:02:17 -070078 mChargeTracker = new ChargingTracker();
79 mChargeTracker.startTracking();
80 }
81
82 @Override
Dianne Hackbornb0001f62016-02-16 10:30:33 -080083 public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
Dianne Hackborna06ec6a2017-02-13 10:08:42 -080084 if (taskStatus.hasPowerConstraint()) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -080085 mTrackedTasks.add(taskStatus);
Dianne Hackbornf9bac162017-04-20 17:17:48 -070086 taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
Dianne Hackborna06ec6a2017-02-13 10:08:42 -080087 taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower());
88 taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow());
Matthew Williamsbafeeb92014-08-08 11:51:06 -070089 }
Matthew Williams3d86fd22014-05-16 18:02:17 -070090 }
91
92 @Override
Dianne Hackborn141f11c2016-04-05 15:46:12 -070093 public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
Dianne Hackbornf9bac162017-04-20 17:17:48 -070094 if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
Dianne Hackbornb0001f62016-02-16 10:30:33 -080095 mTrackedTasks.remove(taskStatus);
Matthew Williams3d86fd22014-05-16 18:02:17 -070096 }
97 }
98
Dianne Hackborn6d068262017-05-16 13:14:37 -070099 private void maybeReportNewChargingStateLocked() {
Matthew Williams3d86fd22014-05-16 18:02:17 -0700100 final boolean stablePower = mChargeTracker.isOnStablePower();
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800101 final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
Matthew Williams759275d2014-06-10 12:59:29 -0700102 if (DEBUG) {
Dianne Hackborn6d068262017-05-16 13:14:37 -0700103 Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
Matthew Williams759275d2014-06-10 12:59:29 -0700104 }
Matthew Williams3d86fd22014-05-16 18:02:17 -0700105 boolean reportChange = false;
Dianne Hackborn6d068262017-05-16 13:14:37 -0700106 for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
107 final JobStatus ts = mTrackedTasks.valueAt(i);
108 boolean previous = ts.setChargingConstraintSatisfied(stablePower);
109 if (previous != stablePower) {
110 reportChange = true;
111 }
112 previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow);
113 if (previous != batteryNotLow) {
114 reportChange = true;
Matthew Williams3d86fd22014-05-16 18:02:17 -0700115 }
116 }
Dianne Hackborn532ea262017-03-17 17:50:55 -0700117 if (stablePower || batteryNotLow) {
Dianne Hackborn6d068262017-05-16 13:14:37 -0700118 // If one of our conditions has been satisfied, always schedule any newly ready jobs.
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700119 mStateChangedListener.onRunJobNow(null);
Dianne Hackborn6d068262017-05-16 13:14:37 -0700120 } else if (reportChange) {
121 // Otherwise, just let the job scheduler know the state has changed and take care of it
122 // as it thinks is best.
123 mStateChangedListener.onControllerStateChanged();
Matthew Williamsbafeeb92014-08-08 11:51:06 -0700124 }
Matthew Williams3d86fd22014-05-16 18:02:17 -0700125 }
126
Dianne Hackborn6466c1c2017-06-13 10:33:19 -0700127 public final class ChargingTracker extends BroadcastReceiver {
Matthew Williams3d86fd22014-05-16 18:02:17 -0700128 /**
129 * Track whether we're "charging", where charging means that we're ready to commit to
130 * doing work.
131 */
132 private boolean mCharging;
133 /** Keep track of whether the battery is charged enough that we want to do work. */
134 private boolean mBatteryHealthy;
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800135 /** Sequence number of last broadcast. */
136 private int mLastBatterySeq = -1;
137
138 private BroadcastReceiver mMonitor;
Matthew Williams3d86fd22014-05-16 18:02:17 -0700139
140 public ChargingTracker() {
Matthew Williams3d86fd22014-05-16 18:02:17 -0700141 }
142
143 public void startTracking() {
144 IntentFilter filter = new IntentFilter();
145
146 // Battery health.
147 filter.addAction(Intent.ACTION_BATTERY_LOW);
148 filter.addAction(Intent.ACTION_BATTERY_OKAY);
149 // Charging/not charging.
Dianne Hackborn4870e9d2015-04-08 16:55:47 -0700150 filter.addAction(BatteryManager.ACTION_CHARGING);
151 filter.addAction(BatteryManager.ACTION_DISCHARGING);
Matthew Williams3d86fd22014-05-16 18:02:17 -0700152 mContext.registerReceiver(this, filter);
153
154 // Initialise tracker state.
Jeff Brown21392762014-06-13 19:00:36 -0700155 BatteryManagerInternal batteryManagerInternal =
156 LocalServices.getService(BatteryManagerInternal.class);
157 mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
158 mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
Matthew Williams3d86fd22014-05-16 18:02:17 -0700159 }
160
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800161 public void setMonitorBatteryLocked(boolean enabled) {
162 if (enabled) {
163 if (mMonitor == null) {
164 mMonitor = new BroadcastReceiver() {
165 @Override public void onReceive(Context context, Intent intent) {
166 ChargingTracker.this.onReceive(context, intent);
167 }
168 };
169 IntentFilter filter = new IntentFilter();
170 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
171 mContext.registerReceiver(mMonitor, filter);
172 }
173 } else {
174 if (mMonitor != null) {
175 mContext.unregisterReceiver(mMonitor);
176 mMonitor = null;
177 }
178 }
179 }
180
181 public boolean isOnStablePower() {
Matthew Williams3d86fd22014-05-16 18:02:17 -0700182 return mCharging && mBatteryHealthy;
183 }
184
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800185 public boolean isBatteryNotLow() {
186 return mBatteryHealthy;
187 }
188
189 public boolean isMonitoring() {
190 return mMonitor != null;
191 }
192
193 public int getSeq() {
194 return mLastBatterySeq;
195 }
196
Matthew Williams3d86fd22014-05-16 18:02:17 -0700197 @Override
198 public void onReceive(Context context, Intent intent) {
199 onReceiveInternal(intent);
200 }
201
202 @VisibleForTesting
203 public void onReceiveInternal(Intent intent) {
Dianne Hackborn6d068262017-05-16 13:14:37 -0700204 synchronized (mLock) {
205 final String action = intent.getAction();
206 if (Intent.ACTION_BATTERY_LOW.equals(action)) {
207 if (DEBUG) {
208 Slog.d(TAG, "Battery life too low to do work. @ "
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700209 + sElapsedRealtimeClock.millis());
Dianne Hackborn6d068262017-05-16 13:14:37 -0700210 }
211 // If we get this action, the battery is discharging => it isn't plugged in so
212 // there's no work to cancel. We track this variable for the case where it is
213 // charging, but hasn't been for long enough to be healthy.
214 mBatteryHealthy = false;
215 maybeReportNewChargingStateLocked();
216 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
217 if (DEBUG) {
218 Slog.d(TAG, "Battery life healthy enough to do work. @ "
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700219 + sElapsedRealtimeClock.millis());
Dianne Hackborn6d068262017-05-16 13:14:37 -0700220 }
221 mBatteryHealthy = true;
222 maybeReportNewChargingStateLocked();
223 } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
224 if (DEBUG) {
225 Slog.d(TAG, "Received charging intent, fired @ "
Jeff Sharkeyd0fff2e2017-11-07 16:55:06 -0700226 + sElapsedRealtimeClock.millis());
Dianne Hackborn6d068262017-05-16 13:14:37 -0700227 }
228 mCharging = true;
229 maybeReportNewChargingStateLocked();
230 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
231 if (DEBUG) {
232 Slog.d(TAG, "Disconnected from power.");
233 }
234 mCharging = false;
235 maybeReportNewChargingStateLocked();
Matthew Williams3d86fd22014-05-16 18:02:17 -0700236 }
Dianne Hackborn6d068262017-05-16 13:14:37 -0700237 mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE,
238 mLastBatterySeq);
Matthew Williams3d86fd22014-05-16 18:02:17 -0700239 }
240 }
241 }
Matthew Williamseffacfa2014-06-05 20:56:40 -0700242
243 @Override
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700244 public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700245 pw.print("Battery: stable power = ");
Dianne Hackborna06ec6a2017-02-13 10:08:42 -0800246 pw.print(mChargeTracker.isOnStablePower());
247 pw.print(", not low = ");
248 pw.println(mChargeTracker.isBatteryNotLow());
249 if (mChargeTracker.isMonitoring()) {
250 pw.print("MONITORING: seq=");
251 pw.println(mChargeTracker.getSeq());
252 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700253 pw.print("Tracking ");
254 pw.print(mTrackedTasks.size());
255 pw.println(":");
256 for (int i = 0; i < mTrackedTasks.size(); i++) {
Dianne Hackbornf9bac162017-04-20 17:17:48 -0700257 final JobStatus js = mTrackedTasks.valueAt(i);
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700258 if (!js.shouldDump(filterUid)) {
259 continue;
Dianne Hackbornef3aa6e2016-04-29 18:18:08 -0700260 }
Dianne Hackborne9a988c2016-05-27 17:59:40 -0700261 pw.print(" #");
262 js.printUniqueId(pw);
263 pw.print(" from ");
264 UserHandle.formatUid(pw, js.getSourceUid());
265 pw.println();
Matthew Williams759275d2014-06-10 12:59:29 -0700266 }
Matthew Williamseffacfa2014-06-05 20:56:40 -0700267 }
Kweku Adams85f2fbc2017-12-18 12:04:12 -0800268
269 @Override
270 public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, int filterUid) {
271 final long token = proto.start(fieldId);
272 final long mToken = proto.start(StateControllerProto.BATTERY);
273
274 proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER,
275 mChargeTracker.isOnStablePower());
276 proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW,
277 mChargeTracker.isBatteryNotLow());
278
279 proto.write(StateControllerProto.BatteryController.IS_MONITORING,
280 mChargeTracker.isMonitoring());
281 proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER,
282 mChargeTracker.getSeq());
283
284 for (int i = 0; i < mTrackedTasks.size(); i++) {
285 final JobStatus js = mTrackedTasks.valueAt(i);
286 if (!js.shouldDump(filterUid)) {
287 continue;
288 }
289 final long jsToken = proto.start(StateControllerProto.BatteryController.TRACKED_JOBS);
290 js.writeToShortProto(proto, StateControllerProto.BatteryController.TrackedJob.INFO);
291 proto.write(StateControllerProto.BatteryController.TrackedJob.SOURCE_UID,
292 js.getSourceUid());
293 proto.end(jsToken);
294 }
295
296 proto.end(mToken);
297 proto.end(token);
298 }
Matthew Williams3d86fd22014-05-16 18:02:17 -0700299}