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