summaryrefslogtreecommitdiffstats
path: root/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java
diff options
context:
space:
mode:
authorClark Scheff <clark@cyngn.com>2014-06-13 17:50:28 -0700
committerAndy Mast <andy@cyngn.com>2015-01-15 09:47:45 -0800
commit3c1b6dd43a9f53bb55c0c1fd8ab52da0989facaf (patch)
tree5e9253dd240c4c81d501b0a5a7240732ebd61bbc /src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java
parent7ccb6305880d89f7ab10356dbe1a9f4ba34796ce (diff)
downloadandroid_packages_providers_ThemesProvider-3c1b6dd43a9f53bb55c0c1fd8ab52da0989facaf.tar.gz
android_packages_providers_ThemesProvider-3c1b6dd43a9f53bb55c0c1fd8ab52da0989facaf.tar.bz2
android_packages_providers_ThemesProvider-3c1b6dd43a9f53bb55c0c1fd8ab52da0989facaf.zip
Themes: Port to CM12 [4/6]
Auto-generate previews. Id: Ia8f12bc852fb708e52ccc98641eea278756b198f Add MODIFIES_STATUS_BAR and MODIFIES_NAVIGATION_BAR Id: If1f81b122cfb5075835c70dff0bcfa6dea5d8de8 Use image with largest size for boot ani preview. Id: I3f2c16aa6ceb18387925e2cf2812d8c3ddf0d3d7 Use same components used in the original chooser. Icon previews will be generated using the same login used in the theme chooser v1. Id: Iafcd9f3d78fc86a66fe3635d27ee9fb7d47ae531 Remove (Default) from Holo's title. Holo may not be the default theme and therefore we should not add this to the title. The clients can handle determining which theme is the default on a device and act accordingly. Id: I162b3af97b05205c3ddf1e6ce130d05d9e0fe81f Add query for retrieving previews of currently applied components. Id: Ic3cffb4b762a047af40c72846e6e6bcf9a273619 Delete previews from provider when theme removed Id: Ica221885b929f7671f048d367648f7c6f82e4529 Alias status bar background as navbar_background Id: I3a32ea59e8fa94689d20fe92fbbc544b9da6e32b Fix provider not updating previews due to missing action. Id: Icd9f38ab499cfc617862be7638f06a9273dbdc22 Retrieve correct wallpaper size and store as jpeg Id: I346516a5860819f15a6b689e6da1a6bdf4f2540f Add style preview. What was originally called STYLE_PREVIEW is now STYLE_THUMBNAIL and STYLE_PREVIEW is now a larger sized image of various controls. Id: Iecf9785e992efa98360c84af2054d3a2a113f094 Use ThemeUtils methods for getting wallpaper paths. Id: I7fb269abe38560d638a2bad74fcfb7ed6a9a4c43 Add newly added navbar background in ThemesContract. [1/2] Id: I6fc92388a58b69c7a52053103cf85bb348f6144a Keep track of when a theme was installed Id: I9cdf637d63a4ed3d14b978a7d6c019aed0afe8e0 Update provider with ComposedIconInfo changes in f/b Id: I6ad0db5f55ed0dfb42c30cb5946f5c10af5b5a0f Use getThemedResourcesForApplication for icons Since we no longer pass bitmaps around in ComposedIconInfo we need to make sure the icons are actually attached to the resources in order to retrieve the drawables for the composed icons. Id: I7c92ee9d4114399024743c01d0df7fcc0284c150 Themes: Let ThemeService handle all theme processing [3/3] Id: Ied877eb5f1e96774d2e415970034d0b892765134 Add theme to db when installed and defer processing This allows the theme to show up in the theme provider even though the theme resources still need to be compiled. Once they are compiled the themes provider will update the database with the previews. Id: I2505cb875ee36975745a46469b2e55afe4fd1e68 Catch excpetions that occur when generating icon previews Id: I22e270911af12b6bd3cd69c84205d8455f74cf79 REF: THEMES-387 Catch all exceptions for styles and systemui previews Id: I4e3ee24e049d6dedba331175e21a143872de1f8e Presentable themes should contian icons, wallpaper and overlays The thems provider considered any theme with 2 or more components as a presentable theme which may not be represented well in the Theme Chooser. This patch makes it mandatory that a theme have icons, wallpaper and overlays in order to be considered a presentable theme. Id: If9918bd50125a4cd74b7926c5617c882eb89d5c2 REF: CHOOSER-2 Get working on CM12 Id: If585b1c9ee10b049f473ba2ca4ee313e09b6e715 Add column for target api This column will allow us to easily track what version of CM a theme was built for in case of incompatibility issues. The system theme will have a value of 0 indicating that it works on whatever the current api level is on the device. This prevents us from updating the row for the system font whenever the api level increases. Id: Ic43b63f2f801243914124043c4f4113a6b75b914 CM11 -> CM12 Upgrade [2/3] Renamed the "holo" theme to "system" Id: I82f2cde2e26e0b13c95ebb0fc61253c805c0a16e Fix String.format in upgradeToVersion11() Id: I13917d0c7ab34ff25a6326aebc0d707780bd6769 Change-Id: Ic98ec257e56a82d7faef37c0d2a2cfe360d6e35c
Diffstat (limited to 'src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java')
-rwxr-xr-xsrc/org/cyanogenmod/themes/provider/view/BatteryMeterView.java733
1 files changed, 733 insertions, 0 deletions
diff --git a/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java b/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java
new file mode 100755
index 0000000..4b772fa
--- /dev/null
+++ b/src/org/cyanogenmod/themes/provider/view/BatteryMeterView.java
@@ -0,0 +1,733 @@
+/*
+ * 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 org.cyanogenmod.themes.provider.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+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.os.Handler;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+import static org.cyanogenmod.themes.provider.util.SystemUiPreviewGenerator.SYSTEMUI_PACKAGE;
+
+public class BatteryMeterView extends View {
+ private static final String TAG = BatteryMeterView.class.getSimpleName();
+ private static final boolean ENABLE_PERCENT = true;
+
+ private static final String BATTERYMETER_COLOR_LEVELS = "batterymeter_color_levels";
+ private static final String BATTERYMETER_COLOR_VALUES = "batterymeter_color_values";
+ private static final String BATTERYMETER_FRAME_COLOR = "batterymeter_frame_color";
+ private static final String BATTERYMETER_BOLT_COLOR = "batterymeter_bolt_color";
+ private static final String BATTERYMETER_BOLT_POINTS = "batterymeter_bolt_points";
+ private static final String STATUS_BAR_CLOCK_COLOR = "status_bar_clock_color";
+
+ public static enum BatteryMeterMode {
+ BATTERY_METER_GONE,
+ BATTERY_METER_ICON_PORTRAIT,
+ BATTERY_METER_ICON_LANDSCAPE,
+ BATTERY_METER_CIRCLE,
+ BATTERY_METER_TEXT
+ }
+
+ private final Runnable mInvalidate = new Runnable() {
+ public void run() {
+ invalidateIfVisible();
+ }
+ };
+
+ private final Handler mHandler;
+
+ protected BatteryMeterMode mMeterMode = null;
+ protected boolean mShowPercent = false;
+ protected boolean mAttached;
+
+ private int mHeight;
+ private int mWidth;
+
+ final int[] mColors;
+ final int[] mBoltPoints;
+ final int mFrameColor;
+ final int mBoltColor;
+ final int mTextColor;
+
+ private BatteryMeterDrawable mBatteryMeterDrawable;
+ private final Object mLock = new Object();
+
+ private Resources mResources;
+
+ public BatteryMeterView(Context context, Resources res) {
+ this(context, null, 0, res);
+ }
+
+ public BatteryMeterView(Context context, AttributeSet attrs, Resources res) {
+ this(context, attrs, 0, res);
+ }
+
+ public BatteryMeterView(Context context, AttributeSet attrs, int defStyle, Resources res) {
+ super(context, attrs, defStyle);
+ mHandler = new Handler();
+
+ TypedArray levels = res.obtainTypedArray(res.getIdentifier(BATTERYMETER_COLOR_LEVELS,
+ "array", SYSTEMUI_PACKAGE));
+ TypedArray colors = res.obtainTypedArray(res.getIdentifier(BATTERYMETER_COLOR_VALUES,
+ "array", SYSTEMUI_PACKAGE));
+ TypedArray boltPoints = res.obtainTypedArray(res.getIdentifier(BATTERYMETER_BOLT_POINTS,
+ "array", SYSTEMUI_PACKAGE));
+
+ mResources = res;
+
+ int N = levels.length();
+ mColors = new int[2*N];
+ for (int i=0; i<N; i++) {
+ mColors[2*i] = levels.getInt(i, 0);
+ mColors[2*i+1] = colors.getColor(i, 0);
+ }
+ levels.recycle();
+ colors.recycle();
+
+ N = boltPoints.length();
+ mBoltPoints = new int[N];
+ for (int i = 0; i < N; i++) {
+ mBoltPoints[i] = boltPoints.getInt(i, 0);
+ }
+ boltPoints.recycle();
+
+ mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt(
+ context.getContentResolver(), "status_bar_show_battery_percent", 0);
+
+ mMeterMode = BatteryMeterMode.BATTERY_METER_CIRCLE;
+ mBatteryMeterDrawable = createBatteryMeterDrawable(mMeterMode);
+
+ mFrameColor = res.getColor(res.getIdentifier(BATTERYMETER_FRAME_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+ mBoltColor = res.getColor(res.getIdentifier(BATTERYMETER_BOLT_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+ mTextColor = res.getColor(res.getIdentifier(STATUS_BAR_CLOCK_COLOR, "color",
+ SYSTEMUI_PACKAGE));
+
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ protected BatteryMeterDrawable createBatteryMeterDrawable(BatteryMeterMode mode) {
+ Resources res = mResources;
+ switch (mode) {
+ case BATTERY_METER_CIRCLE:
+ return new CircleBatteryMeterDrawable(res);
+
+ case BATTERY_METER_TEXT:
+ return new TextBatteryMeterDrawable(res);
+
+ case BATTERY_METER_ICON_LANDSCAPE:
+ return new NormalBatteryMeterDrawable(res, true);
+
+ default:
+ return new NormalBatteryMeterDrawable(res, false);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ 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) {
+ width = (int)((TextBatteryMeterDrawable) mBatteryMeterDrawable).calculateMeasureWidth();
+ onSizeChanged(width, height, 0, 0); // Force a size changed event
+ } else if (mMeterMode.compareTo(BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE) == 0) {
+ width = (int)(height * 1.2f);
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mHeight = h;
+ mWidth = w;
+ synchronized (mLock) {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onSizeChanged(w, h, oldw, oldh);
+ }
+ }
+ }
+
+ protected void invalidateIfVisible() {
+ if (getVisibility() == View.VISIBLE && mAttached) {
+ if (mAttached) {
+ postInvalidate();
+ } else {
+ invalidate();
+ }
+ }
+ }
+
+ public int getColorForLevel(int percent) {
+ int thresh, color = 0;
+ for (int i=0; i<mColors.length; i+=2) {
+ thresh = mColors[i];
+ color = mColors[i+1];
+ if (percent <= thresh) return color;
+ }
+ return color;
+ }
+
+ public void setShowPercent(boolean show) {
+ if (ENABLE_PERCENT) {
+ mShowPercent = show;
+ invalidateIfVisible();
+ }
+ }
+
+ public void setMode(BatteryMeterMode mode) {
+ if (mMeterMode == mode) {
+ return;
+ }
+
+ mMeterMode = mode;
+ if (mode == BatteryMeterMode.BATTERY_METER_GONE) {
+ setVisibility(View.GONE);
+ synchronized (mLock) {
+ mBatteryMeterDrawable = null;
+ }
+ } else {
+ synchronized (mLock) {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onDispose();
+ }
+ mBatteryMeterDrawable = createBatteryMeterDrawable(mode);
+ }
+ if (mMeterMode == BatteryMeterMode.BATTERY_METER_ICON_PORTRAIT ||
+ mMeterMode == BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE) {
+ ((NormalBatteryMeterDrawable)mBatteryMeterDrawable).loadBoltPoints(
+ mContext.getResources());
+ }
+ setVisibility(View.VISIBLE);
+ postInvalidate();
+ requestLayout();
+ }
+ }
+
+ @Override
+ public void draw(Canvas c) {
+ synchronized (mLock) {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onDraw(c);
+ }
+ }
+ }
+
+ protected interface BatteryMeterDrawable {
+ void onDraw(Canvas c);
+ void onSizeChanged(int w, int h, int oldw, int oldh);
+ void onDispose();
+ }
+
+ protected class NormalBatteryMeterDrawable implements BatteryMeterDrawable {
+
+ public static final boolean SINGLE_DIGIT_PERCENT = false;
+
+ public static final int FULL = 96;
+ public static final int EMPTY = 4;
+
+ public static final float SUBPIXEL = 0.4f; // inset rects for softer edges
+
+ private boolean mDisposed;
+
+ protected final boolean mHorizontal;
+
+ private Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
+ private int mButtonHeight;
+ private float mTextHeight;
+
+ private final float[] mBoltPoints;
+ private final Path mBoltPath = new Path();
+
+ private final RectF mFrame = new RectF();
+ private final RectF mButtonFrame = new RectF();
+ private final RectF mClipFrame = new RectF();
+ private final RectF mBoltFrame = new RectF();
+
+ public NormalBatteryMeterDrawable(Resources res, boolean horizontal) {
+ 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);
+ mFramePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
+
+ mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBatteryPaint.setDither(true);
+ mBatteryPaint.setStrokeWidth(0);
+ mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+
+ mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setColor(mBoltColor);
+ Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD);
+ mTextPaint.setTypeface(font);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+
+ 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);
+
+ mBoltPaint = new Paint();
+ mBoltPaint.setAntiAlias(true);
+ mBoltPaint.setColor(mFrameColor);
+ mBoltPoints = loadBoltPoints(res);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mDisposed) return;
+
+ final int level = 50;
+
+ float drawFrac = (float) level / 100f;
+ final int pt = getPaddingTop() + (mHorizontal ? (int)(mHeight * 0.20f) : 0);
+ final int pl = getPaddingLeft();
+ final int pr = getPaddingRight();
+ final int pb = getPaddingBottom();
+ int height = mHeight - pt - pb;
+ int width = mWidth - pl - pr;
+
+ mButtonHeight = (int) ((mHorizontal ? width : height) * 0.12f);
+
+ mFrame.set(0, 0, width, height);
+ mFrame.offset(pl, pt);
+
+ if (mHorizontal) {
+ mButtonFrame.set(
+ /*cover frame border of intersecting area*/
+ width - (mButtonHeight + 5) - mFrame.left,
+ mFrame.top + height * 0.25f,
+ mFrame.right,
+ mFrame.bottom - height * 0.25f);
+
+ mButtonFrame.top += SUBPIXEL;
+ mButtonFrame.bottom -= SUBPIXEL;
+ mButtonFrame.right -= SUBPIXEL;
+ } else {
+ mButtonFrame.set(
+ mFrame.left + width * 0.25f,
+ mFrame.top,
+ mFrame.right - width * 0.25f,
+ mFrame.top + mButtonHeight + 5 /*cover frame border of intersecting area*/);
+
+ mButtonFrame.top += SUBPIXEL;
+ mButtonFrame.left += SUBPIXEL;
+ mButtonFrame.right -= SUBPIXEL;
+ }
+
+ if (mHorizontal) {
+ mFrame.right -= mButtonHeight;
+ } else {
+ mFrame.top += mButtonHeight;
+ }
+ mFrame.left += SUBPIXEL;
+ mFrame.top += SUBPIXEL;
+ mFrame.right -= SUBPIXEL;
+ mFrame.bottom -= SUBPIXEL;
+
+ // first, draw the battery shape
+ c.drawRect(mFrame, mFramePaint);
+
+ // fill 'er up
+ final int color = getColorForLevel(level);
+ mBatteryPaint.setColor(color);
+
+ if (level >= FULL) {
+ drawFrac = 1f;
+ } else if (level <= EMPTY) {
+ drawFrac = 0f;
+ }
+
+ c.drawRect(mButtonFrame, drawFrac == 1f ? mBatteryPaint : mFramePaint);
+
+ mClipFrame.set(mFrame);
+ if (mHorizontal) {
+ mClipFrame.right -= (mFrame.width() * (1f - drawFrac));
+ } else {
+ mClipFrame.top += (mFrame.height() * (1f - drawFrac));
+ }
+
+ c.save(Canvas.CLIP_SAVE_FLAG);
+ c.clipRect(mClipFrame);
+ c.drawRect(mFrame, mBatteryPaint);
+ c.restore();
+
+ // draw the bolt
+ final float bl = (int)(mFrame.left + mFrame.width() / (mHorizontal ? 9f : 4.5f));
+ final float bt = (int)(mFrame.top + mFrame.height() / (mHorizontal ? 4.5f : 6f));
+ final float br = (int)(mFrame.right - mFrame.width() / (mHorizontal ? 6f : 7f));
+ final float bb = (int)(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());
+ }
+ c.drawPath(mBoltPath, mBoltPaint);
+ if (mShowPercent) {
+ final float nofull = mHorizontal ? 0.75f : 0.6f;
+ final float single = mHorizontal ? 0.86f : 0.75f;
+ mTextPaint.setTextSize(height *
+ (SINGLE_DIGIT_PERCENT ? single
+ : nofull));
+ mTextHeight = -mTextPaint.getFontMetrics().ascent;
+
+ final String str = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level);
+ final float x = mWidth * 0.5f;
+ final float y = pt + (height + mTextHeight) * 0.47f;
+
+ c.drawText(str, x, y, mTextPaint);
+ }
+ }
+
+ @Override
+ public void onDispose() {
+ mHandler.removeCallbacks(mInvalidate);
+ mDisposed = true;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mWarningTextPaint.setTextSize(h * 0.75f);
+ }
+
+ private float[] loadBoltPoints(Resources res) {
+ final int[] pts = BatteryMeterView.this.mBoltPoints;
+ 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;
+ }
+ }
+
+ protected class CircleBatteryMeterDrawable implements BatteryMeterDrawable {
+
+ public static final float STROKE_WITH = 6.5f;
+
+ private boolean mDisposed;
+
+ private int mAnimOffset;
+ private boolean mIsAnimating; // stores charge-animation status to reliably
+ //remove callbacks
+
+ 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
+
+ private Paint mTextPaint;
+ private Paint mFrontPaint;
+ private Paint mBackPaint;
+ private Paint mBoltPaint;
+
+ private final RectF mBoltFrame = new RectF();
+ private final float[] mBoltPoints;
+ private final Path mBoltPath = new Path();
+
+ public CircleBatteryMeterDrawable(Resources res) {
+ super();
+ mDisposed = false;
+
+ mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mTextPaint.setColor(mTextColor);
+ Typeface font = Typeface.create("sans-serif-condensed", Typeface.NORMAL);
+ 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(mFrameColor);
+ mBackPaint.setStrokeCap(Paint.Cap.BUTT);
+ mBackPaint.setDither(true);
+ mBackPaint.setStrokeWidth(0);
+ mBackPaint.setStyle(Paint.Style.STROKE);
+ mBackPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
+
+ mBoltPaint = new Paint();
+ mBoltPaint.setAntiAlias(true);
+ mBoltPaint.setColor(getColorForLevel(50));
+ mBoltPoints = loadBoltPoints(res);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mDisposed) return;
+
+ if (mRectLeft == null) {
+ initSizeBasedStuff();
+ }
+
+ drawCircle(c, mTextX, mRectLeft);
+ }
+
+ @Override
+ public void onDispose() {
+ mHandler.removeCallbacks(mInvalidate);
+ mDisposed = true;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ initSizeBasedStuff();
+ }
+
+ private float[] loadBoltPoints(Resources res) {
+ final int[] pts = BatteryMeterView.this.mBoltPoints;
+ 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;
+ }
+
+ private void drawCircle(Canvas canvas, float textX, RectF drawRect) {
+ int level = 50;
+ Paint paint;
+
+ paint = mFrontPaint;
+ paint.setColor(getColorForLevel(level));
+
+ // draw thin gray ring first
+ canvas.drawArc(drawRect, 270, 360, false, mBackPaint);
+ // 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
+
+ // draw the bolt
+ final float bl = (int)(drawRect.left + drawRect.width() / 3.2f);
+ final float bt = (int)(drawRect.top + drawRect.height() / 4f);
+ final float br = (int)(drawRect.right - drawRect.width() / 5.2f);
+ final float bb = (int)(drawRect.bottom - drawRect.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());
+ }
+ canvas.drawPath(mBoltPath, mBoltPaint);
+ }
+
+ /**
+ * 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);
+
+ 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);
+
+ // 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;
+ }
+ }
+
+ protected class TextBatteryMeterDrawable implements BatteryMeterDrawable {
+
+ private static final boolean DRAW_LEVEL = false;
+
+ public static final int FULL = 96;
+ public static final int EMPTY = 4;
+
+ private boolean mDisposed;
+
+ private float mTextX;
+ private float mTextY;
+
+ private boolean mOldPlugged = false;
+ private int mOldLevel = -1;
+
+ private boolean mIsAnimating;
+ private int mAnimOffset;
+
+ private Paint mBackPaint;
+ private Paint mFrontPaint;
+
+ public TextBatteryMeterDrawable(Resources res) {
+ super();
+ mDisposed = false;
+ mIsAnimating = false;
+
+ DisplayMetrics dm = res.getDisplayMetrics();
+
+ mBackPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBackPaint.setTextAlign(Paint.Align.RIGHT);
+ mBackPaint.setColor(mFrameColor);
+ mBackPaint.setTextSize(16.0f * dm.density);
+
+ mFrontPaint = new Paint(mBackPaint);
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mDisposed) return;
+
+ int level = 50;
+ boolean plugged = true;
+
+ if (mOldLevel != level || mOldPlugged != plugged) {
+ mOldLevel = level;
+ mOldPlugged = plugged;
+
+ postInvalidate();
+ requestLayout();
+ return;
+ }
+
+ mFrontPaint.setColor(getColorForLevel(level));
+
+ // Is plugged? Then use the animation status
+ drawWithLevel(c, mAnimOffset, getLevel(level));
+ }
+
+ private void drawWithLevel(Canvas c, int level, String levelTxt) {
+ Rect bounds = getBounds(level);
+
+ // Draw the background
+ c.drawText(levelTxt, mTextX, mTextY, mBackPaint);
+
+ // Draw the foreground
+ c.save();
+ c.clipRect(0.0f, mTextY - ((level * bounds.height()) / 100.0f), mTextX, mTextY);
+ c.drawText(levelTxt, mTextX, mTextY, mFrontPaint);
+ c.restore();
+ }
+
+ private void drawWithoutLevel(Canvas c, String levelTxt) {
+ // We need to draw the overlay back paint to get the proper color
+ c.drawText(levelTxt, mTextX, mTextY, mBackPaint);
+ c.drawText(levelTxt, mTextX, mTextY, mFrontPaint);
+ }
+
+ @Override
+ public void onDispose() {
+ mHandler.removeCallbacks(mInvalidate);
+ mDisposed = true;
+ }
+
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ Rect bounds = getBounds(50);
+ float onedp = mContext.getResources().getDisplayMetrics().density * 0.5f;
+ float height = h - getPaddingBottom() - getPaddingTop();
+
+ mTextX = w;
+ mTextY = h - getPaddingBottom() - (height / 2 - bounds.height() /2) + onedp;
+ }
+
+ protected float calculateMeasureWidth() {
+ Rect bounds = getBounds(50);
+ float onedp = mContext.getResources().getDisplayMetrics().density;
+ return bounds.width() + getPaddingStart() + getPaddingEnd() + onedp;
+ }
+
+ private Rect getBounds(int level) {
+ Rect bounds = new Rect();
+ String levelTxt = getLevel(level);
+ mBackPaint.getTextBounds(levelTxt, 0, levelTxt.length(), bounds);
+ return bounds;
+ }
+
+ private String getLevel(int level) {
+ if (level == -1) {
+ return String.format("?", level);
+ }
+ return String.format("%s%%", level);
+ }
+
+ private void resetChargeAnimation() {
+ if (mIsAnimating) {
+ mIsAnimating = false;
+ mAnimOffset = 0;
+ mHandler.removeCallbacks(mInvalidate);
+ }
+ }
+ }
+}