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