1 /* 2 * Copyright (C) 2013 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; 17 18 import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS; 19 import static android.app.StatusBarManager.DISABLE_NONE; 20 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; 21 22 import android.animation.ArgbEvaluator; 23 import android.app.ActivityManager; 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.database.ContentObserver; 28 import android.graphics.Rect; 29 import android.net.Uri; 30 import android.os.Handler; 31 import android.provider.Settings; 32 import android.util.ArraySet; 33 import android.util.AttributeSet; 34 import android.util.TypedValue; 35 import android.view.ContextThemeWrapper; 36 import android.view.Gravity; 37 import android.view.LayoutInflater; 38 import android.view.View; 39 import android.view.ViewGroup; 40 import android.widget.ImageView; 41 import android.widget.LinearLayout; 42 import android.widget.TextView; 43 44 import com.android.settingslib.Utils; 45 import com.android.settingslib.graph.BatteryMeterDrawableBase; 46 import com.android.systemui.settings.CurrentUserTracker; 47 import com.android.systemui.statusbar.phone.StatusBarIconController; 48 import com.android.systemui.statusbar.policy.BatteryController; 49 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback; 50 import com.android.systemui.statusbar.policy.ConfigurationController; 51 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; 52 import com.android.systemui.statusbar.policy.DarkIconDispatcher; 53 import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver; 54 import com.android.systemui.statusbar.policy.IconLogger; 55 import com.android.systemui.tuner.TunerService; 56 import com.android.systemui.tuner.TunerService.Tunable; 57 import com.android.systemui.util.Utils.DisableStateTracker; 58 59 import java.text.NumberFormat; 60 61 public class BatteryMeterView extends LinearLayout implements 62 BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener { 63 64 private final BatteryMeterDrawableBase mDrawable; 65 private final String mSlotBattery; 66 private final ImageView mBatteryIconView; 67 private final CurrentUserTracker mUserTracker; 68 private TextView mBatteryPercentView; 69 70 private BatteryController mBatteryController; 71 private SettingObserver mSettingObserver; 72 private int mTextColor; 73 private int mLevel; 74 private boolean mForceShowPercent; 75 76 private int mDarkModeBackgroundColor; 77 private int mDarkModeFillColor; 78 79 private int mLightModeBackgroundColor; 80 private int mLightModeFillColor; 81 private float mDarkIntensity; 82 private int mUser; 83 84 /** 85 * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings. 86 */ 87 private boolean mUseWallpaperTextColors; 88 89 private int mNonAdaptedForegroundColor; 90 private int mNonAdaptedBackgroundColor; 91 92 public BatteryMeterView(Context context) { 93 this(context, null, 0); 94 } 95 96 public BatteryMeterView(Context context, AttributeSet attrs) { 97 this(context, attrs, 0); 98 } 99 100 public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) { 101 super(context, attrs, defStyle); 102 103 setOrientation(LinearLayout.HORIZONTAL); 104 setGravity(Gravity.CENTER_VERTICAL | Gravity.START); 105 106 TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView, 107 defStyle, 0); 108 final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor, 109 context.getColor(R.color.meter_background_color)); 110 mDrawable = new BatteryMeterDrawableBase(context, frameColor); 111 atts.recycle(); 112 113 mSettingObserver = new SettingObserver(new Handler(context.getMainLooper())); 114 115 addOnAttachStateChangeListener( 116 new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS)); 117 118 mSlotBattery = context.getString( 119 com.android.internal.R.string.status_bar_battery); 120 mBatteryIconView = new ImageView(context); 121 mBatteryIconView.setImageDrawable(mDrawable); 122 final MarginLayoutParams mlp = new MarginLayoutParams( 123 getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width), 124 getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height)); 125 mlp.setMargins(0, 0, 0, 126 getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom)); 127 addView(mBatteryIconView, mlp); 128 129 updateShowPercent(); 130 setColorsFromContext(context); 131 // Init to not dark at all. 132 onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT); 133 134 mUserTracker = new CurrentUserTracker(mContext) { 135 @Override 136 public void onUserSwitched(int newUserId) { 137 mUser = newUserId; 138 getContext().getContentResolver().unregisterContentObserver(mSettingObserver); 139 getContext().getContentResolver().registerContentObserver( 140 Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, 141 newUserId); 142 } 143 }; 144 145 setClipChildren(false); 146 setClipToPadding(false); 147 } 148 149 public void setForceShowPercent(boolean show) { 150 mForceShowPercent = show; 151 updateShowPercent(); 152 } 153 154 /** 155 * Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll 156 * revert back to dark-mode-based/tinted colors. 157 * 158 * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all 159 * components 160 */ 161 public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) { 162 if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) { 163 return; 164 } 165 166 mUseWallpaperTextColors = shouldUseWallpaperTextColor; 167 168 if (mUseWallpaperTextColors) { 169 updateColors( 170 Utils.getColorAttr(mContext, R.attr.wallpaperTextColor), 171 Utils.getColorAttr(mContext, R.attr.wallpaperTextColorSecondary)); 172 } else { 173 updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor); 174 } 175 } 176 177 public void setColorsFromContext(Context context) { 178 if (context == null) { 179 return; 180 } 181 182 Context dualToneDarkTheme = new ContextThemeWrapper(context, 183 Utils.getThemeAttr(context, R.attr.darkIconTheme)); 184 Context dualToneLightTheme = new ContextThemeWrapper(context, 185 Utils.getThemeAttr(context, R.attr.lightIconTheme)); 186 mDarkModeBackgroundColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.backgroundColor); 187 mDarkModeFillColor = Utils.getColorAttr(dualToneDarkTheme, R.attr.fillColor); 188 mLightModeBackgroundColor = Utils.getColorAttr(dualToneLightTheme, R.attr.backgroundColor); 189 mLightModeFillColor = Utils.getColorAttr(dualToneLightTheme, R.attr.fillColor); 190 } 191 192 @Override 193 public boolean hasOverlappingRendering() { 194 return false; 195 } 196 197 @Override 198 public void onTuningChanged(String key, String newValue) { 199 if (StatusBarIconController.ICON_BLACKLIST.equals(key)) { 200 ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue); 201 boolean hidden = icons.contains(mSlotBattery); 202 Dependency.get(IconLogger.class).onIconVisibility(mSlotBattery, !hidden); 203 setVisibility(hidden ? View.GONE : View.VISIBLE); 204 } 205 } 206 207 @Override 208 public void onAttachedToWindow() { 209 super.onAttachedToWindow(); 210 mBatteryController = Dependency.get(BatteryController.class); 211 mBatteryController.addCallback(this); 212 mUser = ActivityManager.getCurrentUser(); 213 getContext().getContentResolver().registerContentObserver( 214 Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, mUser); 215 updateShowPercent(); 216 Dependency.get(TunerService.class) 217 .addTunable(this, StatusBarIconController.ICON_BLACKLIST); 218 Dependency.get(ConfigurationController.class).addCallback(this); 219 mUserTracker.startTracking(); 220 } 221 222 @Override 223 public void onDetachedFromWindow() { 224 super.onDetachedFromWindow(); 225 mUserTracker.stopTracking(); 226 mBatteryController.removeCallback(this); 227 getContext().getContentResolver().unregisterContentObserver(mSettingObserver); 228 Dependency.get(TunerService.class).removeTunable(this); 229 Dependency.get(ConfigurationController.class).removeCallback(this); 230 } 231 232 @Override 233 public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) { 234 mDrawable.setBatteryLevel(level); 235 mDrawable.setCharging(pluggedIn); 236 mLevel = level; 237 updatePercentText(); 238 setContentDescription( 239 getContext().getString(charging ? R.string.accessibility_battery_level_charging 240 : R.string.accessibility_battery_level, level)); 241 } 242 243 @Override 244 public void onPowerSaveChanged(boolean isPowerSave) { 245 mDrawable.setPowerSave(isPowerSave); 246 } 247 248 private TextView loadPercentView() { 249 return (TextView) LayoutInflater.from(getContext()) 250 .inflate(R.layout.battery_percentage_view, null); 251 } 252 253 private void updatePercentText() { 254 if (mBatteryPercentView != null) { 255 mBatteryPercentView.setText( 256 NumberFormat.getPercentInstance().format(mLevel / 100f)); 257 } 258 } 259 260 private void updateShowPercent() { 261 final boolean showing = mBatteryPercentView != null; 262 if (0 != Settings.System.getIntForUser(getContext().getContentResolver(), 263 SHOW_BATTERY_PERCENT, 0, mUser) || mForceShowPercent) { 264 if (!showing) { 265 mBatteryPercentView = loadPercentView(); 266 if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor); 267 updatePercentText(); 268 addView(mBatteryPercentView, 269 new ViewGroup.LayoutParams( 270 LayoutParams.WRAP_CONTENT, 271 LayoutParams.MATCH_PARENT)); 272 } 273 } else { 274 if (showing) { 275 removeView(mBatteryPercentView); 276 mBatteryPercentView = null; 277 } 278 } 279 } 280 281 @Override 282 public void onDensityOrFontScaleChanged() { 283 scaleBatteryMeterViews(); 284 } 285 286 /** 287 * Looks up the scale factor for status bar icons and scales the battery view by that amount. 288 */ 289 private void scaleBatteryMeterViews() { 290 Resources res = getContext().getResources(); 291 TypedValue typedValue = new TypedValue(); 292 293 res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); 294 float iconScaleFactor = typedValue.getFloat(); 295 296 int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height); 297 int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width); 298 int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom); 299 300 LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams( 301 (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor)); 302 scaledLayoutParams.setMargins(0, 0, 0, marginBottom); 303 304 mBatteryIconView.setLayoutParams(scaledLayoutParams); 305 FontSizeUtils.updateFontSize(mBatteryPercentView, R.dimen.qs_time_expanded_size); 306 } 307 308 @Override 309 public void onDarkChanged(Rect area, float darkIntensity, int tint) { 310 mDarkIntensity = darkIntensity; 311 312 float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0; 313 mNonAdaptedForegroundColor = getColorForDarkIntensity( 314 intensity, mLightModeFillColor, mDarkModeFillColor); 315 mNonAdaptedBackgroundColor = getColorForDarkIntensity( 316 intensity, mLightModeBackgroundColor,mDarkModeBackgroundColor); 317 318 if (!mUseWallpaperTextColors) { 319 updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor); 320 } 321 } 322 323 private void updateColors(int foregroundColor, int backgroundColor) { 324 mDrawable.setColors(foregroundColor, backgroundColor); 325 mTextColor = foregroundColor; 326 if (mBatteryPercentView != null) { 327 mBatteryPercentView.setTextColor(foregroundColor); 328 } 329 } 330 331 public void setFillColor(int color) { 332 if (mLightModeFillColor == color) { 333 return; 334 } 335 mLightModeFillColor = color; 336 onDarkChanged(new Rect(), mDarkIntensity, DarkIconDispatcher.DEFAULT_ICON_TINT); 337 } 338 339 private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) { 340 return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor); 341 } 342 343 private final class SettingObserver extends ContentObserver { 344 public SettingObserver(Handler handler) { 345 super(handler); 346 } 347 348 @Override 349 public void onChange(boolean selfChange, Uri uri) { 350 super.onChange(selfChange, uri); 351 updateShowPercent(); 352 } 353 } 354 } 355