/* * Copyright (C) 2015 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 android.animation.ArgbEvaluator; import android.annotation.Nullable; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; import com.android.systemui.statusbar.policy.BatteryController; public class BatteryMeterDrawable extends Drawable implements BatteryController.BatteryStateChangeCallback { private static final float ASPECT_RATIO = 9.5f / 14.5f; public static final String TAG = BatteryMeterDrawable.class.getSimpleName(); public static final String SHOW_PERCENT_SETTING = "status_bar_show_battery_percent"; private static final boolean SINGLE_DIGIT_PERCENT = false; private static final int FULL = 96; private static final float BOLT_LEVEL_THRESHOLD = 0.3f; // opaque bolt below this fraction private final int[] mColors; private final int mIntrinsicWidth; private final int mIntrinsicHeight; private boolean mShowPercent; private float mButtonHeightFraction; private float mSubpixelSmoothingLeft; private float mSubpixelSmoothingRight; private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint, mPlusPaint; private float mTextHeight, mWarningTextHeight; private int mIconTint = Color.WHITE; private float mOldDarkIntensity = 0f; private int mHeight; private int mWidth; private String mWarningString; private final int mCriticalLevel; private int mChargeColor; private final float[] mBoltPoints; private final Path mBoltPath = new Path(); private final float[] mPlusPoints; private final Path mPlusPath = new Path(); private final RectF mFrame = new RectF(); private final RectF mButtonFrame = new RectF(); private final RectF mBoltFrame = new RectF(); private final RectF mPlusFrame = new RectF(); private final Path mShapePath = new Path(); private final Path mClipPath = new Path(); private final Path mTextPath = new Path(); private BatteryController mBatteryController; private boolean mPowerSaveEnabled; private int mDarkModeBackgroundColor; private int mDarkModeFillColor; private int mLightModeBackgroundColor; private int mLightModeFillColor; private final SettingObserver mSettingObserver = new SettingObserver(); private final Context mContext; private final Handler mHandler; private int mLevel = -1; private boolean mPluggedIn; private boolean mListening; public BatteryMeterDrawable(Context context, Handler handler, int frameColor) { mContext = context; mHandler = handler; final Resources res = context.getResources(); TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels); TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values); final int N = levels.length(); mColors = new int[2*N]; for (int i=0; i= FULL) { drawFrac = 1f; } else if (level <= mCriticalLevel) { drawFrac = 0f; } final float levelTop = drawFrac == 1f ? mButtonFrame.top : (mFrame.top + (mFrame.height() * (1f - drawFrac))); // define the battery shape mShapePath.reset(); mShapePath.moveTo(mButtonFrame.left, mButtonFrame.top); 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 (mPluggedIn) { // define the bolt shape final float bl = mFrame.left + mFrame.width() / 4f; final float bt = mFrame.top + mFrame.height() / 6f; final float br = mFrame.right - mFrame.width() / 4f; final float bb = mFrame.bottom - mFrame.height() / 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 = (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); } } else if (mPowerSaveEnabled) { // define the plus shape final float pw = mFrame.width() * 2 / 3; final float pl = mFrame.left + (mFrame.width() - pw) / 2; final float pt = mFrame.top + (mFrame.height() - pw) / 2; final float pr = mFrame.right - (mFrame.width() - pw) / 2; final float pb = mFrame.bottom - (mFrame.height() - pw) / 2; if (mPlusFrame.left != pl || mPlusFrame.top != pt || mPlusFrame.right != pr || mPlusFrame.bottom != pb) { mPlusFrame.set(pl, pt, pr, pb); mPlusPath.reset(); mPlusPath.moveTo( mPlusFrame.left + mPlusPoints[0] * mPlusFrame.width(), mPlusFrame.top + mPlusPoints[1] * mPlusFrame.height()); for (int i = 2; i < mPlusPoints.length; i += 2) { mPlusPath.lineTo( mPlusFrame.left + mPlusPoints[i] * mPlusFrame.width(), mPlusFrame.top + mPlusPoints[i + 1] * mPlusFrame.height()); } mPlusPath.lineTo( mPlusFrame.left + mPlusPoints[0] * mPlusFrame.width(), mPlusFrame.top + mPlusPoints[1] * mPlusFrame.height()); } float boltPct = (mPlusFrame.bottom - levelTop) / (mPlusFrame.bottom - mPlusFrame.top); boltPct = Math.min(Math.max(boltPct, 0), 1); if (boltPct <= BOLT_LEVEL_THRESHOLD) { // draw the bolt if opaque c.drawPath(mPlusPath, mPlusPaint); } else { // otherwise cut the bolt out of the overall shape mShapePath.op(mPlusPath, Path.Op.DIFFERENCE); } } // compute percentage text boolean pctOpaque = false; float pctX = 0, pctY = 0; String pctText = null; if (!mPluggedIn && !mPowerSaveEnabled && level > mCriticalLevel && mShowPercent) { mTextPaint.setColor(getColorForLevel(level)); mTextPaint.setTextSize(height * (SINGLE_DIGIT_PERCENT ? 0.75f : (mLevel == 100 ? 0.38f : 0.5f))); mTextHeight = -mTextPaint.getFontMetrics().ascent; pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level); pctX = mWidth * 0.5f; pctY = (mHeight + mTextHeight) * 0.47f; 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 mFrame.top = levelTop; mClipPath.reset(); mClipPath.addRect(mFrame, Path.Direction.CCW); mShapePath.op(mClipPath, Path.Op.INTERSECT); c.drawPath(mShapePath, mBatteryPaint); if (!mPluggedIn && !mPowerSaveEnabled) { 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); } } } // Some stuff required by Drawable. @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(@Nullable ColorFilter colorFilter) { } @Override public int getOpacity() { return 0; } private final class SettingObserver extends ContentObserver { public SettingObserver() { super(new Handler()); } @Override public void onChange(boolean selfChange, Uri uri) { super.onChange(selfChange, uri); updateShowPercent(); postInvalidate(); } } }