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