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