Add layout transition animation Fixed: 123362236 Test: manual - turn on/off show battery percentage in settings and go to AOD Change-Id: Ie98434e565d23461d82729d582f8ff4e63d8ac88
497 lines
19 KiB
Java
497 lines
19 KiB
Java
/*
|
|
* Copyright (C) 2013 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
package com.android.systemui;
|
|
|
|
import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
|
|
import static android.app.StatusBarManager.DISABLE_NONE;
|
|
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
|
|
|
|
import static com.android.systemui.util.SysuiLifecycle.viewAttachLifecycle;
|
|
|
|
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
|
|
|
import android.animation.ArgbEvaluator;
|
|
import android.animation.LayoutTransition;
|
|
import android.animation.ObjectAnimator;
|
|
import android.annotation.IntDef;
|
|
import android.app.ActivityManager;
|
|
import android.content.Context;
|
|
import android.content.res.Resources;
|
|
import android.content.res.TypedArray;
|
|
import android.database.ContentObserver;
|
|
import android.graphics.Rect;
|
|
import android.net.Uri;
|
|
import android.os.Handler;
|
|
import android.provider.Settings;
|
|
import android.util.ArraySet;
|
|
import android.util.AttributeSet;
|
|
import android.util.TypedValue;
|
|
import android.view.ContextThemeWrapper;
|
|
import android.view.Gravity;
|
|
import android.view.LayoutInflater;
|
|
import android.view.ViewGroup;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
|
|
import com.android.settingslib.Utils;
|
|
import com.android.settingslib.graph.ThemedBatteryDrawable;
|
|
import com.android.systemui.plugins.DarkIconDispatcher;
|
|
import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
|
|
import com.android.systemui.settings.CurrentUserTracker;
|
|
import com.android.systemui.statusbar.phone.StatusBarIconController;
|
|
import com.android.systemui.statusbar.policy.BatteryController;
|
|
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
|
|
import com.android.systemui.statusbar.policy.ConfigurationController;
|
|
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
|
|
import com.android.systemui.tuner.TunerService;
|
|
import com.android.systemui.tuner.TunerService.Tunable;
|
|
import com.android.systemui.util.Utils.DisableStateTracker;
|
|
|
|
import java.io.FileDescriptor;
|
|
import java.io.PrintWriter;
|
|
import java.lang.annotation.Retention;
|
|
import java.text.NumberFormat;
|
|
|
|
public class BatteryMeterView extends LinearLayout implements
|
|
BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener {
|
|
|
|
|
|
@Retention(SOURCE)
|
|
@IntDef({MODE_DEFAULT, MODE_ON, MODE_OFF, MODE_ESTIMATE})
|
|
public @interface BatteryPercentMode {}
|
|
public static final int MODE_DEFAULT = 0;
|
|
public static final int MODE_ON = 1;
|
|
public static final int MODE_OFF = 2;
|
|
public static final int MODE_ESTIMATE = 3;
|
|
|
|
private final ThemedBatteryDrawable mDrawable;
|
|
private final String mSlotBattery;
|
|
private final ImageView mBatteryIconView;
|
|
private final CurrentUserTracker mUserTracker;
|
|
private TextView mBatteryPercentView;
|
|
|
|
private BatteryController mBatteryController;
|
|
private SettingObserver mSettingObserver;
|
|
private int mTextColor;
|
|
private int mLevel;
|
|
private int mShowPercentMode = MODE_DEFAULT;
|
|
private boolean mForceShowPercent;
|
|
private boolean mShowPercentAvailable;
|
|
// Some places may need to show the battery conditionally, and not obey the tuner
|
|
private boolean mIgnoreTunerUpdates;
|
|
private boolean mIsSubscribedForTunerUpdates;
|
|
private boolean mCharging;
|
|
|
|
private int mDarkModeSingleToneColor;
|
|
private int mDarkModeBackgroundColor;
|
|
private int mDarkModeFillColor;
|
|
|
|
private int mLightModeSingleToneColor;
|
|
private int mLightModeBackgroundColor;
|
|
private int mLightModeFillColor;
|
|
private int mUser;
|
|
|
|
/**
|
|
* Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings.
|
|
*/
|
|
private boolean mUseWallpaperTextColors;
|
|
|
|
private int mNonAdaptedSingleToneColor;
|
|
private int mNonAdaptedForegroundColor;
|
|
private int mNonAdaptedBackgroundColor;
|
|
|
|
public BatteryMeterView(Context context) {
|
|
this(context, null, 0);
|
|
}
|
|
|
|
public BatteryMeterView(Context context, AttributeSet attrs) {
|
|
this(context, attrs, 0);
|
|
}
|
|
|
|
public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
|
|
super(context, attrs, defStyle);
|
|
|
|
setOrientation(LinearLayout.HORIZONTAL);
|
|
setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
|
|
|
|
TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
|
|
defStyle, 0);
|
|
final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
|
|
context.getColor(R.color.meter_background_color));
|
|
mDrawable = new ThemedBatteryDrawable(context, frameColor);
|
|
atts.recycle();
|
|
|
|
mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
|
|
mShowPercentAvailable = context.getResources().getBoolean(
|
|
com.android.internal.R.bool.config_battery_percentage_setting_available);
|
|
|
|
|
|
addOnAttachStateChangeListener(
|
|
new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
|
|
|
|
setupLayoutTransition();
|
|
|
|
mSlotBattery = context.getString(
|
|
com.android.internal.R.string.status_bar_battery);
|
|
mBatteryIconView = new ImageView(context);
|
|
mBatteryIconView.setImageDrawable(mDrawable);
|
|
final MarginLayoutParams mlp = new MarginLayoutParams(
|
|
getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
|
|
getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
|
|
mlp.setMargins(0, 0, 0,
|
|
getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
|
|
addView(mBatteryIconView, mlp);
|
|
|
|
updateShowPercent();
|
|
setColorsFromContext(context);
|
|
// Init to not dark at all.
|
|
onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
|
|
|
|
mUserTracker = new CurrentUserTracker(mContext) {
|
|
@Override
|
|
public void onUserSwitched(int newUserId) {
|
|
mUser = newUserId;
|
|
getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
|
|
getContext().getContentResolver().registerContentObserver(
|
|
Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver,
|
|
newUserId);
|
|
updateShowPercent();
|
|
}
|
|
};
|
|
|
|
setClipChildren(false);
|
|
setClipToPadding(false);
|
|
Dependency.get(ConfigurationController.class).observe(viewAttachLifecycle(this), this);
|
|
|
|
// Needed for PorderDuff.Mode.CLEAR operations to work properly, but redraws don't happen
|
|
// enough to justify a hardware layer.
|
|
setLayerType(LAYER_TYPE_SOFTWARE, null);
|
|
}
|
|
|
|
private void setupLayoutTransition() {
|
|
LayoutTransition transition = new LayoutTransition();
|
|
transition.setDuration(200);
|
|
|
|
ObjectAnimator appearAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
|
|
transition.setAnimator(LayoutTransition.APPEARING, appearAnimator);
|
|
transition.setInterpolator(LayoutTransition.APPEARING, Interpolators.ALPHA_IN);
|
|
|
|
ObjectAnimator disappearAnimator = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
|
|
transition.setInterpolator(LayoutTransition.DISAPPEARING, Interpolators.ALPHA_OUT);
|
|
transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnimator);
|
|
|
|
setLayoutTransition(transition);
|
|
}
|
|
|
|
public void setForceShowPercent(boolean show) {
|
|
setPercentShowMode(show ? MODE_ON : MODE_DEFAULT);
|
|
}
|
|
|
|
/**
|
|
* Force a particular mode of showing percent
|
|
*
|
|
* 0 - No preference
|
|
* 1 - Force on
|
|
* 2 - Force off
|
|
* @param mode desired mode (none, on, off)
|
|
*/
|
|
public void setPercentShowMode(@BatteryPercentMode int mode) {
|
|
mShowPercentMode = mode;
|
|
updateShowPercent();
|
|
}
|
|
|
|
/**
|
|
* Set {@code true} to turn off BatteryMeterView's subscribing to the tuner for updates, and
|
|
* thus avoid it controlling its own visibility
|
|
*
|
|
* @param ignore whether to ignore the tuner or not
|
|
*/
|
|
public void setIgnoreTunerUpdates(boolean ignore) {
|
|
mIgnoreTunerUpdates = ignore;
|
|
updateTunerSubscription();
|
|
}
|
|
|
|
private void updateTunerSubscription() {
|
|
if (mIgnoreTunerUpdates) {
|
|
unsubscribeFromTunerUpdates();
|
|
} else {
|
|
subscribeForTunerUpdates();
|
|
}
|
|
}
|
|
|
|
private void subscribeForTunerUpdates() {
|
|
if (mIsSubscribedForTunerUpdates || mIgnoreTunerUpdates) {
|
|
return;
|
|
}
|
|
|
|
Dependency.get(TunerService.class)
|
|
.addTunable(this, StatusBarIconController.ICON_BLACKLIST);
|
|
mIsSubscribedForTunerUpdates = true;
|
|
}
|
|
|
|
private void unsubscribeFromTunerUpdates() {
|
|
if (!mIsSubscribedForTunerUpdates) {
|
|
return;
|
|
}
|
|
|
|
Dependency.get(TunerService.class).removeTunable(this);
|
|
mIsSubscribedForTunerUpdates = false;
|
|
}
|
|
|
|
/**
|
|
* Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll
|
|
* revert back to dark-mode-based/tinted colors.
|
|
*
|
|
* @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all
|
|
* components
|
|
*/
|
|
public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) {
|
|
if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) {
|
|
return;
|
|
}
|
|
|
|
mUseWallpaperTextColors = shouldUseWallpaperTextColor;
|
|
|
|
if (mUseWallpaperTextColors) {
|
|
updateColors(
|
|
Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor),
|
|
Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColorSecondary),
|
|
Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor));
|
|
} else {
|
|
updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
|
|
mNonAdaptedSingleToneColor);
|
|
}
|
|
}
|
|
|
|
public void setColorsFromContext(Context context) {
|
|
if (context == null) {
|
|
return;
|
|
}
|
|
|
|
Context dualToneDarkTheme = new ContextThemeWrapper(context,
|
|
Utils.getThemeAttr(context, R.attr.darkIconTheme));
|
|
Context dualToneLightTheme = new ContextThemeWrapper(context,
|
|
Utils.getThemeAttr(context, R.attr.lightIconTheme));
|
|
mDarkModeSingleToneColor = Utils.getColorAttrDefaultColor(dualToneDarkTheme,
|
|
R.attr.singleToneColor);
|
|
mDarkModeBackgroundColor = Utils.getColorAttrDefaultColor(dualToneDarkTheme,
|
|
R.attr.backgroundColor);
|
|
mDarkModeFillColor = Utils.getColorAttrDefaultColor(dualToneDarkTheme,
|
|
R.attr.fillColor);
|
|
mLightModeSingleToneColor = Utils.getColorAttrDefaultColor(dualToneLightTheme,
|
|
R.attr.singleToneColor);
|
|
mLightModeBackgroundColor = Utils.getColorAttrDefaultColor(dualToneLightTheme,
|
|
R.attr.backgroundColor);
|
|
mLightModeFillColor = Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor);
|
|
}
|
|
|
|
@Override
|
|
public boolean hasOverlappingRendering() {
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void onTuningChanged(String key, String newValue) {
|
|
if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
|
|
ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onAttachedToWindow() {
|
|
super.onAttachedToWindow();
|
|
mBatteryController = Dependency.get(BatteryController.class);
|
|
mBatteryController.addCallback(this);
|
|
mUser = ActivityManager.getCurrentUser();
|
|
getContext().getContentResolver().registerContentObserver(
|
|
Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, mUser);
|
|
updateShowPercent();
|
|
subscribeForTunerUpdates();
|
|
mUserTracker.startTracking();
|
|
}
|
|
|
|
@Override
|
|
public void onDetachedFromWindow() {
|
|
super.onDetachedFromWindow();
|
|
mUserTracker.stopTracking();
|
|
mBatteryController.removeCallback(this);
|
|
getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
|
|
unsubscribeFromTunerUpdates();
|
|
}
|
|
|
|
@Override
|
|
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
|
|
mDrawable.setCharging(pluggedIn);
|
|
mDrawable.setBatteryLevel(level);
|
|
mCharging = pluggedIn;
|
|
mLevel = level;
|
|
updatePercentText();
|
|
setContentDescription(
|
|
getContext().getString(charging ? R.string.accessibility_battery_level_charging
|
|
: R.string.accessibility_battery_level, level));
|
|
}
|
|
|
|
@Override
|
|
public void onPowerSaveChanged(boolean isPowerSave) {
|
|
mDrawable.setPowerSaveEnabled(isPowerSave);
|
|
}
|
|
|
|
private TextView loadPercentView() {
|
|
return (TextView) LayoutInflater.from(getContext())
|
|
.inflate(R.layout.battery_percentage_view, null);
|
|
}
|
|
|
|
/**
|
|
* Updates percent view by removing old one and reinflating if necessary
|
|
*/
|
|
public void updatePercentView() {
|
|
if (mBatteryPercentView != null) {
|
|
removeView(mBatteryPercentView);
|
|
mBatteryPercentView = null;
|
|
}
|
|
updateShowPercent();
|
|
}
|
|
|
|
private void updatePercentText() {
|
|
if (mBatteryController == null) {
|
|
return;
|
|
}
|
|
|
|
if (mBatteryPercentView != null) {
|
|
if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
|
|
mBatteryController.getEstimatedTimeRemainingString((String estimate) -> {
|
|
if (estimate != null) {
|
|
mBatteryPercentView.setText(estimate);
|
|
} else {
|
|
setPercentTextAtCurrentLevel();
|
|
}
|
|
});
|
|
} else {
|
|
setPercentTextAtCurrentLevel();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void setPercentTextAtCurrentLevel() {
|
|
mBatteryPercentView.setText(
|
|
NumberFormat.getPercentInstance().format(mLevel / 100f));
|
|
}
|
|
|
|
private void updateShowPercent() {
|
|
final boolean showing = mBatteryPercentView != null;
|
|
final boolean systemSetting = 0 != Settings.System
|
|
.getIntForUser(getContext().getContentResolver(),
|
|
SHOW_BATTERY_PERCENT, 0, mUser);
|
|
|
|
if ((mShowPercentAvailable && systemSetting && mShowPercentMode != MODE_OFF)
|
|
|| mShowPercentMode == MODE_ON || mShowPercentMode == MODE_ESTIMATE) {
|
|
if (!showing) {
|
|
mBatteryPercentView = loadPercentView();
|
|
if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
|
|
updatePercentText();
|
|
addView(mBatteryPercentView,
|
|
new ViewGroup.LayoutParams(
|
|
LayoutParams.WRAP_CONTENT,
|
|
LayoutParams.MATCH_PARENT));
|
|
}
|
|
} else {
|
|
if (showing) {
|
|
removeView(mBatteryPercentView);
|
|
mBatteryPercentView = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDensityOrFontScaleChanged() {
|
|
scaleBatteryMeterViews();
|
|
}
|
|
|
|
/**
|
|
* Looks up the scale factor for status bar icons and scales the battery view by that amount.
|
|
*/
|
|
private void scaleBatteryMeterViews() {
|
|
Resources res = getContext().getResources();
|
|
TypedValue typedValue = new TypedValue();
|
|
|
|
res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
|
|
float iconScaleFactor = typedValue.getFloat();
|
|
|
|
int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
|
|
int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
|
|
int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
|
|
|
|
LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
|
|
(int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
|
|
scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
|
|
|
|
mBatteryIconView.setLayoutParams(scaledLayoutParams);
|
|
FontSizeUtils.updateFontSize(mBatteryPercentView, R.dimen.qs_time_expanded_size);
|
|
}
|
|
|
|
@Override
|
|
public void onDarkChanged(Rect area, float darkIntensity, int tint) {
|
|
float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0;
|
|
mNonAdaptedSingleToneColor = getColorForDarkIntensity(
|
|
intensity, mLightModeSingleToneColor, mDarkModeSingleToneColor);
|
|
mNonAdaptedForegroundColor = getColorForDarkIntensity(
|
|
intensity, mLightModeFillColor, mDarkModeFillColor);
|
|
mNonAdaptedBackgroundColor = getColorForDarkIntensity(
|
|
intensity, mLightModeBackgroundColor,mDarkModeBackgroundColor);
|
|
|
|
if (!mUseWallpaperTextColors) {
|
|
updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor,
|
|
mNonAdaptedSingleToneColor);
|
|
}
|
|
}
|
|
|
|
private void updateColors(int foregroundColor, int backgroundColor, int singleToneColor) {
|
|
mDrawable.setColors(foregroundColor, backgroundColor, singleToneColor);
|
|
mTextColor = singleToneColor;
|
|
if (mBatteryPercentView != null) {
|
|
mBatteryPercentView.setTextColor(singleToneColor);
|
|
}
|
|
}
|
|
|
|
private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) {
|
|
return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor);
|
|
}
|
|
|
|
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
|
String powerSave = mDrawable == null ? null : mDrawable.getPowerSaveEnabled() + "";
|
|
CharSequence percent = mBatteryPercentView == null ? null : mBatteryPercentView.getText();
|
|
pw.println(" BatteryMeterView:");
|
|
pw.println(" mDrawable.getPowerSave: " + powerSave);
|
|
pw.println(" mBatteryPercentView.getText(): " + percent);
|
|
pw.println(" mTextColor: #" + Integer.toHexString(mTextColor));
|
|
pw.println(" mLevel: " + mLevel);
|
|
pw.println(" mForceShowPercent: " + mForceShowPercent);
|
|
}
|
|
|
|
private final class SettingObserver extends ContentObserver {
|
|
public SettingObserver(Handler handler) {
|
|
super(handler);
|
|
}
|
|
|
|
@Override
|
|
public void onChange(boolean selfChange, Uri uri) {
|
|
super.onChange(selfChange, uri);
|
|
updateShowPercent();
|
|
}
|
|
}
|
|
}
|