/* * 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. * 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.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.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; 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 final int[] mColors; protected boolean mShowPercent = true; public enum BatteryMeterMode { BATTERY_METER_GONE, BATTERY_METER_ICON_PORTRAIT, BATTERY_METER_ICON_LANDSCAPE, BATTERY_METER_CIRCLE, BATTERY_METER_TEXT } private int mHeight; private int mWidth; private String mWarningString; private final int mCriticalLevel; private boolean mAnimationsEnabled; private BatteryStateRegistar mBatteryStateRegistar; private BatteryController mBatteryController; private boolean mPowerSaveEnabled; private int mDarkModeBackgroundColor; private int mDarkModeFillColor; private int mLightModeBackgroundColor; private int mLightModeFillColor; protected BatteryMeterMode mMeterMode = null; protected boolean mAttached; private boolean mDemoMode; protected BatteryTracker mDemoTracker = new BatteryTracker(); protected BatteryTracker mTracker = new BatteryTracker(); 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; // current battery status boolean present = true; int level = UNKNOWN_LEVEL; String percentStr; int plugType; boolean plugged; int health; int status; String technology; int voltage; int temperature; boolean testmode = false; @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { if (testmode && ! intent.getBooleanExtra("testmode", false)) return; level = (int)(100f * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0) / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100)); present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true); plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); plugged = plugType != 0; health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN); status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY); voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0); temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0); setContentDescription( context.getString(R.string.accessibility_battery_level, level)); if (mBatteryMeterDrawable != null) { setVisibility(View.VISIBLE); invalidate(); } } else if (action.equals(ACTION_LEVEL_TEST)) { testmode = true; post(new Runnable() { int curLevel = 0; int incr = 1; int saveLevel = level; int savePlugged = plugType; Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED); @Override public void run() { if (curLevel < 0) { testmode = false; dummy.putExtra("level", saveLevel); dummy.putExtra("plugged", savePlugged); dummy.putExtra("testmode", false); } else { dummy.putExtra("level", curLevel); dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC : 0); dummy.putExtra("testmode", true); } getContext().sendBroadcast(dummy); if (!testmode) return; curLevel += incr; if (curLevel == 100) { incr *= -1; } postDelayed(this, 200); } }); } } protected boolean shouldIndicateCharging() { if (status == BatteryManager.BATTERY_STATUS_CHARGING) { return true; } if (plugged) { return status == BatteryManager.BATTERY_STATUS_FULL; } return false; } } @Override public void onAttachedToWindow() { super.onAttachedToWindow(); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); filter.addAction(ACTION_LEVEL_TEST); final Intent sticky = getContext().registerReceiver(mTracker, filter); if (sticky != null) { // preload the battery level mTracker.onReceive(getContext(), sticky); } if (mBatteryStateRegistar != null) { mBatteryStateRegistar.addStateChangedCallback(this); } mAttached = true; } @Override public void onDetachedFromWindow() { super.onDetachedFromWindow(); mAttached = false; getContext().unregisterReceiver(mTracker); if (mBatteryStateRegistar != null) { mBatteryStateRegistar.removeStateChangedCallback(this); } } public BatteryMeterView(Context context) { this(context, null, 0); } public BatteryMeterView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); final Resources res = context.getResources(); TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView, defStyle, 0); 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 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); } } /** * 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); // 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; } // 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; } updateBoltDrawableLayer(mBatteryDrawable, mBoltDrawable); mInitialized = true; } 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; } } 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 Paint.Align getPaintAlignmentFromGravity(int gravity) { boolean isRtl = isLayoutRtl(); if ((gravity & Gravity.START) == Gravity.START) { return isRtl ? Paint.Align.RIGHT : Paint.Align.LEFT; } if ((gravity & Gravity.END) == Gravity.END) { return isRtl ? Paint.Align.LEFT : Paint.Align.RIGHT; } if ((gravity & Gravity.LEFT) == Gravity.LEFT) return Paint.Align.LEFT; if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) return Paint.Align.RIGHT; // default to center return Paint.Align.CENTER; } // 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 { 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); } 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); } } else { bolt = ((BitmapDrawable) boltDrawable).getBitmap(); } return bolt; } private class BatteryMeterDrawableException extends RuntimeException { public BatteryMeterDrawableException(String detailMessage) { super(detailMessage); } public BatteryMeterDrawableException(String detailMessage, Throwable throwable) { super(detailMessage, throwable); } } } }