BatteryController.java revision 1a30bd9b13fd127d9bbfdc5fd4cb2f80ab7ece21
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());
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);
71    }
72
73    private BatteryController(StateChangedListener stateChangedListener, Context context) {
74        super(stateChangedListener, context);
75        mChargeTracker = new ChargingTracker();
76        mChargeTracker.startTracking();
77    }
78
79    @Override
80    public void maybeStartTrackingJob(JobStatus taskStatus, JobStatus lastJob) {
81        final boolean isOnStablePower = mChargeTracker.isOnStablePower();
82        if (taskStatus.hasChargingConstraint()) {
83            synchronized (mTrackedTasks) {
84                mTrackedTasks.add(taskStatus);
85                taskStatus.chargingConstraintSatisfied.set(isOnStablePower);
86            }
87        }
88    }
89
90    @Override
91    public void maybeStopTrackingJob(JobStatus taskStatus, boolean forUpdate) {
92        if (taskStatus.hasChargingConstraint()) {
93            synchronized (mTrackedTasks) {
94                mTrackedTasks.remove(taskStatus);
95            }
96        }
97    }
98
99    private void maybeReportNewChargingState() {
100        final boolean stablePower = mChargeTracker.isOnStablePower();
101        if (DEBUG) {
102            Slog.d(TAG, "maybeReportNewChargingState: " + stablePower);
103        }
104        boolean reportChange = false;
105        synchronized (mTrackedTasks) {
106            for (JobStatus ts : mTrackedTasks) {
107                boolean previous = ts.chargingConstraintSatisfied.getAndSet(stablePower);
108                if (previous != stablePower) {
109                    reportChange = true;
110                }
111            }
112        }
113        // Let the scheduler know that state has changed. This may or may not result in an
114        // execution.
115        if (reportChange) {
116            mStateChangedListener.onControllerStateChanged();
117        }
118        // Also tell the scheduler that any ready jobs should be flushed.
119        if (stablePower) {
120            mStateChangedListener.onRunJobNow(null);
121        }
122    }
123
124    public class ChargingTracker extends BroadcastReceiver {
125        /**
126         * Track whether we're "charging", where charging means that we're ready to commit to
127         * doing work.
128         */
129        private boolean mCharging;
130        /** Keep track of whether the battery is charged enough that we want to do work. */
131        private boolean mBatteryHealthy;
132
133        public ChargingTracker() {
134        }
135
136        public void startTracking() {
137            IntentFilter filter = new IntentFilter();
138
139            // Battery health.
140            filter.addAction(Intent.ACTION_BATTERY_LOW);
141            filter.addAction(Intent.ACTION_BATTERY_OKAY);
142            // Charging/not charging.
143            filter.addAction(BatteryManager.ACTION_CHARGING);
144            filter.addAction(BatteryManager.ACTION_DISCHARGING);
145            mContext.registerReceiver(this, filter);
146
147            // Initialise tracker state.
148            BatteryManagerInternal batteryManagerInternal =
149                    LocalServices.getService(BatteryManagerInternal.class);
150            mBatteryHealthy = !batteryManagerInternal.getBatteryLevelLow();
151            mCharging = batteryManagerInternal.isPowered(BatteryManager.BATTERY_PLUGGED_ANY);
152        }
153
154        boolean isOnStablePower() {
155            return mCharging && mBatteryHealthy;
156        }
157
158        @Override
159        public void onReceive(Context context, Intent intent) {
160            onReceiveInternal(intent);
161        }
162
163        @VisibleForTesting
164        public void onReceiveInternal(Intent intent) {
165            final String action = intent.getAction();
166            if (Intent.ACTION_BATTERY_LOW.equals(action)) {
167                if (DEBUG) {
168                    Slog.d(TAG, "Battery life too low to do work. @ "
169                            + SystemClock.elapsedRealtime());
170                }
171                // If we get this action, the battery is discharging => it isn't plugged in so
172                // there's no work to cancel. We track this variable for the case where it is
173                // charging, but hasn't been for long enough to be healthy.
174                mBatteryHealthy = false;
175            } else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
176                if (DEBUG) {
177                    Slog.d(TAG, "Battery life healthy enough to do work. @ "
178                            + SystemClock.elapsedRealtime());
179                }
180                mBatteryHealthy = true;
181                maybeReportNewChargingState();
182            } else if (BatteryManager.ACTION_CHARGING.equals(action)) {
183                if (DEBUG) {
184                    Slog.d(TAG, "Received charging intent, fired @ "
185                            + SystemClock.elapsedRealtime());
186                }
187                mCharging = true;
188                maybeReportNewChargingState();
189            } else if (BatteryManager.ACTION_DISCHARGING.equals(action)) {
190                if (DEBUG) {
191                    Slog.d(TAG, "Disconnected from power.");
192                }
193                mCharging = false;
194                maybeReportNewChargingState();
195            }
196        }
197    }
198
199    @Override
200    public void dumpControllerState(PrintWriter pw) {
201        pw.println("Batt.");
202        pw.println("Stable power: " + mChargeTracker.isOnStablePower());
203        synchronized (mTrackedTasks) {
204            Iterator<JobStatus> it = mTrackedTasks.iterator();
205            if (it.hasNext()) {
206                pw.print(String.valueOf(it.next().hashCode()));
207            }
208            while (it.hasNext()) {
209                pw.print("," + String.valueOf(it.next().hashCode()));
210            }
211            pw.println();
212        }
213    }
214}
215