blob: f0f94201f3af398318bb8b4277f4ad2f1b21c7ac [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;
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000037import com.android.systemui.broadcast.BroadcastDispatcher;
Evan Laird4bf21df2018-10-22 14:24:32 -040038import com.android.systemui.power.EnhancedEstimates;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070039
40import java.io.FileDescriptor;
41import java.io.PrintWriter;
Evan Laird4bf21df2018-10-22 14:24:32 -040042import java.text.NumberFormat;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070043import java.util.ArrayList;
44
Jason Monk196d6392018-12-20 13:25:34 -050045import javax.inject.Inject;
46import javax.inject.Singleton;
47
Anthony Chenda62fdcd52016-04-06 16:15:14 -070048/**
49 * Default implementation of a {@link BatteryController}. This controller monitors for battery
50 * level change events that are broadcasted by the system.
51 */
Jason Monk196d6392018-12-20 13:25:34 -050052@Singleton
Anthony Chenda62fdcd52016-04-06 16:15:14 -070053public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController {
54 private static final String TAG = "BatteryController";
55
56 public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
57
58 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Evan Laird4bf21df2018-10-22 14:24:32 -040059 private static final int UPDATE_GRANULARITY_MSEC = 1000 * 60;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070060
Jason Monkde48d5d2018-12-21 14:06:00 -050061 private final EnhancedEstimates mEstimates;
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000062 private final BroadcastDispatcher mBroadcastDispatcher;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070063 private final ArrayList<BatteryController.BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
Evan Lairda5a73c52019-01-11 13:36:32 -050064 private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
Anthony Chenda62fdcd52016-04-06 16:15:14 -070065 private final PowerManager mPowerManager;
66 private final Handler mHandler;
Jason Monk98d7c7a2016-04-12 13:08:31 -040067 private final Context mContext;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070068
69 protected int mLevel;
70 protected boolean mPluggedIn;
71 protected boolean mCharging;
72 protected boolean mCharged;
73 protected boolean mPowerSave;
Lucas Dupin92a62e52018-01-30 17:22:20 -080074 protected boolean mAodPowerSave;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070075 private boolean mTestmode = false;
Jason Monk159dfb72016-09-30 09:41:03 -040076 private boolean mHasReceivedBattery = false;
Evan Laird4bf21df2018-10-22 14:24:32 -040077 private Estimate mEstimate;
Evan Lairda5a73c52019-01-11 13:36:32 -050078 private boolean mFetchingEstimate = false;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070079
Jason Monk196d6392018-12-20 13:25:34 -050080 @Inject
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000081 public BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates,
82 BroadcastDispatcher broadcastDispatcher) {
83 this(context, enhancedEstimates, context.getSystemService(PowerManager.class),
84 broadcastDispatcher);
Lucas Dupin92a62e52018-01-30 17:22:20 -080085 }
86
87 @VisibleForTesting
Jason Monkde48d5d2018-12-21 14:06:00 -050088 BatteryControllerImpl(Context context, EnhancedEstimates enhancedEstimates,
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000089 PowerManager powerManager, BroadcastDispatcher broadcastDispatcher) {
Jason Monk98d7c7a2016-04-12 13:08:31 -040090 mContext = context;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070091 mHandler = new Handler();
Lucas Dupin92a62e52018-01-30 17:22:20 -080092 mPowerManager = powerManager;
Jason Monkde48d5d2018-12-21 14:06:00 -050093 mEstimates = enhancedEstimates;
Fabian Kozynski5ca7a512019-10-16 19:56:11 +000094 mBroadcastDispatcher = broadcastDispatcher;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070095
Jason Monk98d7c7a2016-04-12 13:08:31 -040096 registerReceiver();
97 updatePowerSave();
Evan Laird4bf21df2018-10-22 14:24:32 -040098 updateEstimate();
Jason Monk98d7c7a2016-04-12 13:08:31 -040099 }
100
101 private void registerReceiver() {
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700102 IntentFilter filter = new IntentFilter();
103 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
104 filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
105 filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
106 filter.addAction(ACTION_LEVEL_TEST);
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000107 mBroadcastDispatcher.registerReceiver(this, filter);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700108 }
109
110 @Override
111 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
112 pw.println("BatteryController state:");
113 pw.print(" mLevel="); pw.println(mLevel);
114 pw.print(" mPluggedIn="); pw.println(mPluggedIn);
115 pw.print(" mCharging="); pw.println(mCharging);
116 pw.print(" mCharged="); pw.println(mCharged);
117 pw.print(" mPowerSave="); pw.println(mPowerSave);
118 }
119
120 @Override
121 public void setPowerSaveMode(boolean powerSave) {
Makoto Onuki16a0dd22018-03-20 10:40:37 -0700122 BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700123 }
124
125 @Override
Jason Monk88529052016-11-04 13:29:58 -0400126 public void addCallback(BatteryController.BatteryStateChangeCallback cb) {
Jason Monk324a28f2016-07-12 13:34:12 -0400127 synchronized (mChangeCallbacks) {
128 mChangeCallbacks.add(cb);
129 }
Jason Monk159dfb72016-09-30 09:41:03 -0400130 if (!mHasReceivedBattery) return;
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700131 cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
132 cb.onPowerSaveChanged(mPowerSave);
133 }
134
135 @Override
Jason Monk88529052016-11-04 13:29:58 -0400136 public void removeCallback(BatteryController.BatteryStateChangeCallback cb) {
Jason Monk324a28f2016-07-12 13:34:12 -0400137 synchronized (mChangeCallbacks) {
138 mChangeCallbacks.remove(cb);
139 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700140 }
141
142 @Override
143 public void onReceive(final Context context, Intent intent) {
144 final String action = intent.getAction();
145 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
146 if (mTestmode && !intent.getBooleanExtra("testmode", false)) return;
Jason Monk159dfb72016-09-30 09:41:03 -0400147 mHasReceivedBattery = true;
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700148 mLevel = (int)(100f
149 * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
150 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
151 mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
152
153 final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
154 BatteryManager.BATTERY_STATUS_UNKNOWN);
155 mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
156 mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
157
158 fireBatteryLevelChanged();
159 } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
160 updatePowerSave();
161 } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)) {
162 setPowerSave(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
163 } else if (action.equals(ACTION_LEVEL_TEST)) {
164 mTestmode = true;
165 mHandler.post(new Runnable() {
166 int curLevel = 0;
167 int incr = 1;
168 int saveLevel = mLevel;
169 boolean savePlugged = mPluggedIn;
170 Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
171 @Override
172 public void run() {
173 if (curLevel < 0) {
174 mTestmode = false;
175 dummy.putExtra("level", saveLevel);
176 dummy.putExtra("plugged", savePlugged);
177 dummy.putExtra("testmode", false);
178 } else {
179 dummy.putExtra("level", curLevel);
180 dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
181 : 0);
182 dummy.putExtra("testmode", true);
183 }
184 context.sendBroadcast(dummy);
185
186 if (!mTestmode) return;
187
188 curLevel += incr;
189 if (curLevel == 100) {
190 incr *= -1;
191 }
192 mHandler.postDelayed(this, 200);
193 }
194 });
195 }
196 }
197
198 @Override
199 public boolean isPowerSave() {
200 return mPowerSave;
201 }
202
Lucas Dupin92a62e52018-01-30 17:22:20 -0800203 @Override
204 public boolean isAodPowerSave() {
205 return mAodPowerSave;
206 }
207
Evan Laird4bf21df2018-10-22 14:24:32 -0400208 @Override
Evan Lairda5a73c52019-01-11 13:36:32 -0500209 public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
Evan Lairda5a73c52019-01-11 13:36:32 -0500210 // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
211 // work
212 synchronized (mFetchCallbacks) {
213 mFetchCallbacks.add(completion);
214 }
215 updateEstimateInBackground();
216 }
217
218 @Nullable
219 private String generateTimeRemainingString() {
Salvador Martinezaf36fb32019-05-01 14:49:19 -0700220 synchronized (mFetchCallbacks) {
221 if (mEstimate == null) {
222 return null;
223 }
Evan Lairda5a73c52019-01-11 13:36:32 -0500224
Salvador Martinezaf36fb32019-05-01 14:49:19 -0700225 String percentage = NumberFormat.getPercentInstance().format((double) mLevel / 100.0);
226 return PowerUtil.getBatteryRemainingShortStringFormatted(
227 mContext, mEstimate.getEstimateMillis());
228 }
Evan Laird4bf21df2018-10-22 14:24:32 -0400229 }
230
Evan Lairda5a73c52019-01-11 13:36:32 -0500231 private void updateEstimateInBackground() {
232 if (mFetchingEstimate) {
233 // Already dispatched a fetch. It will notify all listeners when finished
234 return;
235 }
236
237 mFetchingEstimate = true;
238 Dependency.get(Dependency.BG_HANDLER).post(() -> {
Evan Laird85ee4a32019-03-06 18:09:20 -0500239 // Only fetch the estimate if they are enabled
Salvador Martinezaf36fb32019-05-01 14:49:19 -0700240 synchronized (mFetchCallbacks) {
241 mEstimate = null;
242 if (mEstimates.isHybridNotificationEnabled()) {
243 updateEstimate();
244 }
Salvador Martinez7de89292019-04-23 14:55:36 -0700245 }
Evan Lairda5a73c52019-01-11 13:36:32 -0500246 mFetchingEstimate = false;
Evan Lairda5a73c52019-01-11 13:36:32 -0500247 Dependency.get(Dependency.MAIN_HANDLER).post(this::notifyEstimateFetchCallbacks);
248 });
249 }
250
251 private void notifyEstimateFetchCallbacks() {
Evan Lairda5a73c52019-01-11 13:36:32 -0500252 synchronized (mFetchCallbacks) {
Salvador Martinezaf36fb32019-05-01 14:49:19 -0700253 String estimate = generateTimeRemainingString();
Evan Lairda5a73c52019-01-11 13:36:32 -0500254 for (EstimateFetchCompletion completion : mFetchCallbacks) {
255 completion.onBatteryRemainingEstimateRetrieved(estimate);
256 }
257
258 mFetchCallbacks.clear();
259 }
260 }
261
Evan Laird4bf21df2018-10-22 14:24:32 -0400262 private void updateEstimate() {
Salvador Martinez7de89292019-04-23 14:55:36 -0700263 // if the estimate has been cached we can just use that, otherwise get a new one and
264 // throw it in the cache.
265 mEstimate = Estimate.getCachedEstimateIfAvailable(mContext);
266 if (mEstimate == null) {
267 mEstimate = mEstimates.getEstimate();
268 if (mEstimate != null) {
269 Estimate.storeCachedEstimate(mContext, mEstimate);
270 }
271 }
Evan Laird4bf21df2018-10-22 14:24:32 -0400272 }
273
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700274 private void updatePowerSave() {
275 setPowerSave(mPowerManager.isPowerSaveMode());
276 }
277
278 private void setPowerSave(boolean powerSave) {
279 if (powerSave == mPowerSave) return;
280 mPowerSave = powerSave;
Lucas Dupin92a62e52018-01-30 17:22:20 -0800281
282 // AOD power saving setting might be different from PowerManager power saving mode.
283 PowerSaveState state = mPowerManager.getPowerSaveState(PowerManager.ServiceType.AOD);
284 mAodPowerSave = state.batterySaverEnabled;
285
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700286 if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
287 firePowerSaveChanged();
288 }
289
290 protected void fireBatteryLevelChanged() {
Jason Monk324a28f2016-07-12 13:34:12 -0400291 synchronized (mChangeCallbacks) {
292 final int N = mChangeCallbacks.size();
293 for (int i = 0; i < N; i++) {
294 mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
295 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700296 }
297 }
298
299 private void firePowerSaveChanged() {
Jason Monk324a28f2016-07-12 13:34:12 -0400300 synchronized (mChangeCallbacks) {
301 final int N = mChangeCallbacks.size();
302 for (int i = 0; i < N; i++) {
303 mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
304 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700305 }
306 }
Jason Monk98d7c7a2016-04-12 13:08:31 -0400307
308 private boolean mDemoMode;
309
310 @Override
311 public void dispatchDemoCommand(String command, Bundle args) {
312 if (!mDemoMode && command.equals(COMMAND_ENTER)) {
313 mDemoMode = true;
Fabian Kozynski5ca7a512019-10-16 19:56:11 +0000314 mBroadcastDispatcher.unregisterReceiver(this);
Jason Monk98d7c7a2016-04-12 13:08:31 -0400315 } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
316 mDemoMode = false;
317 registerReceiver();
318 updatePowerSave();
319 } else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
Evan Laird706d9682017-05-30 15:03:29 -0400320 String level = args.getString("level");
321 String plugged = args.getString("plugged");
322 String powerSave = args.getString("powersave");
323 if (level != null) {
324 mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
325 }
326 if (plugged != null) {
327 mPluggedIn = Boolean.parseBoolean(plugged);
328 }
329 if (powerSave != null) {
330 mPowerSave = powerSave.equals("true");
331 firePowerSaveChanged();
332 }
Jason Monk98d7c7a2016-04-12 13:08:31 -0400333 fireBatteryLevelChanged();
334 }
335 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700336}