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