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