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