1 /* 2 * Copyright (C) 2021 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 package com.android.systemui.battery; 17 18 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; 19 20 import static com.android.settingslib.flags.Flags.newStatusBarIcons; 21 import static com.android.systemui.DejankUtils.whitelistIpcs; 22 23 import static java.lang.annotation.RetentionPolicy.SOURCE; 24 25 import android.animation.LayoutTransition; 26 import android.animation.ObjectAnimator; 27 import android.annotation.IntDef; 28 import android.annotation.IntRange; 29 import android.annotation.Nullable; 30 import android.annotation.SuppressLint; 31 import android.content.Context; 32 import android.content.res.Configuration; 33 import android.content.res.Resources; 34 import android.content.res.TypedArray; 35 import android.graphics.Rect; 36 import android.graphics.drawable.Drawable; 37 import android.os.UserHandle; 38 import android.provider.Settings; 39 import android.text.TextUtils; 40 import android.util.AttributeSet; 41 import android.util.TypedValue; 42 import android.view.Gravity; 43 import android.view.LayoutInflater; 44 import android.widget.ImageView; 45 import android.widget.LinearLayout; 46 import android.widget.TextView; 47 48 import androidx.annotation.StyleRes; 49 import androidx.annotation.VisibleForTesting; 50 51 import com.android.app.animation.Interpolators; 52 import com.android.systemui.DualToneHandler; 53 import com.android.systemui.battery.unified.BatteryColors; 54 import com.android.systemui.battery.unified.BatteryDrawableState; 55 import com.android.systemui.battery.unified.BatteryLayersDrawable; 56 import com.android.systemui.battery.unified.ColorProfile; 57 import com.android.systemui.plugins.DarkIconDispatcher; 58 import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver; 59 import com.android.systemui.res.R; 60 import com.android.systemui.statusbar.policy.BatteryController; 61 62 import java.io.PrintWriter; 63 import java.lang.annotation.Retention; 64 import java.text.NumberFormat; 65 import java.util.ArrayList; 66 67 public class BatteryMeterView extends LinearLayout implements DarkReceiver { 68 69 @Retention(SOURCE) 70 @IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF, MODE_ESTIMATE}) 71 public @interface BatteryPercentMode {} 72 public static final int MODE_DEFAULT = 0; 73 public static final int MODE_ON = 1; 74 public static final int MODE_OFF = 2; 75 public static final int MODE_ESTIMATE = 3; 76 77 private final AccessorizedBatteryDrawable mDrawable; 78 private final ImageView mBatteryIconView; 79 private TextView mBatteryPercentView; 80 81 private final @StyleRes int mPercentageStyleId; 82 private int mTextColor; 83 private int mLevel; 84 private int mShowPercentMode = MODE_DEFAULT; 85 private boolean mShowPercentAvailable; 86 private String mEstimateText = null; 87 private boolean mPluggedIn; 88 private boolean mPowerSaveEnabled; 89 private boolean mIsBatteryDefender; 90 private boolean mIsIncompatibleCharging; 91 // Error state where we know nothing about the current battery state 92 private boolean mBatteryStateUnknown; 93 // Lazily-loaded since this is expected to be a rare-if-ever state 94 private Drawable mUnknownStateDrawable; 95 96 private DualToneHandler mDualToneHandler; 97 private boolean mIsStaticColor = false; 98 99 private BatteryEstimateFetcher mBatteryEstimateFetcher; 100 101 // for Flags.newStatusBarIcons. The unified battery icon can show percent inside 102 @Nullable private BatteryLayersDrawable mUnifiedBattery; 103 private BatteryColors mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS; 104 private BatteryDrawableState mUnifiedBatteryState = 105 BatteryDrawableState.Companion.getDefaultInitialState(); 106 BatteryMeterView(Context context, AttributeSet attrs)107 public BatteryMeterView(Context context, AttributeSet attrs) { 108 this(context, attrs, 0); 109 } 110 BatteryMeterView(Context context, AttributeSet attrs, int defStyle)111 public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) { 112 super(context, attrs, defStyle); 113 114 setOrientation(LinearLayout.HORIZONTAL); 115 setGravity(Gravity.CENTER_VERTICAL | Gravity.START); 116 117 TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView, 118 defStyle, 0); 119 final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor, 120 context.getColor(com.android.settingslib.R.color.meter_background_color)); 121 mPercentageStyleId = atts.getResourceId(R.styleable.BatteryMeterView_textAppearance, 0); 122 123 mDrawable = new AccessorizedBatteryDrawable(context, frameColor); 124 atts.recycle(); 125 126 mShowPercentAvailable = context.getResources().getBoolean( 127 com.android.internal.R.bool.config_battery_percentage_setting_available); 128 129 setupLayoutTransition(); 130 131 mBatteryIconView = new ImageView(context); 132 if (newStatusBarIcons()) { 133 mUnifiedBattery = BatteryLayersDrawable.Companion 134 .newBatteryDrawable(context, mUnifiedBatteryState); 135 mBatteryIconView.setImageDrawable(mUnifiedBattery); 136 137 final MarginLayoutParams mlp = new MarginLayoutParams( 138 getResources().getDimensionPixelSize( 139 R.dimen.status_bar_battery_unified_icon_width), 140 getResources().getDimensionPixelSize( 141 R.dimen.status_bar_battery_unified_icon_height)); 142 addView(mBatteryIconView, mlp); 143 } else { 144 mBatteryIconView.setImageDrawable(mDrawable); 145 final MarginLayoutParams mlp = new MarginLayoutParams( 146 getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width), 147 getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height)); 148 mlp.setMargins(0, 0, 0, 149 getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom)); 150 addView(mBatteryIconView, mlp); 151 } 152 153 updateShowPercent(); 154 mDualToneHandler = new DualToneHandler(context); 155 // Init to not dark at all. 156 onDarkChanged(new ArrayList<Rect>(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); 157 158 setClipChildren(false); 159 setClipToPadding(false); 160 } 161 162 setBatteryDrawableState(BatteryDrawableState newState)163 private void setBatteryDrawableState(BatteryDrawableState newState) { 164 if (!newStatusBarIcons()) return; 165 166 mUnifiedBatteryState = newState; 167 mUnifiedBattery.setBatteryState(mUnifiedBatteryState); 168 } 169 setupLayoutTransition()170 private void setupLayoutTransition() { 171 LayoutTransition transition = new LayoutTransition(); 172 transition.setDuration(200); 173 174 // Animates appearing/disappearing of the battery percentage text using fade-in/fade-out 175 // and disables all other animation types 176 ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f); 177 transition.setAnimator(LayoutTransition.APPEARING, appearAnimator); 178 transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN); 179 180 ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f); 181 transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT); 182 transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator); 183 184 transition.setAnimator(LayoutTransition.CHANGE_APPEARING, null); 185 transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null); 186 transition.setAnimator(LayoutTransition.CHANGING, null); 187 188 setLayoutTransition(transition); 189 } 190 setForceShowPercent(boolean show)191 public void setForceShowPercent(boolean show) { 192 setPercentShowMode(show ? MODE_ON : MODE_DEFAULT); 193 } 194 195 /** 196 * Force a particular mode of showing percent 197 * 198 * 0 - No preference 199 * 1 - Force on 200 * 2 - Force off 201 * 3 - Estimate 202 * @param mode desired mode (none, on, off) 203 */ setPercentShowMode(@atteryPercentMode int mode)204 public void setPercentShowMode(@BatteryPercentMode int mode) { 205 if (mode == mShowPercentMode) return; 206 mShowPercentMode = mode; 207 updateShowPercent(); 208 updatePercentText(); 209 } 210 211 @Override onConfigurationChanged(Configuration newConfig)212 protected void onConfigurationChanged(Configuration newConfig) { 213 super.onConfigurationChanged(newConfig); 214 updatePercentView(); 215 mDrawable.notifyDensityChanged(); 216 } 217 setColorsFromContext(Context context)218 public void setColorsFromContext(Context context) { 219 if (context == null) { 220 return; 221 } 222 223 mDualToneHandler.setColorsFromContext(context); 224 } 225 226 @Override hasOverlappingRendering()227 public boolean hasOverlappingRendering() { 228 return false; 229 } 230 231 /** 232 * Update battery level 233 * 234 * @param level int between 0 and 100 (representing percentage value) 235 * @param pluggedIn whether the device is plugged in or not 236 */ onBatteryLevelChanged(@ntRangefrom = 0, to = 100) int level, boolean pluggedIn)237 public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) { 238 boolean wasCharging = isCharging(); 239 mPluggedIn = pluggedIn; 240 mLevel = level; 241 boolean isCharging = isCharging(); 242 mDrawable.setCharging(isCharging); 243 mDrawable.setBatteryLevel(level); 244 updatePercentText(); 245 246 if (newStatusBarIcons()) { 247 Drawable attr = mUnifiedBatteryState.getAttribution(); 248 if (isCharging != wasCharging) { 249 attr = getBatteryAttribution(isCharging); 250 } 251 252 BatteryDrawableState newState = 253 new BatteryDrawableState( 254 level, 255 mUnifiedBatteryState.getShowPercent(), 256 getCurrentColorProfile(), 257 attr 258 ); 259 260 setBatteryDrawableState(newState); 261 } 262 } 263 264 // Potentially reloads any attribution. Should not be called if the state hasn't changed 265 @SuppressLint("UseCompatLoadingForDrawables") getBatteryAttribution(boolean isCharging)266 private Drawable getBatteryAttribution(boolean isCharging) { 267 if (!newStatusBarIcons()) return null; 268 269 int resId = 0; 270 if (mPowerSaveEnabled) { 271 resId = R.drawable.battery_unified_attr_powersave; 272 } else if (mIsBatteryDefender) { 273 resId = R.drawable.battery_unified_attr_defend; 274 } else if (isCharging) { 275 resId = R.drawable.battery_unified_attr_charging; 276 } 277 278 Drawable attr = null; 279 if (resId > 0) { 280 attr = mContext.getDrawable(resId); 281 } 282 283 return attr; 284 } 285 286 /** Calculate the appropriate color for the current state */ getCurrentColorProfile()287 private ColorProfile getCurrentColorProfile() { 288 return getColorProfile( 289 mPowerSaveEnabled, 290 mIsBatteryDefender, 291 mPluggedIn, 292 mLevel <= 20); 293 } 294 295 /** pure function to compute the correct color profile for our battery icon */ getColorProfile( boolean isPowerSave, boolean isBatteryDefender, boolean isCharging, boolean isLowBattery )296 private ColorProfile getColorProfile( 297 boolean isPowerSave, 298 boolean isBatteryDefender, 299 boolean isCharging, 300 boolean isLowBattery 301 ) { 302 if (isCharging) return ColorProfile.Active; 303 if (isPowerSave) return ColorProfile.Warning; 304 if (isBatteryDefender) return ColorProfile.None; 305 if (isLowBattery) return ColorProfile.Error; 306 307 return ColorProfile.None; 308 } 309 onPowerSaveChanged(boolean isPowerSave)310 void onPowerSaveChanged(boolean isPowerSave) { 311 if (isPowerSave == mPowerSaveEnabled) { 312 return; 313 } 314 mPowerSaveEnabled = isPowerSave; 315 if (!newStatusBarIcons()) { 316 mDrawable.setPowerSaveEnabled(isPowerSave); 317 } else { 318 setBatteryDrawableState( 319 new BatteryDrawableState( 320 mUnifiedBatteryState.getLevel(), 321 mUnifiedBatteryState.getShowPercent(), 322 getCurrentColorProfile(), 323 getBatteryAttribution(isCharging()) 324 ) 325 ); 326 } 327 } 328 onIsBatteryDefenderChanged(boolean isBatteryDefender)329 void onIsBatteryDefenderChanged(boolean isBatteryDefender) { 330 boolean valueChanged = mIsBatteryDefender != isBatteryDefender; 331 mIsBatteryDefender = isBatteryDefender; 332 333 if (!valueChanged) { 334 return; 335 } 336 337 updateContentDescription(); 338 if (!newStatusBarIcons()) { 339 // The battery drawable is a different size depending on whether it's currently 340 // overheated or not, so we need to re-scale the view when overheated changes. 341 scaleBatteryMeterViews(); 342 } else { 343 setBatteryDrawableState( 344 new BatteryDrawableState( 345 mUnifiedBatteryState.getLevel(), 346 mUnifiedBatteryState.getShowPercent(), 347 getCurrentColorProfile(), 348 getBatteryAttribution(isCharging()) 349 ) 350 ); 351 } 352 } 353 onIsIncompatibleChargingChanged(boolean isIncompatibleCharging)354 void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) { 355 boolean valueChanged = mIsIncompatibleCharging != isIncompatibleCharging; 356 mIsIncompatibleCharging = isIncompatibleCharging; 357 if (valueChanged) { 358 if (newStatusBarIcons()) { 359 setBatteryDrawableState( 360 new BatteryDrawableState( 361 mUnifiedBatteryState.getLevel(), 362 mUnifiedBatteryState.getShowPercent(), 363 getCurrentColorProfile(), 364 getBatteryAttribution(isCharging()) 365 ) 366 ); 367 } else { 368 mDrawable.setCharging(isCharging()); 369 } 370 updateContentDescription(); 371 } 372 } 373 inflatePercentView()374 private TextView inflatePercentView() { 375 return (TextView) LayoutInflater.from(getContext()) 376 .inflate(R.layout.battery_percentage_view, null); 377 } 378 addPercentView(TextView inflatedPercentView)379 private void addPercentView(TextView inflatedPercentView) { 380 mBatteryPercentView = inflatedPercentView; 381 382 if (mPercentageStyleId != 0) { // Only set if specified as attribute 383 mBatteryPercentView.setTextAppearance(mPercentageStyleId); 384 } 385 float fontHeight = mBatteryPercentView.getPaint().getFontMetricsInt(null); 386 mBatteryPercentView.setLineHeight(TypedValue.COMPLEX_UNIT_PX, fontHeight); 387 if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor); 388 addView(mBatteryPercentView, new LayoutParams( 389 LayoutParams.WRAP_CONTENT, 390 (int) Math.ceil(fontHeight))); 391 } 392 393 /** 394 * Updates percent view by removing old one and reinflating if necessary 395 */ updatePercentView()396 public void updatePercentView() { 397 if (mBatteryPercentView != null) { 398 removeView(mBatteryPercentView); 399 mBatteryPercentView = null; 400 } 401 updateShowPercent(); 402 } 403 404 /** 405 * Sets the fetcher that should be used to get the estimated time remaining for the user's 406 * battery. 407 */ setBatteryEstimateFetcher(BatteryEstimateFetcher fetcher)408 void setBatteryEstimateFetcher(BatteryEstimateFetcher fetcher) { 409 mBatteryEstimateFetcher = fetcher; 410 } 411 updatePercentText()412 void updatePercentText() { 413 if (!newStatusBarIcons()) { 414 updatePercentTextLegacy(); 415 return; 416 } 417 418 // The unified battery can show the percent inside, so we only need to handle 419 // the estimated time remaining case 420 if (mShowPercentMode == MODE_ESTIMATE 421 && mBatteryEstimateFetcher != null 422 && !isCharging() 423 ) { 424 mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate( 425 (String estimate) -> { 426 if (mBatteryPercentView == null) { 427 // Similar to the legacy behavior, inflate and add the view. We will 428 // only use it for the estimate text 429 addPercentView(inflatePercentView()); 430 } 431 if (estimate != null && mShowPercentMode == MODE_ESTIMATE) { 432 mEstimateText = estimate; 433 mBatteryPercentView.setText(estimate); 434 updateContentDescription(); 435 } else { 436 mEstimateText = null; 437 mBatteryPercentView.setText(null); 438 updateContentDescription(); 439 } 440 }); 441 } else { 442 if (mBatteryPercentView != null) { 443 mEstimateText = null; 444 mBatteryPercentView.setText(null); 445 } 446 updateContentDescription(); 447 } 448 } 449 updatePercentTextLegacy()450 void updatePercentTextLegacy() { 451 if (mBatteryStateUnknown) { 452 return; 453 } 454 455 if (mBatteryEstimateFetcher == null) { 456 setPercentTextAtCurrentLevel(); 457 return; 458 } 459 460 if (mBatteryPercentView != null) { 461 if (mShowPercentMode == MODE_ESTIMATE && !isCharging()) { 462 mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate( 463 (String estimate) -> { 464 if (mBatteryPercentView == null) { 465 return; 466 } 467 if (estimate != null && mShowPercentMode == MODE_ESTIMATE) { 468 mEstimateText = estimate; 469 mBatteryPercentView.setText(estimate); 470 updateContentDescription(); 471 } else { 472 setPercentTextAtCurrentLevel(); 473 } 474 }); 475 } else { 476 setPercentTextAtCurrentLevel(); 477 } 478 } else { 479 updateContentDescription(); 480 } 481 } 482 setPercentTextAtCurrentLevel()483 private void setPercentTextAtCurrentLevel() { 484 if (mBatteryPercentView != null) { 485 mEstimateText = null; 486 String percentText = NumberFormat.getPercentInstance().format(mLevel / 100f); 487 // Setting text actually triggers a layout pass (because the text view is set to 488 // wrap_content width and TextView always relayouts for this). Avoid needless 489 // relayout if the text didn't actually change. 490 if (!TextUtils.equals(mBatteryPercentView.getText(), percentText)) { 491 mBatteryPercentView.setText(percentText); 492 } 493 } 494 495 updateContentDescription(); 496 } 497 updateContentDescription()498 private void updateContentDescription() { 499 Context context = getContext(); 500 501 String contentDescription; 502 if (mBatteryStateUnknown) { 503 contentDescription = context.getString(R.string.accessibility_battery_unknown); 504 } else if (mShowPercentMode == MODE_ESTIMATE && !TextUtils.isEmpty(mEstimateText)) { 505 contentDescription = context.getString( 506 mIsBatteryDefender 507 ? R.string.accessibility_battery_level_charging_paused_with_estimate 508 : R.string.accessibility_battery_level_with_estimate, 509 mLevel, 510 mEstimateText); 511 } else if (mIsBatteryDefender) { 512 contentDescription = 513 context.getString(R.string.accessibility_battery_level_charging_paused, mLevel); 514 } else if (isCharging()) { 515 contentDescription = 516 context.getString(R.string.accessibility_battery_level_charging, mLevel); 517 } else { 518 contentDescription = context.getString(R.string.accessibility_battery_level, mLevel); 519 } 520 521 setContentDescription(contentDescription); 522 } 523 updateShowPercent()524 void updateShowPercent() { 525 if (!newStatusBarIcons()) { 526 updateShowPercentLegacy(); 527 return; 528 } 529 530 if (!mShowPercentAvailable || mUnifiedBattery == null) return; 531 532 boolean shouldShow = mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE; 533 if (!mBatteryStateUnknown && !shouldShow && (mShowPercentMode != MODE_OFF)) { 534 // Slow case: fall back to the system setting 535 // TODO(b/140051051) 536 shouldShow = 0 != whitelistIpcs(() -> Settings.System 537 .getIntForUser(getContext().getContentResolver(), 538 SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean( 539 com.android.internal.R.bool.config_defaultBatteryPercentageSetting) 540 ? 1 : 0, UserHandle.USER_CURRENT)); 541 } 542 543 setBatteryDrawableState( 544 new BatteryDrawableState( 545 mUnifiedBatteryState.getLevel(), 546 shouldShow, 547 mUnifiedBatteryState.getColor(), 548 mUnifiedBatteryState.getAttribution() 549 ) 550 ); 551 552 // The legacy impl used the percent view for the estimate and the percent text. The modern 553 // version only uses it for estimate. It can be safely removed here 554 if (mShowPercentMode != MODE_ESTIMATE) { 555 removeView(mBatteryPercentView); 556 mBatteryPercentView = null; 557 } 558 } 559 updateShowPercentLegacy()560 private void updateShowPercentLegacy() { 561 final boolean showing = mBatteryPercentView != null; 562 // TODO(b/140051051) 563 final boolean systemSetting = 0 != whitelistIpcs(() -> Settings.System 564 .getIntForUser(getContext().getContentResolver(), 565 SHOW_BATTERY_PERCENT, getContext().getResources().getBoolean( 566 com.android.internal.R.bool.config_defaultBatteryPercentageSetting) 567 ? 1 : 0, UserHandle.USER_CURRENT)); 568 boolean shouldShow = 569 (mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF) 570 || mShowPercentMode == MODE_ON 571 || mShowPercentMode == MODE_ESTIMATE; 572 shouldShow = shouldShow && !mBatteryStateUnknown; 573 574 if (shouldShow) { 575 if (!showing) { 576 addPercentView(inflatePercentView()); 577 updatePercentText(); 578 } 579 } else { 580 if (showing) { 581 removeView(mBatteryPercentView); 582 mBatteryPercentView = null; 583 } 584 } 585 } 586 getUnknownStateDrawable()587 private Drawable getUnknownStateDrawable() { 588 if (mUnknownStateDrawable == null) { 589 mUnknownStateDrawable = mContext.getDrawable(R.drawable.ic_battery_unknown); 590 mUnknownStateDrawable.setTint(mTextColor); 591 } 592 593 return mUnknownStateDrawable; 594 } 595 onBatteryUnknownStateChanged(boolean isUnknown)596 void onBatteryUnknownStateChanged(boolean isUnknown) { 597 if (mBatteryStateUnknown == isUnknown) { 598 return; 599 } 600 601 mBatteryStateUnknown = isUnknown; 602 updateContentDescription(); 603 604 if (mBatteryStateUnknown) { 605 mBatteryIconView.setImageDrawable(getUnknownStateDrawable()); 606 } else { 607 mBatteryIconView.setImageDrawable(mDrawable); 608 } 609 610 updateShowPercent(); 611 } 612 scaleBatteryMeterViews()613 void scaleBatteryMeterViews() { 614 if (!newStatusBarIcons()) { 615 scaleBatteryMeterViewsLegacy(); 616 return; 617 } 618 619 // For simplicity's sake, copy the general pattern in the legacy method and use the new 620 // resources, excluding what we don't need 621 Resources res = getContext().getResources(); 622 TypedValue typedValue = new TypedValue(); 623 624 res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); 625 float iconScaleFactor = typedValue.getFloat(); 626 627 float mainBatteryHeight = 628 res.getDimensionPixelSize( 629 R.dimen.status_bar_battery_unified_icon_height) * iconScaleFactor; 630 float mainBatteryWidth = 631 res.getDimensionPixelSize( 632 R.dimen.status_bar_battery_unified_icon_width) * iconScaleFactor; 633 634 LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams( 635 Math.round(mainBatteryWidth), 636 Math.round(mainBatteryHeight)); 637 638 mBatteryIconView.setLayoutParams(scaledLayoutParams); 639 mBatteryIconView.invalidateDrawable(mUnifiedBattery); 640 } 641 642 /** 643 * Looks up the scale factor for status bar icons and scales the battery view by that amount. 644 */ scaleBatteryMeterViewsLegacy()645 void scaleBatteryMeterViewsLegacy() { 646 Resources res = getContext().getResources(); 647 TypedValue typedValue = new TypedValue(); 648 649 res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); 650 float iconScaleFactor = typedValue.getFloat(); 651 652 float mainBatteryHeight = 653 res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height) * iconScaleFactor; 654 float mainBatteryWidth = 655 res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width) * iconScaleFactor; 656 657 boolean displayShield = mIsBatteryDefender; 658 float fullBatteryIconHeight = 659 BatterySpecs.getFullBatteryHeight(mainBatteryHeight, displayShield); 660 float fullBatteryIconWidth = 661 BatterySpecs.getFullBatteryWidth(mainBatteryWidth, displayShield); 662 663 int marginTop; 664 if (displayShield) { 665 // If the shield is displayed, we need some extra marginTop so that the bottom of the 666 // main icon is still aligned with the bottom of all the other system icons. 667 int shieldHeightAddition = Math.round(fullBatteryIconHeight - mainBatteryHeight); 668 // However, the other system icons have some embedded bottom padding that the battery 669 // doesn't have, so we shouldn't move the battery icon down by the full amount. 670 // See b/258672854. 671 marginTop = shieldHeightAddition 672 - res.getDimensionPixelSize(R.dimen.status_bar_battery_extra_vertical_spacing); 673 } else { 674 marginTop = 0; 675 } 676 677 int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom); 678 679 LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams( 680 Math.round(fullBatteryIconWidth), 681 Math.round(fullBatteryIconHeight)); 682 scaledLayoutParams.setMargins(0, marginTop, 0, marginBottom); 683 684 mDrawable.setDisplayShield(displayShield); 685 mBatteryIconView.setLayoutParams(scaledLayoutParams); 686 mBatteryIconView.invalidateDrawable(mDrawable); 687 } 688 689 @Override onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint)690 public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) { 691 if (mIsStaticColor) return; 692 693 if (!newStatusBarIcons()) { 694 onDarkChangedLegacy(areas, darkIntensity, tint); 695 return; 696 } 697 698 if (mUnifiedBattery == null) { 699 return; 700 } 701 702 if (DarkIconDispatcher.isInAreas(areas, this)) { 703 if (darkIntensity < 0.5) { 704 mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS; 705 } else { 706 mUnifiedBatteryColors = BatteryColors.LIGHT_THEME_COLORS; 707 } 708 709 mUnifiedBattery.setColors(mUnifiedBatteryColors); 710 } else { 711 // Same behavior as the legacy code when not isInArea 712 mUnifiedBatteryColors = BatteryColors.DARK_THEME_COLORS; 713 mUnifiedBattery.setColors(mUnifiedBatteryColors); 714 } 715 } 716 onDarkChangedLegacy(ArrayList<Rect> areas, float darkIntensity, int tint)717 private void onDarkChangedLegacy(ArrayList<Rect> areas, float darkIntensity, int tint) { 718 float intensity = DarkIconDispatcher.isInAreas(areas, this) ? darkIntensity : 0; 719 int nonAdaptedSingleToneColor = mDualToneHandler.getSingleColor(intensity); 720 int nonAdaptedForegroundColor = mDualToneHandler.getFillColor(intensity); 721 int nonAdaptedBackgroundColor = mDualToneHandler.getBackgroundColor(intensity); 722 723 updateColors(nonAdaptedForegroundColor, nonAdaptedBackgroundColor, 724 nonAdaptedSingleToneColor); 725 } 726 setStaticColor(boolean isStaticColor)727 public void setStaticColor(boolean isStaticColor) { 728 mIsStaticColor = isStaticColor; 729 } 730 731 /** 732 * Sets icon and text colors. This will be overridden by {@code onDarkChanged} events, 733 * if registered. 734 * 735 * @param foregroundColor 736 * @param backgroundColor 737 * @param singleToneColor 738 */ updateColors(int foregroundColor, int backgroundColor, int singleToneColor)739 public void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) { 740 mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor); 741 mTextColor = singleToneColor; 742 if (mBatteryPercentView != null) { 743 mBatteryPercentView.setTextColor(singleToneColor); 744 } 745 746 if (mUnknownStateDrawable != null) { 747 mUnknownStateDrawable.setTint(singleToneColor); 748 } 749 } 750 751 /** For newStatusBarIcons(), we use a BatteryColors object to declare the theme */ setUnifiedBatteryColors(BatteryColors colors)752 public void setUnifiedBatteryColors(BatteryColors colors) { 753 if (!newStatusBarIcons()) return; 754 755 mUnifiedBatteryColors = colors; 756 mUnifiedBattery.setColors(mUnifiedBatteryColors); 757 } 758 759 @VisibleForTesting isCharging()760 boolean isCharging() { 761 return mPluggedIn && !mIsIncompatibleCharging; 762 } 763 dump(PrintWriter pw, String[] args)764 public void dump(PrintWriter pw, String[] args) { 765 String powerSave = mDrawable == null ? null : mDrawable.getPowerSaveEnabled() + ""; 766 String displayShield = mDrawable == null ? null : mDrawable.getDisplayShield() + ""; 767 String charging = mDrawable == null ? null : mDrawable.getCharging() + ""; 768 CharSequence percent = mBatteryPercentView == null ? null : mBatteryPercentView.getText(); 769 pw.println(" BatteryMeterView:"); 770 pw.println(" mDrawable.getPowerSave: " + powerSave); 771 pw.println(" mDrawable.getDisplayShield: " + displayShield); 772 pw.println(" mDrawable.getCharging: " + charging); 773 pw.println(" mBatteryPercentView.getText(): " + percent); 774 pw.println(" mTextColor: #" + Integer.toHexString(mTextColor)); 775 pw.println(" mBatteryStateUnknown: " + mBatteryStateUnknown); 776 pw.println(" mIsIncompatibleCharging: " + mIsIncompatibleCharging); 777 pw.println(" mPluggedIn: " + mPluggedIn); 778 pw.println(" mLevel: " + mLevel); 779 pw.println(" mMode: " + mShowPercentMode); 780 if (newStatusBarIcons()) { 781 pw.println(" mUnifiedBatteryState: " + mUnifiedBatteryState); 782 } 783 } 784 785 @VisibleForTesting getBatteryPercentViewText()786 CharSequence getBatteryPercentViewText() { 787 return mBatteryPercentView.getText(); 788 } 789 790 @VisibleForTesting getBatteryPercentView()791 TextView getBatteryPercentView() { 792 return mBatteryPercentView; 793 } 794 795 @VisibleForTesting getUnifiedBatteryState()796 BatteryDrawableState getUnifiedBatteryState() { 797 return mUnifiedBatteryState; 798 } 799 800 /** An interface that will fetch the estimated time remaining for the user's battery. */ 801 public interface BatteryEstimateFetcher { fetchBatteryTimeRemainingEstimate( BatteryController.EstimateFetchCompletion completion)802 void fetchBatteryTimeRemainingEstimate( 803 BatteryController.EstimateFetchCompletion completion); 804 } 805 } 806 807