blob: 3fc9b44f38508358ab2ed0e90b02354c16acf7a6 [file] [log] [blame]
Anthony Chenda62fdcd52016-04-06 16:15:14 -07001/*
2 * Copyright (C) 2016 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.systemui.statusbar.policy;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.os.BatteryManager;
Jason Monk98d7c7a2016-04-12 13:08:31 -040024import android.os.Bundle;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070025import android.os.Handler;
26import android.os.PowerManager;
Lucas Dupin92a62e52018-01-30 17:22:20 -080027import android.os.PowerSaveState;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070028import android.util.Log;
Lucas Dupin92a62e52018-01-30 17:22:20 -080029
Evan Lairda5a73c52019-01-11 13:36:32 -050030import androidx.annotation.Nullable;
31
Lucas Dupin92a62e52018-01-30 17:22:20 -080032import com.android.internal.annotations.VisibleForTesting;
Makoto Onuki16a0dd22018-03-20 10:40:37 -070033import com.android.settingslib.fuelgauge.BatterySaverUtils;
Salvador Martinez580098fe2019-04-11 10:42:15 -070034import com.android.settingslib.fuelgauge.Estimate;
Evan Laird4bf21df2018-10-22 14:24:32 -040035import com.android.settingslib.utils.PowerUtil;
Evan Lairda5a73c52019-01-11 13:36:32 -050036import com.android.systemui.Dependency;
Evan Laird4bf21df2018-10-22 14:24:32 -040037import com.android.systemui.power.EnhancedEstimates;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070038
39import java.io.FileDescriptor;
40import java.io.PrintWriter;
Evan Laird4bf21df2018-10-22 14:24:32 -040041import java.text.NumberFormat;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070042import java.util.ArrayList;
43
Jason Monk196d6392018-12-20 13:25:34 -050044import javax.inject.Inject;
45import javax.inject.Singleton;
46
Anthony Chenda62fdcd52016-04-06 16:15:14 -070047/**
48 * Default implementation of a {@link BatteryController}. This controller monitors for battery
49 * level change events that are broadcasted by the system.
50 */
Jason Monk196d6392018-12-20 13:25:34 -050051@Singleton
Anthony Chenda62fdcd52016-04-06 16:15:14 -070052public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
53 private static final String TAG = "BatteryController";
54
55 public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
56
57 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Evan Laird4bf21df2018-10-22 14:24:32 -040058 private static final int UPDATE_GRANULARITY_MSEC = 1000 * 60;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070059
Jason Monkde48d5d2018-12-21 14:06:00 -050060 private final EnhancedEstimates mEstimates;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070061 private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
Evan Lairda5a73c52019-01-11 13:36:32 -050062 private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
Anthony Chenda62fdcd52016-04-06 16:15:14 -070063 private final PowerManager mPowerManager;
64 private final Handler mHandler;
Jason Monk98d7c7a2016-04-12 13:08:31 -040065 private final Context mContext;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070066
67 protected int mLevel;
68 protected boolean mPluggedIn;
69 protected boolean mCharging;
70 protected boolean mCharged;
71 protected boolean mPowerSave;
Lucas Dupin92a62e52018-01-30 17:22:20 -080072 protected boolean mAodPowerSave;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070073 private boolean mTestmode = false;
Jason Monk159dfb72016-09-30 09:41:03 -040074 private boolean mHasReceivedBattery = false;
Evan Laird4bf21df2018-10-22 14:24:32 -040075 private Estimate mEstimate;
Evan Lairda5a73c52019-01-11 13:36:32 -050076 private boolean mFetchingEstimate = false;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070077
Jason Monk196d6392018-12-20 13:25:34 -050078 @Inject
Jason Monkde48d5d2018-12-21 14:06:00 -050079 public BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates) {
80 this(context, enhancedEstimates, context.getSystemService(PowerManager.class));
Lucas Dupin92a62e52018-01-30 17:22:20 -080081 }
82
83 @VisibleForTesting
Jason Monkde48d5d2018-12-21 14:06:00 -050084 BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates,
85 PowerManager powerManager) {
Jason Monk98d7c7a2016-04-12 13:08:31 -040086 mContext = context;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070087 mHandler = new Handler();
Lucas Dupin92a62e52018-01-30 17:22:20 -080088 mPowerManager = powerManager;
Jason Monkde48d5d2018-12-21 14:06:00 -050089 mEstimates = enhancedEstimates;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070090
Jason Monk98d7c7a2016-04-12 13:08:31 -040091 registerReceiver();
92 updatePowerSave();
Evan Laird4bf21df2018-10-22 14:24:32 -040093 updateEstimate();
Jason Monk98d7c7a2016-04-12 13:08:31 -040094 }
95
96 private void registerReceiver() {
Anthony Chenda62fdcd52016-04-06 16:15:14 -070097 IntentFilter filter = new IntentFilter();
98 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
99 filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
100 filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
101 filter.addAction(ACTION_LEVEL_TEST);
Jason Monk98d7c7a2016-04-12 13:08:31 -0400102 mContext.registerReceiver(this, filter);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700103 }
104
105 @Override
106 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
107 pw.println("BatteryController state:");
108 pw.print(" mLevel="); pw.println(mLevel);
109 pw.print(" mPluggedIn="); pw.println(mPluggedIn);
110 pw.print(" mCharging="); pw.println(mCharging);
111 pw.print(" mCharged="); pw.println(mCharged);
112 pw.print(" mPowerSave="); pw.println(mPowerSave);
113 }
114
115 @Override
116 public void setPowerSaveMode(boolean powerSave) {
Makoto Onuki16a0dd22018-03-20 10:40:37 -0700117 BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700118 }
119
120 @Override
Jason Monk88529052016-11-04 13:29:58 -0400121 public void addCallback(BatteryController.BatteryStateChangeCallback cb) {
Jason Monk324a28f2016-07-12 13:34:12 -0400122 synchronized (mChangeCallbacks) {
123 mChangeCallbacks.add(cb);
124 }
Jason Monk159dfb72016-09-30 09:41:03 -0400125 if (!mHasReceivedBattery) return;
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700126 cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
127 cb.onPowerSaveChanged(mPowerSave);
128 }
129
130 @Override
Jason Monk88529052016-11-04 13:29:58 -0400131 public void removeCallback(BatteryController.BatteryStateChangeCallback cb) {
Jason Monk324a28f2016-07-12 13:34:12 -0400132 synchronized (mChangeCallbacks) {
133 mChangeCallbacks.remove(cb);
134 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700135 }
136
137 @Override
138 public void onReceive(final Context context, Intent intent) {
139 final String action = intent.getAction();
140 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
141 if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
Jason Monk159dfb72016-09-30 09:41:03 -0400142 mHasReceivedBattery = true;
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700143 mLevel = (int)(100f
144 * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
145 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
146 mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
147
148 final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
149 BatteryManager.BATTERY_STATUS_UNKNOWN);
150 mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
151 mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
152
153 fireBatteryLevelChanged();
154 } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
155 updatePowerSave();
156 } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) {
157 setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
158 } else if (action.equals(ACTION_LEVEL_TEST)) {
159 mTestmode = true;
160 mHandler.post(new Runnable() {
161 int curLevel = 0;
162 int incr = 1;
163 int saveLevel = mLevel;
164 boolean savePlugged = mPluggedIn;
165 Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
166 @Override
167 public void run() {
168 if (curLevel < 0) {
169 mTestmode = false;
170 dummy.putExtra("level", saveLevel);
171 dummy.putExtra("plugged", savePlugged);
172 dummy.putExtra("testmode", false);
173 } else {
174 dummy.putExtra("level", curLevel);
175 dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
176 : 0);
177 dummy.putExtra("testmode", true);
178 }
179 context.sendBroadcast(dummy);
180
181 if (!mTestmode) return;
182
183 curLevel += incr;
184 if (curLevel == 100) {
185 incr *= -1;
186 }
187 mHandler.postDelayed(this, 200);
188 }
189 });
190 }
191 }
192
193 @Override
194 public boolean isPowerSave() {
195 return mPowerSave;
196 }
197
Lucas Dupin92a62e52018-01-30 17:22:20 -0800198 @Override
199 public boolean isAodPowerSave() {
200 return mAodPowerSave;
201 }
202
Evan Laird4bf21df2018-10-22 14:24:32 -0400203 @Override
Evan Lairda5a73c52019-01-11 13:36:32 -0500204 public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
Evan Lairda5a73c52019-01-11 13:36:32 -0500205 // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
206 // work
207 synchronized (mFetchCallbacks) {
208 mFetchCallbacks.add(completion);
209 }
210 updateEstimateInBackground();
211 }
212
213 @Nullable
214 private String generateTimeRemainingString() {
Salvador Martinezaf36fb32019-05-01 14:49:19 -0700215 synchronized (mFetchCallbacks) {
216 if (mEstimate == null) {
217 return null;
218 }
Evan Lairda5a73c52019-01-11 13:36:32 -0500219
Salvador Martinezaf36fb32019-05-01 14:49:19 -0700220 String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
221 return PowerUtil.getBatteryRemainingShortStringFormatted(
222 mContext, mEstimate.getEstimateMillis());
223 }
Evan Laird4bf21df2018-10-22 14:24:32 -0400224 }
225
Evan Lairda5a73c52019-01-11 13:36:32 -0500226 private void updateEstimateInBackground() {
227 if (mFetchingEstimate) {
228 // Already dispatched a fetch. It will notify all listeners when finished
229 return;
230 }
231
232 mFetchingEstimate = true;
233 Dependency.get(Dependency.BG_HANDLER).post(() -> {
Evan Laird85ee4a32019-03-06 18:09:20 -0500234 // Only fetch the estimate if they are enabled
Salvador Martinezaf36fb32019-05-01 14:49:19 -0700235 synchronized (mFetchCallbacks) {
236 mEstimate = null;
237 if (mEstimates.isHybridNotificationEnabled()) {
238 updateEstimate();
239 }
Salvador Martinez7de89292019-04-23 14:55:36 -0700240 }
Evan Lairda5a73c52019-01-11 13:36:32 -0500241 mFetchingEstimate = false;
Evan Lairda5a73c52019-01-11 13:36:32 -0500242 Dependency.get(Dependency.MAIN_HANDLER).post(this::notifyEstimateFetchCallbacks);
243 });
244 }
245
246 private void notifyEstimateFetchCallbacks() {
Evan Lairda5a73c52019-01-11 13:36:32 -0500247 synchronized (mFetchCallbacks) {
Salvador Martinezaf36fb32019-05-01 14:49:19 -0700248 String estimate = generateTimeRemainingString();
Evan Lairda5a73c52019-01-11 13:36:32 -0500249 for (EstimateFetchCompletion completion : mFetchCallbacks) {
250 completion.onBatteryRemainingEstimateRetrieved(estimate);
251 }
252
253 mFetchCallbacks.clear();
254 }
255 }
256
Evan Laird4bf21df2018-10-22 14:24:32 -0400257 private void updateEstimate() {
Salvador Martinez7de89292019-04-23 14:55:36 -0700258 // if the estimate has been cached we can just use that, otherwise get a new one and
259 // throw it in the cache.
260 mEstimate = Estimate.getCachedEstimateIfAvailable(mContext);
261 if (mEstimate == null) {
262 mEstimate = mEstimates.getEstimate();
263 if (mEstimate != null) {
264 Estimate.storeCachedEstimate(mContext, mEstimate);
265 }
266 }
Evan Laird4bf21df2018-10-22 14:24:32 -0400267 }
268
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700269 private void updatePowerSave() {
270 setPowerSave(mPowerManager.isPowerSaveMode());
271 }
272
273 private void setPowerSave(boolean powerSave) {
274 if (powerSave == mPowerSave) return;
275 mPowerSave = powerSave;
Lucas Dupin92a62e52018-01-30 17:22:20 -0800276
277 // AOD power saving setting might be different from PowerManager power saving mode.
278 PowerSaveState state = mPowerManager.getPowerSaveState(PowerManager.ServiceType.AOD);
279 mAodPowerSave = state.batterySaverEnabled;
280
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700281 if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
282 firePowerSaveChanged();
283 }
284
285 protected void fireBatteryLevelChanged() {
Jason Monk324a28f2016-07-12 13:34:12 -0400286 synchronized (mChangeCallbacks) {
287 final int N = mChangeCallbacks.size();
288 for (int i = 0; i < N; i++) {
289 mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
290 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700291 }
292 }
293
294 private void firePowerSaveChanged() {
Jason Monk324a28f2016-07-12 13:34:12 -0400295 synchronized (mChangeCallbacks) {
296 final int N = mChangeCallbacks.size();
297 for (int i = 0; i < N; i++) {
298 mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
299 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700300 }
301 }
Jason Monk98d7c7a2016-04-12 13:08:31 -0400302
303 private boolean mDemoMode;
304
305 @Override
306 public void dispatchDemoCommand(String command, Bundle args) {
307 if (!mDemoMode && command.equals(COMMAND_ENTER)) {
308 mDemoMode = true;
309 mContext.unregisterReceiver(this);
310 } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
311 mDemoMode = false;
312 registerReceiver();
313 updatePowerSave();
314 } else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
Evan Laird706d9682017-05-30 15:03:29 -0400315 String level = args.getString("level");
316 String plugged = args.getString("plugged");
317 String powerSave = args.getString("powersave");
318 if (level != null) {
319 mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
320 }
321 if (plugged != null) {
322 mPluggedIn = Boolean.parseBoolean(plugged);
323 }
324 if (powerSave != null) {
325 mPowerSave = powerSave.equals("true");
326 firePowerSaveChanged();
327 }
Jason Monk98d7c7a2016-04-12 13:08:31 -0400328 fireBatteryLevelChanged();
329 }
330 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700331}