• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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