• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
20 
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.UserHandle;
28 import android.util.ArraySet;
29 import android.util.Log;
30 import android.util.Slog;
31 import android.util.proto.ProtoOutputStream;
32 
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.util.IndentingPrintWriter;
35 import com.android.server.LocalServices;
36 import com.android.server.job.JobSchedulerService;
37 import com.android.server.job.StateControllerProto;
38 
39 import java.util.function.Predicate;
40 
41 /**
42  * Simple controller that tracks whether the phone is charging or not. The phone is considered to
43  * be charging when it's been plugged in for more than two minutes, and the system has broadcast
44  * ACTION_BATTERY_OK.
45  */
46 public final class BatteryController extends RestrictingController {
47     private static final String TAG = "JobScheduler.Battery";
48     private static final boolean DEBUG = JobSchedulerService.DEBUG
49             || Log.isLoggable(TAG, Log.DEBUG);
50 
51     private final ArraySet<JobStatus> mTrackedTasks = new ArraySet<>();
52     private ChargingTracker mChargeTracker;
53 
54     @VisibleForTesting
getTracker()55     public ChargingTracker getTracker() {
56         return mChargeTracker;
57     }
58 
BatteryController(JobSchedulerService service)59     public BatteryController(JobSchedulerService service) {
60         super(service);
61         mChargeTracker = new ChargingTracker();
62         mChargeTracker.startTracking();
63     }
64 
65     @Override
maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob)66     public void maybeStartTrackingJobLocked(JobStatus taskStatus, JobStatus lastJob) {
67         if (taskStatus.hasPowerConstraint()) {
68             mTrackedTasks.add(taskStatus);
69             taskStatus.setTrackingController(JobStatus.TRACKING_BATTERY);
70             taskStatus.setChargingConstraintSatisfied(mChargeTracker.isOnStablePower());
71             taskStatus.setBatteryNotLowConstraintSatisfied(mChargeTracker.isBatteryNotLow());
72         }
73     }
74 
75     @Override
startTrackingRestrictedJobLocked(JobStatus jobStatus)76     public void startTrackingRestrictedJobLocked(JobStatus jobStatus) {
77         maybeStartTrackingJobLocked(jobStatus, null);
78     }
79 
80     @Override
maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate)81     public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
82         if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
83             mTrackedTasks.remove(taskStatus);
84         }
85     }
86 
87     @Override
stopTrackingRestrictedJobLocked(JobStatus jobStatus)88     public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
89         if (!jobStatus.hasPowerConstraint()) {
90             maybeStopTrackingJobLocked(jobStatus, null, false);
91         }
92     }
93 
maybeReportNewChargingStateLocked()94     private void maybeReportNewChargingStateLocked() {
95         final boolean stablePower = mChargeTracker.isOnStablePower();
96         final boolean batteryNotLow = mChargeTracker.isBatteryNotLow();
97         if (DEBUG) {
98             Slog.d(TAG, "maybeReportNewChargingStateLocked: " + stablePower);
99         }
100         boolean reportChange = false;
101         for (int i = mTrackedTasks.size() - 1; i >= 0; i--) {
102             final JobStatus ts = mTrackedTasks.valueAt(i);
103             boolean previous = ts.setChargingConstraintSatisfied(stablePower);
104             if (previous != stablePower) {
105                 reportChange = true;
106             }
107             previous = ts.setBatteryNotLowConstraintSatisfied(batteryNotLow);
108             if (previous != batteryNotLow) {
109                 reportChange = true;
110             }
111         }
112         if (stablePower || batteryNotLow) {
113             // If one of our conditions has been satisfied, always schedule any newly ready jobs.
114             mStateChangedListener.onRunJobNow(null);
115         } else if (reportChange) {
116             // Otherwise, just let the job scheduler know the state has changed and take care of it
117             // as it thinks is best.
118             mStateChangedListener.onControllerStateChanged();
119         }
120     }
121 
122     public final 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         /** Sequence number of last broadcast. */
131         private int mLastBatterySeq = -1;
132 
133         private BroadcastReceiver mMonitor;
134 
ChargingTracker()135         public ChargingTracker() {
136         }
137 
startTracking()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 
setMonitorBatteryLocked(boolean enabled)156         public void setMonitorBatteryLocked(boolean enabled) {
157             if (enabled) {
158                 if (mMonitor == null) {
159                     mMonitor = new BroadcastReceiver() {
160                         @Override public void onReceive(Context context, Intent intent) {
161                             ChargingTracker.this.onReceive(context, intent);
162                         }
163                     };
164                     IntentFilter filter = new IntentFilter();
165                     filter.addAction(Intent.ACTION_BATTERY_CHANGED);
166                     mContext.registerReceiver(mMonitor, filter);
167                 }
168             } else {
169                 if (mMonitor != null) {
170                     mContext.unregisterReceiver(mMonitor);
171                     mMonitor = null;
172                 }
173             }
174         }
175 
isOnStablePower()176         public boolean isOnStablePower() {
177             return mCharging && mBatteryHealthy;
178         }
179 
isBatteryNotLow()180         public boolean isBatteryNotLow() {
181             return mBatteryHealthy;
182         }
183 
isMonitoring()184         public boolean isMonitoring() {
185             return mMonitor != null;
186         }
187 
getSeq()188         public int getSeq() {
189             return mLastBatterySeq;
190         }
191 
192         @Override
onReceive(Context context, Intent intent)193         public void onReceive(Context context, Intent intent) {
194             onReceiveInternal(intent);
195         }
196 
197         @VisibleForTesting
onReceiveInternal(Intent intent)198         public void onReceiveInternal(Intent intent) {
199             synchronized (mLock) {
200                 final String action = intent.getAction();
201                 if (Intent.ACTION_BATTERY_LOW.equals(action)) {
202                     if (DEBUG) {
203                         Slog.d(TAG, "Battery life too low to do work. @ "
204                                 + sElapsedRealtimeClock.millis());
205                     }
206                     // If we get this action, the battery is discharging => it isn't plugged in so
207                     // there's no work to cancel. We track this variable for the case where it is
208                     // charging, but hasn't been for long enough to be healthy.
209                     mBatteryHealthy = false;
210                     maybeReportNewChargingStateLocked();
211                 } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
212                     if (DEBUG) {
213                         Slog.d(TAG, "Battery life healthy enough to do work. @ "
214                                 + sElapsedRealtimeClock.millis());
215                     }
216                     mBatteryHealthy = true;
217                     maybeReportNewChargingStateLocked();
218                 } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
219                     if (DEBUG) {
220                         Slog.d(TAG, "Received charging intent, fired @ "
221                                 + sElapsedRealtimeClock.millis());
222                     }
223                     mCharging = true;
224                     maybeReportNewChargingStateLocked();
225                 } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
226                     if (DEBUG) {
227                         Slog.d(TAG, "Disconnected from power.");
228                     }
229                     mCharging = false;
230                     maybeReportNewChargingStateLocked();
231                 }
232                 mLastBatterySeq = intent.getIntExtra(BatteryManager.EXTRA_SEQUENCE,
233                         mLastBatterySeq);
234             }
235         }
236     }
237 
238     @Override
dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate)239     public void dumpControllerStateLocked(IndentingPrintWriter pw,
240             Predicate<JobStatus> predicate) {
241         pw.println("Stable power: " + mChargeTracker.isOnStablePower());
242         pw.println("Not low: " + mChargeTracker.isBatteryNotLow());
243 
244         if (mChargeTracker.isMonitoring()) {
245             pw.print("MONITORING: seq=");
246             pw.println(mChargeTracker.getSeq());
247         }
248         pw.println();
249 
250         for (int i = 0; i < mTrackedTasks.size(); i++) {
251             final JobStatus js = mTrackedTasks.valueAt(i);
252             if (!predicate.test(js)) {
253                 continue;
254             }
255             pw.print("#");
256             js.printUniqueId(pw);
257             pw.print(" from ");
258             UserHandle.formatUid(pw, js.getSourceUid());
259             pw.println();
260         }
261     }
262 
263     @Override
dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Predicate<JobStatus> predicate)264     public void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId,
265             Predicate<JobStatus> predicate) {
266         final long token = proto.start(fieldId);
267         final long mToken = proto.start(StateControllerProto.BATTERY);
268 
269         proto.write(StateControllerProto.BatteryController.IS_ON_STABLE_POWER,
270                 mChargeTracker.isOnStablePower());
271         proto.write(StateControllerProto.BatteryController.IS_BATTERY_NOT_LOW,
272                 mChargeTracker.isBatteryNotLow());
273 
274         proto.write(StateControllerProto.BatteryController.IS_MONITORING,
275                 mChargeTracker.isMonitoring());
276         proto.write(StateControllerProto.BatteryController.LAST_BROADCAST_SEQUENCE_NUMBER,
277                 mChargeTracker.getSeq());
278 
279         for (int i = 0; i < mTrackedTasks.size(); i++) {
280             final JobStatus js = mTrackedTasks.valueAt(i);
281             if (!predicate.test(js)) {
282                 continue;
283             }
284             final long jsToken = proto.start(StateControllerProto.BatteryController.TRACKED_JOBS);
285             js.writeToShortProto(proto, StateControllerProto.BatteryController.TrackedJob.INFO);
286             proto.write(StateControllerProto.BatteryController.TrackedJob.SOURCE_UID,
287                     js.getSourceUid());
288             proto.end(jsToken);
289         }
290 
291         proto.end(mToken);
292         proto.end(token);
293     }
294 }
295