diff options
author | Robert Burns <burnsra@gmail.com> | 2012-07-17 22:01:45 -0400 |
---|---|---|
committer | Robert Burns <burnsra@gmail.com> | 2012-11-26 21:17:14 -0500 |
commit | 1abc2dd1021793806ee3239994f4b3e1ca50233b (patch) | |
tree | 099655d338831e92cc8d445bf54b0a88ef170d6f /packages/SystemUI/src/com/android/systemui/statusbar/policy/CircleBattery.java | |
parent | ee52a8d30aa78ee98cc5f93cc330aac1add7fa02 (diff) | |
download | frameworks_base-1abc2dd1021793806ee3239994f4b3e1ca50233b.zip frameworks_base-1abc2dd1021793806ee3239994f4b3e1ca50233b.tar.gz frameworks_base-1abc2dd1021793806ee3239994f4b3e1ca50233b.tar.bz2 |
Forward port Status Bar interface
Patch Set 1: Signal / Clock / Battery / Notification Count
Patch Set 2: Updated commit message
Change-Id: Iecd4bb06b95b14888916331f5d36327f3206aeef
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/statusbar/policy/CircleBattery.java')
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/statusbar/policy/CircleBattery.java | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CircleBattery.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CircleBattery.java new file mode 100644 index 0000000..e521b02 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CircleBattery.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2012 Sven Dawitz for 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. + * 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.statusbar.policy; + +import android.view.ViewGroup.LayoutParams; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.ContentObserver; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Rect; +import android.graphics.RectF; +import android.os.BatteryManager; +import android.os.Handler; +import android.provider.Settings; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; + +import com.android.internal.R; + +/*** + * Note about CircleBattery Implementation: + * + * Unfortunately, we cannot use BatteryController or DockBatteryController here, + * since communication between controller and this view is not possible without + * huge changes. As a result, this Class is doing everything by itself, + * monitoring battery level and battery settings. + */ + +public class CircleBattery extends ImageView { + private Handler mHandler; + private Context mContext; + private BatteryReceiver mBatteryReceiver = null; + + // state variables + private boolean mAttached; // whether or not attached to a window + private boolean mActivated; // whether or not activated due to system settings + private boolean mPercentage; // whether or not to show percentage number + private boolean mIsCharging; // whether or not device is currently charging + private int mLevel; // current battery level + private int mAnimOffset; // current level of charging animation + private boolean mIsAnimating; // stores charge-animation status to reliably remove callbacks + private int mDockLevel; // current dock battery level + private boolean mDockIsCharging;// whether or not dock battery is currently charging + private boolean mIsDocked = false; // whether or not dock battery is connected + + private int mCircleSize; // draw size of circle. read rather complicated from + // another status bar icon, so it fits the icon size + // no matter the dps and resolution + private RectF mRectLeft; // contains the precalculated rect used in drawArc(), derived from mCircleSize + private RectF mRectRight; // contains the precalculated rect used in drawArc() for dock battery + private Float mTextLeftX; // precalculated x position for drawText() to appear centered + private Float mTextY; // precalculated y position for drawText() to appear vertical-centered + private Float mTextRightX; // precalculated x position for dock battery drawText() + + // quiet a lot of paint variables. helps to move cpu-usage from actual drawing to initialization + private Paint mPaintFont; + private Paint mPaintGray; + private Paint mPaintSystem; + private Paint mPaintRed; + + // runnable to invalidate view via mHandler.postDelayed() call + private final Runnable mInvalidate = new Runnable() { + public void run() { + if(mActivated && mAttached) { + invalidate(); + } + } + }; + + // observes changes in system battery settings and enables/disables view accordingly + class SettingsObserver extends ContentObserver { + SettingsObserver(Handler handler) { + super(handler); + } + + public void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.STATUS_BAR_BATTERY), false, this); + onChange(true); + } + + @Override + public void onChange(boolean selfChange) { + int batteryStyle = (Settings.System.getInt(mContext.getContentResolver(), + Settings.System.STATUS_BAR_BATTERY, 0)); + + mActivated = (batteryStyle == BatteryController.BATTERY_STYLE_CIRCLE || batteryStyle == BatteryController.BATTERY_STYLE_CIRCLE_PERCENT); + mPercentage = (batteryStyle == BatteryController.BATTERY_STYLE_CIRCLE_PERCENT); + + setVisibility(mActivated ? View.VISIBLE : View.GONE); + if (mBatteryReceiver != null) { + mBatteryReceiver.updateRegistration(); + } + + if (mActivated && mAttached) { + invalidate(); + } + } + } + + // keeps track of current battery level and charger-plugged-state + class BatteryReceiver extends BroadcastReceiver { + private boolean mIsRegistered = false; + + public BatteryReceiver(Context context) { + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { + mLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0); + mIsCharging = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0; + + if (mActivated && mAttached) { + LayoutParams l = getLayoutParams(); + l.width = mCircleSize + getPaddingLeft() + + (mIsDocked ? mCircleSize + getPaddingLeft() : 0); + setLayoutParams(l); + + invalidate(); + } + } + } + + private void registerSelf() { + if (!mIsRegistered) { + mIsRegistered = true; + + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_BATTERY_CHANGED); + mContext.registerReceiver(mBatteryReceiver, filter); + } + } + + private void unregisterSelf() { + if (mIsRegistered) { + mIsRegistered = false; + mContext.unregisterReceiver(this); + } + } + + private void updateRegistration() { + if (mActivated && mAttached) { + registerSelf(); + } else { + unregisterSelf(); + } + } + } + + /*** + * Start of CircleBattery implementation + */ + public CircleBattery(Context context) { + this(context, null); + } + + public CircleBattery(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public CircleBattery(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + mContext = context; + mHandler = new Handler(); + + SettingsObserver settingsObserver = new SettingsObserver(mHandler); + settingsObserver.observe(); + mBatteryReceiver = new BatteryReceiver(mContext); + + // initialize and setup all paint variables + // stroke width is later set in initSizeBasedStuff() + Resources res = getResources(); + + mPaintFont = new Paint(); + mPaintFont.setAntiAlias(true); + mPaintFont.setDither(true); + mPaintFont.setStyle(Paint.Style.STROKE); + + mPaintGray = new Paint(mPaintFont); + mPaintSystem = new Paint(mPaintFont); + mPaintRed = new Paint(mPaintFont); + + mPaintFont.setColor(res.getColor(R.color.holo_blue_dark)); + mPaintSystem.setColor(res.getColor(R.color.holo_blue_dark)); + // could not find the darker definition anywhere in resources + // do not want to use static 0x404040 color value. would break theming. + mPaintGray.setColor(res.getColor(R.color.darker_gray)); + mPaintRed.setColor(res.getColor(R.color.holo_red_light)); + + // font needs some extra settings + mPaintFont.setTextAlign(Align.CENTER); + mPaintFont.setFakeBoldText(true); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (!mAttached) { + mAttached = true; + mBatteryReceiver.updateRegistration(); + mHandler.postDelayed(mInvalidate, 250); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mAttached) { + mAttached = false; + mBatteryReceiver.updateRegistration(); + mRectLeft = null; // makes sure, size based variables get + // recalculated on next attach + mCircleSize = 0; // makes sure, mCircleSize is reread from icons on + // next attach + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mCircleSize == 0) { + initSizeMeasureIconHeight(); + } + + setMeasuredDimension(mCircleSize + getPaddingLeft() + + (mIsDocked ? mCircleSize + getPaddingLeft() : 0), mCircleSize); + } + + private void drawCircle(Canvas canvas, int level, int animOffset, float textX, RectF drawRect) { + Paint usePaint = mPaintSystem; + // turn red at 14% - same level android battery warning appears + if (level <= 14) { + usePaint = mPaintRed; + } + + // pad circle percentage to 100% once it reaches 97% + // for one, the circle looks odd with a too small gap, + // for another, some phones never reach 100% due to hardware design + int padLevel = level; + if (padLevel >= 97) { + padLevel = 100; + } + + // draw thin gray ring first + canvas.drawArc(drawRect, 270, 360, false, mPaintGray); + // draw colored arc representing charge level + canvas.drawArc(drawRect, 270 + animOffset, 3.6f * padLevel, false, usePaint); + // if chosen by options, draw percentage text in the middle + // always skip percentage when 100, so layout doesnt break + if (level < 100 && mPercentage) { + mPaintFont.setColor(usePaint.getColor()); + canvas.drawText(Integer.toString(level), textX, mTextY, mPaintFont); + } + + } + + @Override + protected void onDraw(Canvas canvas) { + if (mRectLeft == null) { + initSizeBasedStuff(); + } + + updateChargeAnim(); + + if (mIsDocked) { + drawCircle(canvas, mDockLevel, (mDockIsCharging ? mAnimOffset : 0), mTextLeftX, mRectLeft); + drawCircle(canvas, mLevel, (mIsCharging ? mAnimOffset : 0), mTextRightX, mRectRight); + } else { + drawCircle(canvas, mLevel, (mIsCharging ? mAnimOffset : 0), mTextLeftX, mRectLeft); + } + } + + /*** + * updates the animation counter + * cares for timed callbacks to continue animation cycles + * uses mInvalidate for delayed invalidate() callbacks + */ + private void updateChargeAnim() { + if (!(mIsCharging || mDockIsCharging) || (mLevel >= 97 && mDockLevel >= 97)) { + if (mIsAnimating) { + mIsAnimating = false; + mAnimOffset = 0; + mHandler.removeCallbacks(mInvalidate); + } + return; + } + + mIsAnimating = true; + + if (mAnimOffset > 360) { + mAnimOffset = 0; + } else { + mAnimOffset += 3; + } + + mHandler.removeCallbacks(mInvalidate); + mHandler.postDelayed(mInvalidate, 50); + } + + /*** + * 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() { + if (mCircleSize == 0) { + initSizeMeasureIconHeight(); + } + + mPaintFont.setTextSize(mCircleSize / 2f); + + float strokeWidth = mCircleSize / 6.5f; + mPaintRed.setStrokeWidth(strokeWidth); + mPaintSystem.setStrokeWidth(strokeWidth); + mPaintGray.setStrokeWidth(strokeWidth / 3.5f); + + // 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); + int off = pLeft + mCircleSize; + mRectRight = new RectF(mRectLeft.left + off, mRectLeft.top, mRectLeft.right + off, + mRectLeft.bottom); + + // calculate Y position for text + Rect bounds = new Rect(); + mPaintFont.getTextBounds("99", 0, "99".length(), bounds); + mTextLeftX = mCircleSize / 2.0f + getPaddingLeft(); + mTextRightX = mTextLeftX + off; + // the +1 at end of formular balances out rounding issues. works out on all resolutions + mTextY = mCircleSize / 2.0f + (bounds.bottom - bounds.top) / 2.0f - strokeWidth / 2.0f + 1; + + // force new measurement for wrap-content xml tag + onMeasure(0, 0); + } + + /*** + * we need to measure the size of the circle battery by checking another + * resource. unfortunately, those resources have transparent/empty borders + * so we have to count the used pixel manually and deduct the size from + * it. quiet complicated, but the only way to fit properly into the + * statusbar for all resolutions + */ + private void initSizeMeasureIconHeight() { + final Bitmap measure = BitmapFactory.decodeResource(getResources(), + com.android.systemui.R.drawable.stat_sys_wifi_signal_4_fully); + final int x = measure.getWidth() / 2; + + mCircleSize = 0; + for (int y = 0; y < measure.getHeight(); y++) { + int alpha = Color.alpha(measure.getPixel(x, y)); + if (alpha > 5) { + mCircleSize++; + } + } + } +} |