1 /*
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 
17 package com.android.systemui.statusbar.policy;
18 
19 import static android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE;
20 import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
21 import static android.os.BatteryManager.EXTRA_CHARGING_STATUS;
22 import static android.os.BatteryManager.EXTRA_PRESENT;
23 
24 import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS;
25 import static com.android.systemui.util.DumpUtilsKt.asIndenting;
26 
27 import android.annotation.WorkerThread;
28 import android.content.BroadcastReceiver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.hardware.usb.UsbManager;
33 import android.os.BatteryManager;
34 import android.os.Bundle;
35 import android.os.Handler;
36 import android.os.PowerManager;
37 import android.os.PowerSaveState;
38 import android.util.IndentingPrintWriter;
39 import android.util.Log;
40 import android.view.View;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.Nullable;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.settingslib.Utils;
47 import com.android.settingslib.fuelgauge.BatterySaverUtils;
48 import com.android.settingslib.fuelgauge.Estimate;
49 import com.android.settingslib.utils.PowerUtil;
50 import com.android.systemui.Dumpable;
51 import com.android.systemui.broadcast.BroadcastDispatcher;
52 import com.android.systemui.dagger.qualifiers.Background;
53 import com.android.systemui.dagger.qualifiers.Main;
54 import com.android.systemui.demomode.DemoMode;
55 import com.android.systemui.demomode.DemoModeController;
56 import com.android.systemui.dump.DumpManager;
57 import com.android.systemui.power.EnhancedEstimates;
58 import com.android.systemui.util.Assert;
59 
60 import java.io.PrintWriter;
61 import java.lang.ref.WeakReference;
62 import java.util.ArrayList;
63 import java.util.List;
64 import java.util.concurrent.atomic.AtomicReference;
65 
66 import javax.annotation.concurrent.GuardedBy;
67 
68 /**
69  * Default implementation of a {@link BatteryController}. This controller monitors for battery
70  * level change events that are broadcasted by the system.
71  */
72 public class BatteryControllerImpl extends BroadcastReceiver implements BatteryController,
73         Dumpable {
74     private static final String TAG = "BatteryController";
75 
76     private static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
77 
78     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
79 
80     private final EnhancedEstimates mEstimates;
81     protected final BroadcastDispatcher mBroadcastDispatcher;
82     protected final ArrayList<BatteryController.BatteryStateChangeCallback>
83             mChangeCallbacks = new ArrayList<>();
84     private final ArrayList<EstimateFetchCompletion> mFetchCallbacks = new ArrayList<>();
85     private final PowerManager mPowerManager;
86     private final DemoModeController mDemoModeController;
87     private final DumpManager mDumpManager;
88     private final Handler mMainHandler;
89     private final Handler mBgHandler;
90     protected final Context mContext;
91 
92     protected int mLevel;
93     protected boolean mPluggedIn;
94     private int mPluggedChargingSource;
95     protected boolean mCharging;
96     private boolean mStateUnknown = false;
97     private boolean mCharged;
98     protected boolean mPowerSave;
99     private boolean mAodPowerSave;
100     private boolean mWirelessCharging;
101     private boolean mIsBatteryDefender = false;
102     private boolean mIsIncompatibleCharging = false;
103     private boolean mTestMode = false;
104     @VisibleForTesting
105     boolean mHasReceivedBattery = false;
106     @GuardedBy("mEstimateLock")
107     private Estimate mEstimate;
108     private final Object mEstimateLock = new Object();
109 
110     private boolean mFetchingEstimate = false;
111 
112     // Use AtomicReference because we may request it from a different thread
113     // Use WeakReference because we are keeping a reference to a View that's not as long lived
114     // as this controller.
115     private AtomicReference<WeakReference<View>> mPowerSaverStartView = new AtomicReference<>();
116 
117     @VisibleForTesting
BatteryControllerImpl( Context context, EnhancedEstimates enhancedEstimates, PowerManager powerManager, BroadcastDispatcher broadcastDispatcher, DemoModeController demoModeController, DumpManager dumpManager, @Main Handler mainHandler, @Background Handler bgHandler)118     public BatteryControllerImpl(
119             Context context,
120             EnhancedEstimates enhancedEstimates,
121             PowerManager powerManager,
122             BroadcastDispatcher broadcastDispatcher,
123             DemoModeController demoModeController,
124             DumpManager dumpManager,
125             @Main Handler mainHandler,
126             @Background Handler bgHandler) {
127         mContext = context;
128         mMainHandler = mainHandler;
129         mBgHandler = bgHandler;
130         mPowerManager = powerManager;
131         mEstimates = enhancedEstimates;
132         mBroadcastDispatcher = broadcastDispatcher;
133         mDemoModeController = demoModeController;
134         mDumpManager = dumpManager;
135     }
136 
registerReceiver()137     private void registerReceiver() {
138         IntentFilter filter = new IntentFilter();
139         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
140         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
141         filter.addAction(ACTION_LEVEL_TEST);
142         filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
143         mBroadcastDispatcher.registerReceiver(this, filter);
144     }
145 
146     @Override
init()147     public void init() {
148         registerReceiver();
149         if (!mHasReceivedBattery) {
150             // Get initial state. Relying on Sticky behavior until API for getting info.
151             Intent intent = mContext.registerReceiver(
152                     null,
153                     new IntentFilter(Intent.ACTION_BATTERY_CHANGED)
154             );
155             if (intent != null && !mHasReceivedBattery) {
156                 onReceive(mContext, intent);
157             }
158         }
159         mDemoModeController.addCallback(this);
160         mDumpManager.registerDumpable(TAG, this);
161         updatePowerSave();
162         updateEstimateInBackground();
163     }
164 
165     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)166     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
167         IndentingPrintWriter ipw = asIndenting(pw);
168         ipw.println("BatteryController state:");
169         ipw.increaseIndent();
170         ipw.print("mHasReceivedBattery="); ipw.println(mHasReceivedBattery);
171         ipw.print("mLevel="); ipw.println(mLevel);
172         ipw.print("mPluggedIn="); ipw.println(mPluggedIn);
173         ipw.print("mCharging="); ipw.println(mCharging);
174         ipw.print("mCharged="); ipw.println(mCharged);
175         ipw.print("mIsBatteryDefender="); ipw.println(mIsBatteryDefender);
176         ipw.print("mIsIncompatibleCharging="); ipw.println(mIsIncompatibleCharging);
177         ipw.print("mPowerSave="); ipw.println(mPowerSave);
178         ipw.print("mStateUnknown="); ipw.println(mStateUnknown);
179         ipw.println("Callbacks:------------------");
180         // Since the above lines are already indented, we need to indent twice for the callbacks.
181         ipw.increaseIndent();
182         synchronized (mChangeCallbacks) {
183             final int n = mChangeCallbacks.size();
184             for (int i = 0; i < n; i++) {
185                 mChangeCallbacks.get(i).dump(ipw, args);
186             }
187         }
188         ipw.decreaseIndent();
189         ipw.println("------------------");
190     }
191 
192     @Override
setPowerSaveMode(boolean powerSave, View view)193     public void setPowerSaveMode(boolean powerSave, View view) {
194         if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view));
195         BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true,
196                 SAVER_ENABLED_QS);
197     }
198 
199     @Override
getLastPowerSaverStartView()200     public WeakReference<View> getLastPowerSaverStartView() {
201         return mPowerSaverStartView.get();
202     }
203 
204     @Override
clearLastPowerSaverStartView()205     public void clearLastPowerSaverStartView() {
206         mPowerSaverStartView.set(null);
207     }
208 
209     @Override
addCallback(@onNull BatteryController.BatteryStateChangeCallback cb)210     public void addCallback(@NonNull BatteryController.BatteryStateChangeCallback cb) {
211         synchronized (mChangeCallbacks) {
212             mChangeCallbacks.add(cb);
213         }
214         if (!mHasReceivedBattery) return;
215 
216         // Make sure new callbacks get the correct initial state
217         cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
218         cb.onPowerSaveChanged(mPowerSave);
219         cb.onBatteryUnknownStateChanged(mStateUnknown);
220         cb.onWirelessChargingChanged(mWirelessCharging);
221         cb.onIsBatteryDefenderChanged(mIsBatteryDefender);
222         cb.onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
223     }
224 
225     @Override
removeCallback(@onNull BatteryController.BatteryStateChangeCallback cb)226     public void removeCallback(@NonNull BatteryController.BatteryStateChangeCallback cb) {
227         synchronized (mChangeCallbacks) {
228             mChangeCallbacks.remove(cb);
229         }
230     }
231 
232     @Override
onReceive(final Context context, Intent intent)233     public void onReceive(final Context context, Intent intent) {
234         final String action = intent.getAction();
235         if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
236             if (mTestMode && !intent.getBooleanExtra("testmode", false)) return;
237             mHasReceivedBattery = true;
238             mLevel = (int) (100f
239                     * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
240                     / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
241             mPluggedChargingSource = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
242             mPluggedIn = mPluggedChargingSource != 0;
243             final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
244                     BatteryManager.BATTERY_STATUS_UNKNOWN);
245             mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
246             mCharging = mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING;
247             if (mWirelessCharging != (mCharging
248                     && intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
249                     == BatteryManager.BATTERY_PLUGGED_WIRELESS)) {
250                 mWirelessCharging = !mWirelessCharging;
251                 fireWirelessChargingChanged();
252             }
253 
254             boolean present = intent.getBooleanExtra(EXTRA_PRESENT, true);
255             boolean unknown = !present;
256             if (unknown != mStateUnknown) {
257                 mStateUnknown = unknown;
258                 fireBatteryUnknownStateChanged();
259             }
260 
261             int chargingStatus = intent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
262             boolean isBatteryDefender = chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
263             if (isBatteryDefender != mIsBatteryDefender) {
264                 mIsBatteryDefender = isBatteryDefender;
265                 fireIsBatteryDefenderChanged();
266             }
267 
268             fireBatteryLevelChanged();
269         } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
270             updatePowerSave();
271         } else if (action.equals(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED)) {
272             boolean isIncompatibleCharging = Utils.containsIncompatibleChargers(mContext, TAG);
273             if (isIncompatibleCharging != mIsIncompatibleCharging) {
274                 mIsIncompatibleCharging = isIncompatibleCharging;
275                 fireIsIncompatibleChargingChanged();
276             }
277         } else if (action.equals(ACTION_LEVEL_TEST)) {
278             mTestMode = true;
279             mMainHandler.post(new Runnable() {
280                 int mCurrentLevel = 0;
281                 int mIncrement = 1;
282                 int mSavedLevel = mLevel;
283                 boolean mSavedPluggedIn = mPluggedIn;
284                 Intent mTestIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
285 
286                 @Override
287                 public void run() {
288                     if (mCurrentLevel < 0) {
289                         mTestMode = false;
290                         mTestIntent.putExtra("level", mSavedLevel);
291                         mTestIntent.putExtra("plugged", mSavedPluggedIn);
292                         mTestIntent.putExtra("testmode", false);
293                     } else {
294                         mTestIntent.putExtra("level", mCurrentLevel);
295                         mTestIntent.putExtra("plugged",
296                                 mIncrement > 0 ? BatteryManager.BATTERY_PLUGGED_AC : 0);
297                         mTestIntent.putExtra("testmode", true);
298                     }
299                     context.sendBroadcast(mTestIntent);
300 
301                     if (!mTestMode) return;
302 
303                     mCurrentLevel += mIncrement;
304                     if (mCurrentLevel == 100) {
305                         mIncrement *= -1;
306                     }
307                     mMainHandler.postDelayed(this, 200);
308                 }
309             });
310         }
311     }
312 
fireWirelessChargingChanged()313     private void fireWirelessChargingChanged() {
314         synchronized (mChangeCallbacks) {
315             mChangeCallbacks.forEach(batteryStateChangeCallback ->
316                     batteryStateChangeCallback.onWirelessChargingChanged(mWirelessCharging));
317         }
318     }
319 
320     @Override
isPluggedIn()321     public boolean isPluggedIn() {
322         return mPluggedIn;
323     }
324 
325     @Override
isPowerSave()326     public boolean isPowerSave() {
327         return mPowerSave;
328     }
329 
330     @Override
isAodPowerSave()331     public boolean isAodPowerSave() {
332         return mAodPowerSave;
333     }
334 
335     @Override
isWirelessCharging()336     public boolean isWirelessCharging() {
337         return mWirelessCharging;
338     }
339 
340     @Override
isPluggedInWireless()341     public boolean isPluggedInWireless() {
342         return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
343     }
344 
isBatteryDefender()345     public boolean isBatteryDefender() {
346         return mIsBatteryDefender;
347     }
348 
349     /**
350      * Returns whether the charging adapter is incompatible.
351      */
isIncompatibleCharging()352     public boolean isIncompatibleCharging() {
353         return mIsIncompatibleCharging;
354     }
355 
356     @Override
getEstimatedTimeRemainingString(EstimateFetchCompletion completion)357     public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
358         // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
359         // work
360         synchronized (mFetchCallbacks) {
361             mFetchCallbacks.add(completion);
362         }
363         updateEstimateInBackground();
364     }
365 
366     @Nullable
generateTimeRemainingString()367     private String generateTimeRemainingString() {
368         synchronized (mEstimateLock) {
369             if (mEstimate == null) {
370                 return null;
371             }
372 
373             return PowerUtil.getBatteryRemainingShortStringFormatted(
374                     mContext, mEstimate.getEstimateMillis());
375         }
376     }
377 
updateEstimateInBackground()378     private void updateEstimateInBackground() {
379         if (mFetchingEstimate) {
380             // Already dispatched a fetch. It will notify all listeners when finished
381             return;
382         }
383 
384         mFetchingEstimate = true;
385         mBgHandler.post(() -> {
386             // Only fetch the estimate if they are enabled
387             synchronized (mEstimateLock) {
388                 mEstimate = null;
389                 if (mEstimates.isHybridNotificationEnabled()) {
390                     updateEstimate();
391                 }
392             }
393             mFetchingEstimate = false;
394             mMainHandler.post(this::notifyEstimateFetchCallbacks);
395         });
396     }
397 
notifyEstimateFetchCallbacks()398     private void notifyEstimateFetchCallbacks() {
399         synchronized (mFetchCallbacks) {
400             String estimate = generateTimeRemainingString();
401             for (EstimateFetchCompletion completion : mFetchCallbacks) {
402                 completion.onBatteryRemainingEstimateRetrieved(estimate);
403             }
404 
405             mFetchCallbacks.clear();
406         }
407     }
408 
409     @WorkerThread
410     @GuardedBy("mEstimateLock")
updateEstimate()411     private void updateEstimate() {
412         Assert.isNotMainThread();
413         // if the estimate has been cached we can just use that, otherwise get a new one and
414         // throw it in the cache.
415         mEstimate = Estimate.getCachedEstimateIfAvailable(mContext);
416         if (mEstimate == null) {
417             mEstimate = mEstimates.getEstimate();
418             if (mEstimate != null) {
419                 Estimate.storeCachedEstimate(mContext, mEstimate);
420             }
421         }
422     }
423 
updatePowerSave()424     private void updatePowerSave() {
425         setPowerSave(mPowerManager.isPowerSaveMode());
426     }
427 
setPowerSave(boolean powerSave)428     private void setPowerSave(boolean powerSave) {
429         if (powerSave == mPowerSave) return;
430         mPowerSave = powerSave;
431 
432         // AOD power saving setting might be different from PowerManager power saving mode.
433         PowerSaveState state = mPowerManager.getPowerSaveState(PowerManager.ServiceType.AOD);
434         mAodPowerSave = state.batterySaverEnabled;
435 
436         if (DEBUG) Log.d(TAG, "Power save is " + (mPowerSave ? "on" : "off"));
437         firePowerSaveChanged();
438     }
439 
fireBatteryLevelChanged()440     protected void fireBatteryLevelChanged() {
441         synchronized (mChangeCallbacks) {
442             final int N = mChangeCallbacks.size();
443             for (int i = 0; i < N; i++) {
444                 mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
445             }
446         }
447     }
448 
fireBatteryUnknownStateChanged()449     private void fireBatteryUnknownStateChanged() {
450         synchronized (mChangeCallbacks) {
451             final int n = mChangeCallbacks.size();
452             for (int i = 0; i < n; i++) {
453                 mChangeCallbacks.get(i).onBatteryUnknownStateChanged(mStateUnknown);
454             }
455         }
456     }
457 
firePowerSaveChanged()458     private void firePowerSaveChanged() {
459         synchronized (mChangeCallbacks) {
460             final int N = mChangeCallbacks.size();
461             for (int i = 0; i < N; i++) {
462                 mChangeCallbacks.get(i).onPowerSaveChanged(mPowerSave);
463             }
464         }
465     }
466 
fireIsBatteryDefenderChanged()467     private void fireIsBatteryDefenderChanged() {
468         synchronized (mChangeCallbacks) {
469             final int n = mChangeCallbacks.size();
470             for (int i = 0; i < n; i++) {
471                 mChangeCallbacks.get(i).onIsBatteryDefenderChanged(mIsBatteryDefender);
472             }
473         }
474     }
475 
fireIsIncompatibleChargingChanged()476     private void fireIsIncompatibleChargingChanged() {
477         synchronized (mChangeCallbacks) {
478             final int n = mChangeCallbacks.size();
479             for (int i = 0; i < n; i++) {
480                 mChangeCallbacks.get(i).onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
481             }
482         }
483     }
484 
485     @Override
dispatchDemoCommand(String command, Bundle args)486     public void dispatchDemoCommand(String command, Bundle args) {
487         if (!mDemoModeController.isInDemoMode()) {
488             return;
489         }
490 
491         String level = args.getString("level");
492         String plugged = args.getString("plugged");
493         String powerSave = args.getString("powersave");
494         String present = args.getString("present");
495         String defender = args.getString("defender");
496         String incompatible = args.getString("incompatible");
497         if (level != null) {
498             mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
499         }
500         if (plugged != null) {
501             mPluggedIn = Boolean.parseBoolean(plugged);
502         }
503         if (powerSave != null) {
504             mPowerSave = powerSave.equals("true");
505             firePowerSaveChanged();
506         }
507         if (present != null) {
508             mStateUnknown = !present.equals("true");
509             fireBatteryUnknownStateChanged();
510         }
511         if (defender != null) {
512             mIsBatteryDefender = defender.equals("true");
513             fireIsBatteryDefenderChanged();
514         }
515         if (incompatible != null) {
516             mIsIncompatibleCharging = incompatible.equals("true");
517             fireIsIncompatibleChargingChanged();
518         }
519         fireBatteryLevelChanged();
520     }
521 
522     @Override
demoCommands()523     public List<String> demoCommands() {
524         List<String> s = new ArrayList<>();
525         s.add(DemoMode.COMMAND_BATTERY);
526         return s;
527     }
528 
529     @Override
onDemoModeStarted()530     public void onDemoModeStarted() {
531         mBroadcastDispatcher.unregisterReceiver(this);
532     }
533 
534     @Override
onDemoModeFinished()535     public void onDemoModeFinished() {
536         registerReceiver();
537         updatePowerSave();
538     }
539 
540     @Override
isChargingSourceDock()541     public boolean isChargingSourceDock() {
542         return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_DOCK;
543     }
544 }
545