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 }