BatteryController.java revision bafeeb98135a7580cbcdd657818cd78f7bda35d8
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.app.AlarmManager;
20import android.app.PendingIntent;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.os.BatteryManager;
26import android.os.BatteryManagerInternal;
27import android.os.SystemClock;
28import android.util.Slog;
29
30import com.android.internal.annotations.VisibleForTesting;
31import com.android.server.LocalServices;
32import com.android.server.job.JobSchedulerService;
33import com.android.server.job.StateChangedListener;
34
35import java.io.PrintWriter;
36import java.util.ArrayList;
37import java.util.Iterator;
38import 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 */
45public 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    private static final String ACTION_CHARGING_STABLE =
51            "com.android.server.task.controllers.BatteryController.ACTION_CHARGING_STABLE";
52    /** Wait this long after phone is plugged in before doing any work. */
53    private static final long STABLE_CHARGING_THRESHOLD_MILLIS = 2 * 60 * 1000; // 2 minutes.
54
55    private List<JobStatus> mTrackedTasks = new ArrayList<JobStatus>();
56    private ChargingTracker mChargeTracker;
57
58    public static BatteryController get(JobSchedulerService taskManagerService) {
59        synchronized (sCreationLock) {
60            if (sController == null) {
61                sController = new BatteryController(taskManagerService,
62                        taskManagerService.getContext());
63            }
64        }
65        return sController;
66    }
67
68    @VisibleForTesting
69    public ChargingTracker getTracker() {
70        return mChargeTracker;
71    }
72
73    @VisibleForTesting
74    public static BatteryController getForTesting(StateChangedListener stateChangedListener,
75                                           Context context) {
76        return new BatteryController(stateChangedListener, context);
77    }
78
79    private BatteryController(StateChangedListener stateChangedListener, Context context) {
80        super(stateChangedListener, context);
81        mChargeTracker = new ChargingTracker();
82        mChargeTracker.startTracking();
83    }
84
85    @Override
86    public void maybeStartTrackingJob(JobStatus taskStatus) {
87        final boolean isOnStablePower = mChargeTracker.isOnStablePower();
88        if (taskStatus.hasChargingConstraint()) {
89            synchronized (mTrackedTasks) {
90                mTrackedTasks.add(taskStatus);
91                taskStatus.chargingConstraintSatisfied.set(isOnStablePower);
92            }
93        }
94        if (isOnStablePower) {
95            mChargeTracker.setStableChargingAlarm();
96        }
97    }
98
99    @Override
100    public void maybeStopTrackingJob(JobStatus taskStatus) {
101        if (taskStatus.hasChargingConstraint()) {
102            synchronized (mTrackedTasks) {
103                mTrackedTasks.remove(taskStatus);
104            }
105        }
106    }
107
108    private void maybeReportNewChargingState() {
109        final boolean stablePower = mChargeTracker.isOnStablePower();
110        if (DEBUG) {
111            Slog.d(TAG, "maybeReportNewChargingState: " + stablePower);
112        }
113        boolean reportChange = false;
114        synchronized (mTrackedTasks) {
115            for (JobStatus ts : mTrackedTasks) {
116                boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower);
117                if (previous != stablePower) {
118                    reportChange = true;
119                }
120            }
121        }
122        // Let the scheduler know that state has changed. This may or may not result in an
123        // execution.
124        if (reportChange) {
125            mStateChangedListener.onControllerStateChanged();
126        }
127        // Also tell the scheduler that any ready jobs should be flushed.
128        if (stablePower) {
129            mStateChangedListener.onRunJobNow(null);
130        }
131    }
132
133    public class ChargingTracker extends BroadcastReceiver {
134        private final AlarmManager mAlarm;
135        private final PendingIntent mStableChargingTriggerIntent;
136        /**
137         * Track whether we're "charging", where charging means that we're ready to commit to
138         * doing work.
139         */
140        private boolean mCharging;
141        /** Keep track of whether the battery is charged enough that we want to do work. */
142        private boolean mBatteryHealthy;
143
144        public ChargingTracker() {
145            mAlarm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
146            Intent intent = new Intent(ACTION_CHARGING_STABLE);
147            mStableChargingTriggerIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
148        }
149
150        public void startTracking() {
151            IntentFilter filter = new IntentFilter();
152
153            // Battery health.
154            filter.addAction(Intent.ACTION_BATTERY_LOW);
155            filter.addAction(Intent.ACTION_BATTERY_OKAY);
156            // Charging/not charging.
157            filter.addAction(Intent.ACTION_POWER_CONNECTED);
158            filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
159            // Charging stable.
160            filter.addAction(ACTION_CHARGING_STABLE);
161            mContext.registerReceiver(this, filter);
162
163            // Initialise tracker state.
164            BatteryManagerInternal batteryManagerInternal =
165                    LocalServices.getService(BatteryManagerInternal.class);
166            mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
167            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
168        }
169
170        boolean isOnStablePower() {
171            return mCharging && mBatteryHealthy;
172        }
173
174        @Override
175        public void onReceive(Context context, Intent intent) {
176            onReceiveInternal(intent);
177        }
178
179        @VisibleForTesting
180        public void onReceiveInternal(Intent intent) {
181            final String action = intent.getAction();
182            if (Intent.ACTION_BATTERY_LOW.equals(action)) {
183                if (DEBUG) {
184                    Slog.d(TAG, "Battery life too low to do work. @ "
185                            + SystemClock.elapsedRealtime());
186                }
187                // If we get this action, the battery is discharging => it isn't plugged in so
188                // there's no work to cancel. We track this variable for the case where it is
189                // charging, but hasn't been for long enough to be healthy.
190                mBatteryHealthy = false;
191            } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
192                if (DEBUG) {
193                    Slog.d(TAG, "Battery life healthy enough to do work. @ "
194                            + SystemClock.elapsedRealtime());
195                }
196                mBatteryHealthy = true;
197                maybeReportNewChargingState();
198            } else if (Intent.ACTION_POWER_CONNECTED.equals(action)) {
199                if (DEBUG) {
200                    Slog.d(TAG, "Received charging intent, setting alarm for "
201                            + STABLE_CHARGING_THRESHOLD_MILLIS);
202                }
203                // Set up an alarm for ACTION_CHARGING_STABLE - we don't want to kick off tasks
204                // here if the user unplugs the phone immediately.
205                setStableChargingAlarm();
206                mCharging = true;
207            } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
208                if (DEBUG) {
209                    Slog.d(TAG, "Disconnected from power, cancelling any set alarms.");
210                }
211                // If an alarm is set, breathe a sigh of relief and cancel it - crisis averted.
212                mAlarm.cancel(mStableChargingTriggerIntent);
213                mCharging = false;
214                maybeReportNewChargingState();
215            }else if (ACTION_CHARGING_STABLE.equals(action)) {
216                // Here's where we actually do the notify for a task being ready.
217                if (DEBUG) {
218                    Slog.d(TAG, "Stable charging fired @ " + SystemClock.elapsedRealtime()
219                            + " charging: " + mCharging);
220                }
221                if (mCharging) {  // Should never receive this intent if mCharging is false.
222                    maybeReportNewChargingState();
223                }
224            }
225        }
226
227        void setStableChargingAlarm() {
228            final long alarmTriggerElapsed =
229                    SystemClock.elapsedRealtime() + STABLE_CHARGING_THRESHOLD_MILLIS;
230            if (DEBUG) {
231                Slog.d(TAG, "Setting stable alarm to go off in " +
232                        (STABLE_CHARGING_THRESHOLD_MILLIS / 1000) + "s");
233            }
234            mAlarm.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTriggerElapsed,
235                    mStableChargingTriggerIntent);
236        }
237    }
238
239    @Override
240    public void dumpControllerState(PrintWriter pw) {
241        pw.println("Batt.");
242        pw.println("Stable power: " + mChargeTracker.isOnStablePower());
243        synchronized (mTrackedTasks) {
244            Iterator<JobStatus> it = mTrackedTasks.iterator();
245            if (it.hasNext()) {
246                pw.print(String.valueOf(it.next().hashCode()));
247            }
248            while (it.hasNext()) {
249                pw.print("," + String.valueOf(it.next().hashCode()));
250            }
251            pw.println();
252        }
253    }
254}
255