diff options
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/BatteryMeterView.java')
-rwxr-xr-x | packages/SystemUI/src/com/android/systemui/BatteryMeterView.java | 805 |
1 files changed, 313 insertions, 492 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index e606156..06c2957 100755 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2013-14 The Android Open Source Project + * Copyright (C) 2016 The CyanogenMod Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,43 +17,47 @@ package com.android.systemui; -import com.android.systemui.statusbar.policy.BatteryController; -import com.android.systemui.statusbar.policy.BatteryStateRegistar; - import android.animation.ArgbEvaluator; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.content.res.Resources; +import android.content.res.ThemeConfig; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; -import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; import android.graphics.Rect; -import android.graphics.RectF; import android.graphics.Typeface; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.os.BatteryManager; import android.os.Bundle; import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; import android.view.View; +import com.android.systemui.statusbar.policy.BatteryController; +import com.android.systemui.statusbar.policy.BatteryStateRegistar; + +import org.cyanogenmod.graphics.drawable.StopMotionVectorDrawable; + public class BatteryMeterView extends View implements DemoMode, BatteryController.BatteryStateChangeCallback { public static final String TAG = BatteryMeterView.class.getSimpleName(); public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST"; - private static final int FULL = 96; - - private static final float BOLT_LEVEL_THRESHOLD = 0.3f; // opaque bolt below this fraction - private final int[] mColors; protected boolean mShowPercent = true; - private float mButtonHeightFraction; - private float mSubpixelSmoothingLeft; - private float mSubpixelSmoothingRight; public enum BatteryMeterMode { BATTERY_METER_GONE, @@ -66,14 +71,9 @@ public class BatteryMeterView extends View implements DemoMode, private int mWidth; private String mWarningString; private final int mCriticalLevel; - private final int mFrameColor; private boolean mAnimationsEnabled; - private final Path mShapePath = new Path(); - private final Path mClipPath = new Path(); - private final Path mTextPath = new Path(); - private BatteryStateRegistar mBatteryStateRegistar; private BatteryController mBatteryController; private boolean mPowerSaveEnabled; @@ -94,6 +94,9 @@ public class BatteryMeterView extends View implements DemoMode, private BatteryMeterDrawable mBatteryMeterDrawable; private int mIconTint = Color.WHITE; + private int mCurrentBackgroundColor = 0; + private int mCurrentFillColor = 0; + protected class BatteryTracker extends BroadcastReceiver { public static final int UNKNOWN_LEVEL = -1; @@ -225,8 +228,6 @@ public class BatteryMeterView extends View implements DemoMode, final Resources res = context.getResources(); TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView, defStyle, 0); - mFrameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor, - res.getColor(R.color.batterymeter_frame_color)); TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels); TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values); @@ -242,12 +243,6 @@ public class BatteryMeterView extends View implements DemoMode, mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol); mCriticalLevel = getContext().getResources().getInteger( com.android.internal.R.integer.config_criticalBatteryWarningLevel); - mButtonHeightFraction = context.getResources().getFraction( - R.fraction.battery_button_height_fraction, 1, 1); - mSubpixelSmoothingLeft = context.getResources().getFraction( - R.fraction.battery_subpixel_smoothing_left, 1, 1); - mSubpixelSmoothingRight = context.getResources().getFraction( - R.fraction.battery_subpixel_smoothing_right, 1, 1); mDarkModeBackgroundColor = context.getColor(R.color.dark_mode_icon_color_dual_tone_background); @@ -260,17 +255,13 @@ public class BatteryMeterView extends View implements DemoMode, } protected BatteryMeterDrawable createBatteryMeterDrawable(BatteryMeterMode mode) { - Resources res = mContext.getResources(); + Resources res = getResources(); switch (mode) { - case BATTERY_METER_CIRCLE: - return new CircleBatteryMeterDrawable(res); - case BATTERY_METER_ICON_LANDSCAPE: - return new NormalBatteryMeterDrawable(res, true); case BATTERY_METER_TEXT: case BATTERY_METER_GONE: return null; default: - return new NormalBatteryMeterDrawable(res, false); + return new AllInOneBatteryMeterDrawable(res, mode); } } @@ -279,13 +270,8 @@ public class BatteryMeterView extends View implements DemoMode, int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); - if (mMeterMode == BatteryMeterMode.BATTERY_METER_CIRCLE) { - height += (CircleBatteryMeterDrawable.STROKE_WITH / 3); - width = height; - } else if (mMeterMode == BatteryMeterMode.BATTERY_METER_TEXT) { + if (mMeterMode == BatteryMeterMode.BATTERY_METER_TEXT) { onSizeChanged(width, height, 0, 0); // Force a size changed event - } else if (mMeterMode == BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE) { - width = (int)(height * 1.2f); } setMeasuredDimension(width, height); @@ -378,11 +364,6 @@ public class BatteryMeterView extends View implements DemoMode, mBatteryMeterDrawable.onDispose(); } mBatteryMeterDrawable = createBatteryMeterDrawable(mode); - if (mMeterMode == BatteryMeterMode.BATTERY_METER_ICON_PORTRAIT || - mMeterMode == BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE) { - ((NormalBatteryMeterDrawable)mBatteryMeterDrawable).loadBoltPoints( - mContext.getResources()); - } if (tracker.present) { setVisibility(View.VISIBLE); requestLayout(); @@ -394,7 +375,6 @@ public class BatteryMeterView extends View implements DemoMode, } public int getColorForLevel(int percent) { - // If we are in power save mode, always use the normal color. if (mPowerSaveEnabled) { return mColors[mColors.length-1]; @@ -418,9 +398,9 @@ public class BatteryMeterView extends View implements DemoMode, public void setDarkIntensity(float darkIntensity) { if (mBatteryMeterDrawable != null) { - int backgroundColor = getBackgroundColor(darkIntensity); - int fillColor = getFillColor(darkIntensity); - mBatteryMeterDrawable.setDarkIntensity(backgroundColor, fillColor); + mCurrentBackgroundColor = getBackgroundColor(darkIntensity); + mCurrentFillColor = getFillColor(darkIntensity); + mBatteryMeterDrawable.setDarkIntensity(mCurrentBackgroundColor, mCurrentFillColor); } } @@ -482,253 +462,83 @@ public class BatteryMeterView extends View implements DemoMode, void setDarkIntensity(int backgroundColor, int fillColor); } - protected class NormalBatteryMeterDrawable implements BatteryMeterDrawable { + protected class AllInOneBatteryMeterDrawable implements BatteryMeterDrawable { private static final boolean SINGLE_DIGIT_PERCENT = false; private static final boolean SHOW_100_PERCENT = false; private boolean mDisposed; - protected final boolean mHorizontal; + private boolean mIsAnimating; // stores charge-animation status to remove callbacks + + private float mTextX, mTextY; // precalculated position for drawText() to appear centered - private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint; - private float mTextHeight, mWarningTextHeight; + private boolean mInitialized; - private int mChargeColor; - private final float[] mBoltPoints; - private final Path mBoltPath = new Path(); + private Paint mTextAndBoltPaint; + private Paint mWarningTextPaint; + private Paint mClearPaint; - private final RectF mFrame = new RectF(); - private final RectF mButtonFrame = new RectF(); - private final RectF mBoltFrame = new RectF(); + private LayerDrawable mBatteryDrawable; + private Drawable mFrameDrawable; + private StopMotionVectorDrawable mLevelDrawable; + private Drawable mBoltDrawable; - public NormalBatteryMeterDrawable(Resources res, boolean horizontal) { + private BatteryMeterMode mMode; + private int mTextGravity; + + public AllInOneBatteryMeterDrawable(Resources res, BatteryMeterMode mode) { super(); - mHorizontal = horizontal; - mDisposed = false; - mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mFramePaint.setColor(mFrameColor); - mFramePaint.setDither(true); - mFramePaint.setStrokeWidth(0); - mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE); + loadBatteryDrawables(res, mode); - mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBatteryPaint.setDither(true); - mBatteryPaint.setStrokeWidth(0); - mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE); + mMode = mode; + mDisposed = false; - mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + // load text gravity and blend mode + int[] attrs = new int[] {android.R.attr.gravity, R.attr.blendMode}; + int resId = getBatteryDrawableStyleResourceForMode(mode); + PorterDuff.Mode xferMode = PorterDuff.Mode.XOR; + if (resId != 0) { + TypedArray a = getContext().obtainStyledAttributes( + getBatteryDrawableStyleResourceForMode(mode), attrs); + mTextGravity = a.getInt(0, Gravity.CENTER); + xferMode = PorterDuff.intToMode(a.getInt(1, + PorterDuff.modeToInt(PorterDuff.Mode.XOR))); + } else { + mTextGravity = Gravity.CENTER; + } + Log.d(TAG, "mTextGravity=" + mTextGravity); + + mTextAndBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG); Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD); - mTextPaint.setTypeface(font); - mTextPaint.setTextAlign(Paint.Align.CENTER); + mTextAndBoltPaint.setTypeface(font); + mTextAndBoltPaint.setTextAlign(getPaintAlignmentFromGravity(mTextGravity)); + mTextAndBoltPaint.setXfermode(new PorterDuffXfermode(xferMode)); + mTextAndBoltPaint.setColor(mCurrentFillColor != 0 + ? mCurrentFillColor + : res.getColor(R.color.batterymeter_bolt_color)); mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mWarningTextPaint.setColor(mColors[1]); font = Typeface.create("sans-serif", Typeface.BOLD); mWarningTextPaint.setTypeface(font); - mWarningTextPaint.setTextAlign(Paint.Align.CENTER); - - mChargeColor = getResources().getColor(R.color.batterymeter_charge_color); + mWarningTextPaint.setTextAlign(getPaintAlignmentFromGravity(mTextGravity)); - mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBoltPaint.setColor(res.getColor(R.color.batterymeter_bolt_color)); - mBoltPoints = loadBoltPoints(res); + mClearPaint = new Paint(); + mClearPaint.setColor(0); } @Override public void onDraw(Canvas c, BatteryTracker tracker) { if (mDisposed) return; - final int level = tracker.level; - - if (level == BatteryTracker.UNKNOWN_LEVEL) return; - - float drawFrac = (float) level / 100f; - final int pt = getPaddingTop() + (mHorizontal ? (int)(mHeight * 0.12f) : 0); - final int pl = getPaddingLeft(); - final int pr = getPaddingRight(); - final int pb = getPaddingBottom() + (mHorizontal ? (int)(mHeight * 0.08f) : 0); - final int height = mHeight - pt - pb; - final int width = mWidth - pl - pr; - - final int buttonHeight = (int) ((mHorizontal ? width : height) * mButtonHeightFraction); - - mFrame.set(0, 0, width, height); - mFrame.offset(pl, pt); - - if (mHorizontal) { - mButtonFrame.set( - /*cover frame border of intersecting area*/ - width - buttonHeight - mFrame.left, - mFrame.top + Math.round(height * 0.25f), - mFrame.right, - mFrame.bottom - Math.round(height * 0.25f)); - - mButtonFrame.top += mSubpixelSmoothingLeft; - mButtonFrame.bottom -= mSubpixelSmoothingRight; - mButtonFrame.right -= mSubpixelSmoothingRight; - } else { - // button-frame: area above the battery body - mButtonFrame.set( - mFrame.left + Math.round(width * 0.25f), - mFrame.top, - mFrame.right - Math.round(width * 0.25f), - mFrame.top + buttonHeight); - - mButtonFrame.top += mSubpixelSmoothingLeft; - mButtonFrame.left += mSubpixelSmoothingLeft; - mButtonFrame.right -= mSubpixelSmoothingRight; - } - - // frame: battery body area - - if (mHorizontal) { - mFrame.right -= buttonHeight; - } else { - mFrame.top += buttonHeight; - } - mFrame.left += mSubpixelSmoothingLeft; - mFrame.top += mSubpixelSmoothingLeft; - mFrame.right -= mSubpixelSmoothingRight; - mFrame.bottom -= mSubpixelSmoothingRight; - - // set the battery charging color - mBatteryPaint.setColor(tracker.plugged ? mChargeColor : getColorForLevel(level)); - - if (level >= FULL) { - drawFrac = 1f; - } else if (level <= mCriticalLevel) { - drawFrac = 0f; - } - - final float levelTop; - - if (drawFrac == 1f) { - if (mHorizontal) { - levelTop = mButtonFrame.right; - } else { - levelTop = mButtonFrame.top; - } - } else { - if (mHorizontal) { - levelTop = (mFrame.right - (mFrame.width() * (1f - drawFrac))); - } else { - levelTop = (mFrame.top + (mFrame.height() * (1f - drawFrac))); - } - } - - // define the battery shape - mShapePath.reset(); - mShapePath.moveTo(mButtonFrame.left, mButtonFrame.top); - if (mHorizontal) { - mShapePath.lineTo(mButtonFrame.right, mButtonFrame.top); - mShapePath.lineTo(mButtonFrame.right, mButtonFrame.bottom); - mShapePath.lineTo(mButtonFrame.left, mButtonFrame.bottom); - mShapePath.lineTo(mFrame.right, mFrame.bottom); - mShapePath.lineTo(mFrame.left, mFrame.bottom); - mShapePath.lineTo(mFrame.left, mFrame.top); - mShapePath.lineTo(mButtonFrame.left, mFrame.top); - mShapePath.lineTo(mButtonFrame.left, mButtonFrame.top); - } else { - mShapePath.lineTo(mButtonFrame.right, mButtonFrame.top); - mShapePath.lineTo(mButtonFrame.right, mFrame.top); - mShapePath.lineTo(mFrame.right, mFrame.top); - mShapePath.lineTo(mFrame.right, mFrame.bottom); - mShapePath.lineTo(mFrame.left, mFrame.bottom); - mShapePath.lineTo(mFrame.left, mFrame.top); - mShapePath.lineTo(mButtonFrame.left, mFrame.top); - mShapePath.lineTo(mButtonFrame.left, mButtonFrame.top); + if (!mInitialized) { + init(); } - if (tracker.plugged) { - // define the bolt shape - final float bl = mFrame.left + mFrame.width() / (mHorizontal ? 9f : 4.5f); - final float bt = mFrame.top + mFrame.height() / (mHorizontal ? 4.5f : 6f); - final float br = mFrame.right - mFrame.width() / (mHorizontal ? 6f : 7f); - final float bb = mFrame.bottom - mFrame.height() / (mHorizontal ? 7f : 10f); - if (mBoltFrame.left != bl || mBoltFrame.top != bt - || mBoltFrame.right != br || mBoltFrame.bottom != bb) { - mBoltFrame.set(bl, bt, br, bb); - mBoltPath.reset(); - mBoltPath.moveTo( - mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), - mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); - for (int i = 2; i < mBoltPoints.length; i += 2) { - mBoltPath.lineTo( - mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(), - mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height()); - } - mBoltPath.lineTo( - mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), - mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); - } - - float boltPct = mHorizontal ? - (mBoltFrame.left - levelTop) / (mBoltFrame.left - mBoltFrame.right) : - (mBoltFrame.bottom - levelTop) / (mBoltFrame.bottom - mBoltFrame.top); - boltPct = Math.min(Math.max(boltPct, 0), 1); - if (boltPct <= BOLT_LEVEL_THRESHOLD) { - // draw the bolt if opaque - c.drawPath(mBoltPath, mBoltPaint); - } else { - // otherwise cut the bolt out of the overall shape - mShapePath.op(mBoltPath, Path.Op.DIFFERENCE); - } - } - - // compute percentage text - boolean pctOpaque = false; - float pctX = 0, pctY = 0; - String pctText = null; - if (!tracker.plugged && level > mCriticalLevel && mShowPercent) { - mTextPaint.setColor(getColorForLevel(level)); - final float full = mHorizontal ? 0.60f : 0.45f; - final float nofull = mHorizontal ? 0.75f : 0.6f; - final float single = mHorizontal ? 0.86f : 0.75f; - mTextPaint.setTextSize(height * - (SINGLE_DIGIT_PERCENT ? single - : (tracker.level == 100 ? full : nofull))); - mTextHeight = -mTextPaint.getFontMetrics().ascent; - pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level); - pctX = mWidth * 0.5f; - pctY = (mHeight + mTextHeight) * 0.47f; - if (mHorizontal) { - pctOpaque = pctX > levelTop; - } else { - pctOpaque = levelTop > pctY; - } - if (!pctOpaque) { - mTextPath.reset(); - mTextPaint.getTextPath(pctText, 0, pctText.length(), pctX, pctY, mTextPath); - // cut the percentage text out of the overall shape - mShapePath.op(mTextPath, Path.Op.DIFFERENCE); - } - } - - // draw the battery shape background - c.drawPath(mShapePath, mFramePaint); - - // draw the battery shape, clipped to charging level - if (mHorizontal) { - mFrame.right = levelTop; - } else { - mFrame.top = levelTop; - } - mClipPath.reset(); - mClipPath.addRect(mFrame, Path.Direction.CCW); - mShapePath.op(mClipPath, Path.Op.INTERSECT); - c.drawPath(mShapePath, mBatteryPaint); - - if (!tracker.plugged) { - if (level <= mCriticalLevel) { - // draw the warning text - final float x = mWidth * 0.5f; - final float y = (mHeight + mWarningTextHeight) * 0.48f; - c.drawText(mWarningString, x, y, mWarningTextPaint); - } else if (pctOpaque) { - // draw the percentage text - c.drawText(pctText, pctX, pctY, mTextPaint); - } + drawBattery(c, tracker); + if (mAnimationsEnabled) { + // TODO: Allow custom animations to be used } } @@ -740,282 +550,293 @@ public class BatteryMeterView extends View implements DemoMode, @Override public void setDarkIntensity(int backgroundColor, int fillColor) { mIconTint = fillColor; - mFramePaint.setColor(backgroundColor); - mBoltPaint.setColor(fillColor); - mChargeColor = fillColor; + // Make bolt fully opaque for increased visibility + mBoltDrawable.setTint(0xff000000 | fillColor); + mFrameDrawable.setTint(backgroundColor); + updateBoltDrawableLayer(mBatteryDrawable, mBoltDrawable); invalidate(); } @Override public void onSizeChanged(int w, int h, int oldw, int oldh) { - mHeight = h; - mWidth = w; - mWarningTextPaint.setTextSize(h * 0.75f); - mWarningTextHeight = -mWarningTextPaint.getFontMetrics().ascent; - } - - private float[] loadBoltPoints(Resources res) { - final int[] pts = res.getIntArray(getBoltPointsArrayResource()); - int maxX = 0, maxY = 0; - for (int i = 0; i < pts.length; i += 2) { - maxX = Math.max(maxX, pts[i]); - maxY = Math.max(maxY, pts[i + 1]); - } - final float[] ptsF = new float[pts.length]; - for (int i = 0; i < pts.length; i += 2) { - ptsF[i] = (float)pts[i] / maxX; - ptsF[i + 1] = (float)pts[i + 1] / maxY; - } - return ptsF; + init(); } - protected int getBoltPointsArrayResource() { - return mHorizontal - ? R.array.batterymeter_inverted_bolt_points - : R.array.batterymeter_bolt_points; + private boolean isThemeApplied() { + ThemeConfig themeConfig = ThemeConfig.getBootTheme(getContext().getContentResolver()); + return themeConfig != null && + !ThemeConfig.SYSTEM_DEFAULT.equals(themeConfig.getOverlayForStatusBar()); } - } - protected class CircleBatteryMeterDrawable implements BatteryMeterDrawable { - private static final boolean SINGLE_DIGIT_PERCENT = false; - private static final boolean SHOW_100_PERCENT = false; - - private static final int FULL = 96; + private void checkBatteryMeterDrawableValid(Resources res, BatteryMeterMode mode) { + final int resId = getBatteryDrawableResourceForMode(mode); + final Drawable batteryDrawable; + try { + batteryDrawable = res.getDrawable(resId); + } catch (Resources.NotFoundException e) { + throw new BatteryMeterDrawableException(res.getResourceName(resId) + " is an " + + "invalid drawable", e); + } - public static final float STROKE_WITH = 6.5f; + // check that the drawable is a LayerDrawable + if (!(batteryDrawable instanceof LayerDrawable)) { + throw new BatteryMeterDrawableException("Expected a LayerDrawable but received a " + + batteryDrawable.getClass().getSimpleName()); + } - private boolean mDisposed; + final LayerDrawable layerDrawable = (LayerDrawable) batteryDrawable; + final Drawable frame = layerDrawable.findDrawableByLayerId(R.id.battery_frame); + final Drawable level = layerDrawable.findDrawableByLayerId(R.id.battery_fill); + final Drawable bolt = layerDrawable.findDrawableByLayerId( + R.id.battery_charge_indicator); + // now check that the required layers exist and are of the correct type + if (frame == null) { + throw new BatteryMeterDrawableException("Missing battery_frame drawble"); + } + if (bolt == null) { + throw new BatteryMeterDrawableException( + "Missing battery_charge_indicator drawable"); + } + if (level != null) { + // check that the level drawable is an AnimatedVectorDrawable + if (!(level instanceof AnimatedVectorDrawable)) { + throw new BatteryMeterDrawableException("Expected a AnimatedVectorDrawable " + + "but received a " + level.getClass().getSimpleName()); + } + // make sure we can stop motion animate the level drawable + try { + StopMotionVectorDrawable smvd = new StopMotionVectorDrawable(level); + smvd.setCurrentFraction(0.5f); + } catch (Exception e) { + throw new BatteryMeterDrawableException("Unable to perform stop motion on " + + "battery_fill drawable", e); + } + } else { + throw new BatteryMeterDrawableException("Missing battery_fill drawable"); + } + } - private int mAnimOffset; - private boolean mIsAnimating; // stores charge-animation status to reliably - //remove callbacks + private void loadBatteryDrawables(Resources res, BatteryMeterMode mode) { + if (isThemeApplied()) { + try { + checkBatteryMeterDrawableValid(res, mode); + } catch (BatteryMeterDrawableException e) { + Log.w(TAG, "Invalid themed battery meter drawable, falling back to system", e); + final Context context = getContext(); + PackageManager pm = getContext().getPackageManager(); + try { + res = pm.getThemedResourcesForApplication(context.getPackageName(), + ThemeConfig.SYSTEM_DEFAULT); + } catch (PackageManager.NameNotFoundException nnfe) { + /* ignore, this should not happen */ + } + } + } - private int mCircleSize; // draw size of circle - private RectF mRectLeft; // contains the precalculated rect used in drawArc(), - // derived from mCircleSize - private float mTextX, mTextY; // precalculated position for drawText() to appear centered + int drawableResId = getBatteryDrawableResourceForMode(mode); + mBatteryDrawable = (LayerDrawable) res.getDrawable(drawableResId); + mFrameDrawable = mBatteryDrawable.findDrawableByLayerId(R.id.battery_frame); + mFrameDrawable.setTint(mCurrentBackgroundColor != 0 + ? mCurrentBackgroundColor + : res.getColor(R.color.batterymeter_frame_color)); + // set the animated vector drawable we will be stop animating + Drawable levelDrawable = mBatteryDrawable.findDrawableByLayerId(R.id.battery_fill); + mLevelDrawable = new StopMotionVectorDrawable(levelDrawable); + mBoltDrawable = mBatteryDrawable.findDrawableByLayerId(R.id.battery_charge_indicator); + } - private Paint mTextPaint; - private Paint mFrontPaint; - private Paint mBackPaint; - private Paint mBoltPaint; - private Paint mWarningTextPaint; + private void drawBattery(Canvas canvas, BatteryTracker tracker) { + boolean unknownStatus = tracker.status == BatteryManager.BATTERY_STATUS_UNKNOWN; + int level = tracker.level; - private final RectF mBoltFrame = new RectF(); + if (unknownStatus || tracker.status == BatteryManager.BATTERY_STATUS_FULL) { + level = 100; + } - private int mChargeColor; - private final float[] mBoltPoints; - private final Path mBoltPath = new Path(); + mTextAndBoltPaint.setColor(getColorForLevel(level)); - public CircleBatteryMeterDrawable(Resources res) { - super(); - mDisposed = false; + // Make sure we don't draw the charge indicator if not plugged in + Drawable d = mBatteryDrawable.findDrawableByLayerId(R.id.battery_charge_indicator); + if (d instanceof BitmapDrawable) { + // In case we are using a BitmapDrawable, which we should be unless something bad + // happened, we need to change the paint rather than the alpha in case the blendMode + // has been set to clear. Clear always clears regardless of alpha level ;) + BitmapDrawable bd = (BitmapDrawable) d; + bd.getPaint().set(tracker.plugged ? mTextAndBoltPaint : mClearPaint); + } else { + d.setAlpha(tracker.plugged ? 255 : 0); + } - mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD); - mTextPaint.setTypeface(font); - mTextPaint.setTextAlign(Paint.Align.CENTER); - - mFrontPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mFrontPaint.setStrokeCap(Paint.Cap.BUTT); - mFrontPaint.setDither(true); - mFrontPaint.setStrokeWidth(0); - mFrontPaint.setStyle(Paint.Style.STROKE); - - mBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBackPaint.setColor(res.getColor(R.color.batterymeter_frame_color)); - mBackPaint.setStrokeCap(Paint.Cap.BUTT); - mBackPaint.setDither(true); - mBackPaint.setStrokeWidth(0); - mBackPaint.setStyle(Paint.Style.STROKE); + // Now draw the level indicator + // set the level and tint color of the fill drawable + mLevelDrawable.setCurrentFraction(level / 100f); + mLevelDrawable.setTint(getColorForLevel(level)); + mBatteryDrawable.draw(canvas); - mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mWarningTextPaint.setColor(mColors[1]); - font = Typeface.create("sans-serif", Typeface.BOLD); - mWarningTextPaint.setTypeface(font); - mWarningTextPaint.setTextAlign(Paint.Align.CENTER); + // if chosen by options, draw percentage text in the middle + // always skip percentage when 100, so layout doesnt break + if (unknownStatus) { + mTextAndBoltPaint.setColor(getContext().getColor(R.color.batterymeter_frame_color)); + canvas.drawText("?", mTextX, mTextY, mTextAndBoltPaint); - mChargeColor = getResources().getColor(R.color.batterymeter_charge_color); + } else if (!tracker.plugged) { + drawPercentageText(canvas, tracker); + } + } - mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mBoltPaint.setColor(res.getColor(R.color.batterymeter_bolt_color)); - mBoltPoints = loadBoltPoints(res); + private void drawPercentageText(Canvas canvas, BatteryTracker tracker) { + final int level = tracker.level; + if (level > mCriticalLevel + && (mShowPercent && !(level == 100 && !SHOW_100_PERCENT))) { + // draw the percentage text + String pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level); + mTextAndBoltPaint.setColor(getColorForLevel(level)); + canvas.drawText(pctText, mTextX, mTextY, mTextAndBoltPaint); + } else if (level <= mCriticalLevel) { + // draw the warning text + canvas.drawText(mWarningString, mTextX, mTextY, mWarningTextPaint); + } } - @Override - public void onDraw(Canvas c, BatteryTracker tracker) { - if (mDisposed) return; + /** + * initializes all size dependent variables + */ + private void init() { + // not much we can do with zero width or height, we'll get another pass later + if (mWidth <= 0 || mHeight <=0) return; + + final float widthDiv2 = mWidth / 2f; + // text size is width / 2 - 2dp for wiggle room + final float textSize = widthDiv2 - getResources().getDisplayMetrics().density * 2; + mTextAndBoltPaint.setTextSize(textSize); + mWarningTextPaint.setTextSize(textSize); + + int pLeft = getPaddingLeft(); + Rect iconBounds = new Rect(pLeft, 0, pLeft + mWidth, mHeight); + mBatteryDrawable.setBounds(iconBounds); - if (mRectLeft == null) { - initSizeBasedStuff(); + // calculate text position + Rect bounds = new Rect(); + mTextAndBoltPaint.getTextBounds("99", 0, "99".length(), bounds); + boolean isRtl = isLayoutRtl(); + + // compute mTextX based on text gravity + if ((mTextGravity & Gravity.START) == Gravity.START) { + mTextX = isRtl ? mWidth : 0; + } else if ((mTextGravity & Gravity.END) == Gravity.END) { + mTextX = isRtl ? 0 : mWidth; + } else if ((mTextGravity & Gravity.LEFT) == Gravity.LEFT) { + mTextX = 0; + }else if ((mTextGravity & Gravity.RIGHT) == Gravity.RIGHT) { + mTextX = mWidth; + } else { + mTextX = widthDiv2 + pLeft; } - drawCircle(c, tracker, mTextX, mRectLeft); - if (mAnimationsEnabled) { - updateChargeAnim(tracker); + // compute mTextY based on text gravity + if ((mTextGravity & Gravity.TOP) == Gravity.TOP) { + mTextY = bounds.height(); + } else if ((mTextGravity & Gravity.BOTTOM) == Gravity.BOTTOM) { + mTextY = mHeight; + } else { + mTextY = widthDiv2 + bounds.height() / 2.0f; } - } - @Override - public void onDispose() { - mDisposed = true; + updateBoltDrawableLayer(mBatteryDrawable, mBoltDrawable); + + mInitialized = true; } - @Override - public void setDarkIntensity(int backgroundColor, int fillColor) { - mIconTint = fillColor; - mBoltPaint.setColor(fillColor); - mChargeColor = fillColor; - invalidate(); + private int getBatteryDrawableResourceForMode(BatteryMeterMode mode) { + switch (mode) { + case BATTERY_METER_ICON_LANDSCAPE: + return R.drawable.ic_battery_landscape; + case BATTERY_METER_CIRCLE: + return R.drawable.ic_battery_circle; + case BATTERY_METER_ICON_PORTRAIT: + return R.drawable.ic_battery_portrait; + default: + return 0; + } } - @Override - public void onSizeChanged(int w, int h, int oldw, int oldh) { - initSizeBasedStuff(); + private int getBatteryDrawableStyleResourceForMode(BatteryMeterMode mode) { + switch (mode) { + case BATTERY_METER_ICON_LANDSCAPE: + return R.style.BatteryMeterViewDrawable_Landscape; + case BATTERY_METER_CIRCLE: + return R.style.BatteryMeterViewDrawable_Circle; + case BATTERY_METER_ICON_PORTRAIT: + return R.style.BatteryMeterViewDrawable_Portrait; + default: + return R.style.BatteryMeterViewDrawable; + } } - private float[] loadBoltPoints(Resources res) { - final int[] pts = res.getIntArray(getBoltPointsArrayResource()); - int maxX = 0, maxY = 0; - for (int i = 0; i < pts.length; i += 2) { - maxX = Math.max(maxX, pts[i]); - maxY = Math.max(maxY, pts[i + 1]); + private Paint.Align getPaintAlignmentFromGravity(int gravity) { + boolean isRtl = isLayoutRtl(); + if ((gravity & Gravity.START) == Gravity.START) { + return isRtl ? Paint.Align.RIGHT : Paint.Align.LEFT; } - final float[] ptsF = new float[pts.length]; - for (int i = 0; i < pts.length; i += 2) { - ptsF[i] = (float)pts[i] / maxX; - ptsF[i + 1] = (float)pts[i + 1] / maxY; + if ((gravity & Gravity.END) == Gravity.END) { + return isRtl ? Paint.Align.LEFT : Paint.Align.RIGHT; } - return ptsF; - } + if ((gravity & Gravity.LEFT) == Gravity.LEFT) return Paint.Align.LEFT; + if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) return Paint.Align.RIGHT; - protected int getBoltPointsArrayResource() { - return R.array.batterymeter_bolt_points; + // default to center + return Paint.Align.CENTER; } - private void drawCircle(Canvas canvas, BatteryTracker tracker, - float textX, RectF drawRect) { - boolean unknownStatus = tracker.status == BatteryManager.BATTERY_STATUS_UNKNOWN; - int level = tracker.level; - Paint paint; - - if (unknownStatus) { - paint = mBackPaint; - level = 100; // Draw all the circle; + // Creates a BitmapDrawable of the bolt so we can make use of the XOR xfer mode with vector + // based drawables + private void updateBoltDrawableLayer(LayerDrawable batteryDrawable, Drawable boltDrawable) { + BitmapDrawable newBoltDrawable; + if (boltDrawable instanceof BitmapDrawable) { + newBoltDrawable = (BitmapDrawable) boltDrawable.mutate(); } else { - paint = mFrontPaint; - paint.setColor(getColorForLevel(level)); - if (tracker.status == BatteryManager.BATTERY_STATUS_FULL) { - level = 100; + Bitmap boltBitmap = createBoltBitmap(boltDrawable); + if (boltBitmap == null) { + // not much to do with a null bitmap so keep original bolt for now + return; } + Rect bounds = boltDrawable.getBounds(); + newBoltDrawable = new BitmapDrawable(getResources(), boltBitmap); + newBoltDrawable.setBounds(bounds); } - - // draw thin gray ring first - canvas.drawArc(drawRect, 270, 360, false, mBackPaint); - if (level != 0) { - // draw colored arc representing charge level - canvas.drawArc(drawRect, 270 + mAnimOffset, 3.6f * level, false, paint); - } - // if chosen by options, draw percentage text in the middle - // always skip percentage when 100, so layout doesnt break - if (unknownStatus) { - mTextPaint.setColor(paint.getColor()); - canvas.drawText("?", textX, mTextY, mTextPaint); - - } else if (tracker.plugged) { - canvas.drawPath(mBoltPath, mBoltPaint); - } else { - if (level > mCriticalLevel - && (mShowPercent && !(tracker.level == 100 && !SHOW_100_PERCENT))) { - // draw the percentage text - String pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level); - mTextPaint.setColor(paint.getColor()); - canvas.drawText(pctText, textX, mTextY, mTextPaint); - } else if (level <= mCriticalLevel) { - // draw the warning text - canvas.drawText(mWarningString, textX, mTextY, mWarningTextPaint); + newBoltDrawable.getPaint().set(mTextAndBoltPaint); + batteryDrawable.setDrawableByLayerId(R.id.battery_charge_indicator, newBoltDrawable); + } + + private Bitmap createBoltBitmap(Drawable boltDrawable) { + // not much we can do with zero width or height, we'll get another pass later + if (mWidth <= 0 || mHeight <= 0) return null; + + Bitmap bolt; + if (!(boltDrawable instanceof BitmapDrawable)) { + int pLeft = getPaddingLeft(); + Rect iconBounds = new Rect(pLeft, 0, pLeft + mWidth, mHeight); + bolt = Bitmap.createBitmap(iconBounds.width(), iconBounds.height(), + Bitmap.Config.ARGB_8888); + if (bolt != null) { + Canvas c = new Canvas(bolt); + c.drawColor(-1, PorterDuff.Mode.CLEAR); + boltDrawable.draw(c); } - } - } - - /** - * updates the animation counter - * cares for timed callbacks to continue animation cycles - * uses mInvalidate for delayed invalidate() callbacks - */ - private void updateChargeAnim(BatteryTracker tracker) { - // Stop animation when battery is full or after the meter - // rotated back to 0 after unplugging. - if (!tracker.shouldIndicateCharging() - || tracker.status == BatteryManager.BATTERY_STATUS_FULL - || tracker.level == 0) { - mIsAnimating = false; } else { - mIsAnimating = true; - } - - if (mAnimOffset > 360) { - mAnimOffset = 0; - } - - boolean continueAnimation = mIsAnimating || mAnimOffset != 0; - - if (continueAnimation) { - mAnimOffset += 3; + bolt = ((BitmapDrawable) boltDrawable).getBitmap(); } - if (continueAnimation) { - postInvalidateDelayed(50); - } + return bolt; } - /** - * initializes all size dependent variables - * sets stroke width and text size of all involved paints - * YES! i think the method name is appropriate - */ - private void initSizeBasedStuff() { - mCircleSize = Math.min(getMeasuredWidth(), getMeasuredHeight()); - mTextPaint.setTextSize(mCircleSize / 2f); - mWarningTextPaint.setTextSize(mCircleSize / 2f); - - float strokeWidth = mCircleSize / STROKE_WITH; - mFrontPaint.setStrokeWidth(strokeWidth); - mBackPaint.setStrokeWidth(strokeWidth); - - // calculate rectangle for drawArc calls - int pLeft = getPaddingLeft(); - mRectLeft = new RectF(pLeft + strokeWidth / 2.0f, 0 + strokeWidth / 2.0f, mCircleSize - - strokeWidth / 2.0f + pLeft, mCircleSize - strokeWidth / 2.0f); + private class BatteryMeterDrawableException extends RuntimeException { + public BatteryMeterDrawableException(String detailMessage) { + super(detailMessage); + } - // calculate Y position for text - Rect bounds = new Rect(); - mTextPaint.getTextBounds("99", 0, "99".length(), bounds); - mTextX = mCircleSize / 2.0f + getPaddingLeft(); - // the +1dp at end of formula balances out rounding issues.works out on all resolutions - mTextY = mCircleSize / 2.0f + (bounds.bottom - bounds.top) / 2.0f - - strokeWidth / 2.0f + getResources().getDisplayMetrics().density; - - // draw the bolt - final float bl = (int) (mRectLeft.left + mRectLeft.width() / 3.2f); - final float bt = (int) (mRectLeft.top + mRectLeft.height() / 4f); - final float br = (int) (mRectLeft.right - mRectLeft.width() / 5.2f); - final float bb = (int) (mRectLeft.bottom - mRectLeft.height() / 8f); - if (mBoltFrame.left != bl || mBoltFrame.top != bt - || mBoltFrame.right != br || mBoltFrame.bottom != bb) { - mBoltFrame.set(bl, bt, br, bb); - mBoltPath.reset(); - mBoltPath.moveTo( - mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), - mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); - for (int i = 2; i < mBoltPoints.length; i += 2) { - mBoltPath.lineTo( - mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(), - mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height()); - } - mBoltPath.lineTo( - mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(), - mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height()); + public BatteryMeterDrawableException(String detailMessage, Throwable throwable) { + super(detailMessage, throwable); } } } |