493 lines
19 KiB
Java
Executable File
493 lines
19 KiB
Java
Executable File
/*
|
|
* 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 com.android.systemui;
|
|
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.res.Resources;
|
|
import android.content.res.TypedArray;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.ColorFilter;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Path;
|
|
import android.graphics.PorterDuff;
|
|
import android.graphics.PorterDuffColorFilter;
|
|
import android.graphics.RectF;
|
|
import android.graphics.Typeface;
|
|
import android.os.BatteryManager;
|
|
import android.os.Bundle;
|
|
import android.provider.Settings;
|
|
import android.util.AttributeSet;
|
|
import android.view.View;
|
|
|
|
import com.android.systemui.statusbar.policy.BatteryController;
|
|
|
|
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 boolean ENABLE_PERCENT = true;
|
|
private static final boolean SINGLE_DIGIT_PERCENT = false;
|
|
private static final boolean SHOW_100_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;
|
|
|
|
boolean mShowPercent = true;
|
|
private float mButtonHeightFraction;
|
|
private float mSubpixelSmoothingLeft;
|
|
private float mSubpixelSmoothingRight;
|
|
private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
|
|
private float mTextHeight, mWarningTextHeight;
|
|
private int mIconTint = Color.WHITE;
|
|
|
|
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 RectF mFrame = new RectF();
|
|
private final RectF mButtonFrame = new RectF();
|
|
private final RectF mBoltFrame = 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 class BatteryTracker extends BroadcastReceiver {
|
|
public static final int UNKNOWN_LEVEL = -1;
|
|
|
|
// current battery status
|
|
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));
|
|
|
|
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));
|
|
postInvalidate();
|
|
} 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);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
BatteryTracker mTracker = new BatteryTracker();
|
|
|
|
@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);
|
|
}
|
|
mBatteryController.addStateChangedCallback(this);
|
|
}
|
|
|
|
@Override
|
|
public void onDetachedFromWindow() {
|
|
super.onDetachedFromWindow();
|
|
|
|
getContext().unregisterReceiver(mTracker);
|
|
mBatteryController.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);
|
|
final int frameColor = 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);
|
|
|
|
final 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();
|
|
atts.recycle();
|
|
mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt(
|
|
context.getContentResolver(), "status_bar_show_battery_percent", 0);
|
|
mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol);
|
|
mCriticalLevel = mContext.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);
|
|
|
|
mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
mFramePaint.setColor(frameColor);
|
|
mFramePaint.setDither(true);
|
|
mFramePaint.setStrokeWidth(0);
|
|
mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
|
|
|
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);
|
|
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);
|
|
|
|
mChargeColor = getResources().getColor(R.color.batterymeter_charge_color);
|
|
|
|
mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
|
mBoltPaint.setColor(res.getColor(R.color.batterymeter_bolt_color));
|
|
mBoltPoints = loadBoltPoints(res);
|
|
}
|
|
|
|
public void setBatteryController(BatteryController batteryController) {
|
|
mBatteryController = batteryController;
|
|
mPowerSaveEnabled = mBatteryController.isPowerSave();
|
|
}
|
|
|
|
@Override
|
|
public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
|
|
// TODO: Use this callback instead of own broadcast receiver.
|
|
}
|
|
|
|
@Override
|
|
public void onPowerSaveChanged() {
|
|
mPowerSaveEnabled = mBatteryController.isPowerSave();
|
|
invalidate();
|
|
}
|
|
|
|
private static float[] loadBoltPoints(Resources res) {
|
|
final int[] pts = res.getIntArray(R.array.batterymeter_bolt_points);
|
|
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;
|
|
}
|
|
|
|
@Override
|
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
|
mHeight = h;
|
|
mWidth = w;
|
|
mWarningTextPaint.setTextSize(h * 0.75f);
|
|
mWarningTextHeight = -mWarningTextPaint.getFontMetrics().ascent;
|
|
}
|
|
|
|
private int getColorForLevel(int percent) {
|
|
|
|
// If we are in power save mode, always use the normal color.
|
|
if (mPowerSaveEnabled) {
|
|
return mColors[mColors.length-1];
|
|
}
|
|
int thresh, color = 0;
|
|
for (int i=0; i<mColors.length; i+=2) {
|
|
thresh = mColors[i];
|
|
color = mColors[i+1];
|
|
if (percent <= thresh) {
|
|
|
|
// Respect tinting for "normal" level
|
|
if (i == mColors.length-2) {
|
|
return mIconTint;
|
|
} else {
|
|
return color;
|
|
}
|
|
}
|
|
}
|
|
return color;
|
|
}
|
|
|
|
public void setIconTint(int tint) {
|
|
mIconTint = tint;
|
|
mFramePaint.setColorFilter(new PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_ATOP));
|
|
mBoltPaint.setColor(tint);
|
|
mChargeColor = tint;
|
|
invalidate();
|
|
}
|
|
|
|
@Override
|
|
public void draw(Canvas c) {
|
|
BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
|
|
final int level = tracker.level;
|
|
|
|
if (level == BatteryTracker.UNKNOWN_LEVEL) return;
|
|
|
|
float drawFrac = (float) level / 100f;
|
|
final int pt = getPaddingTop();
|
|
final int pl = getPaddingLeft();
|
|
final int pr = getPaddingRight();
|
|
final int pb = getPaddingBottom();
|
|
final int height = mHeight - pt - pb;
|
|
final int width = mWidth - pl - pr;
|
|
|
|
final int buttonHeight = (int) (height * mButtonHeightFraction);
|
|
|
|
mFrame.set(0, 0, width, height);
|
|
mFrame.offset(pl, pt);
|
|
|
|
// 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
|
|
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 = 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 (tracker.plugged) {
|
|
// define the bolt shape
|
|
final float bl = mFrame.left + mFrame.width() / 4.5f;
|
|
final float bt = mFrame.top + mFrame.height() / 6f;
|
|
final float br = mFrame.right - mFrame.width() / 7f;
|
|
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);
|
|
}
|
|
}
|
|
|
|
// compute percentage text
|
|
boolean pctOpaque = false;
|
|
float pctX = 0, pctY = 0;
|
|
String pctText = null;
|
|
if (!tracker.plugged && level > mCriticalLevel && mShowPercent
|
|
&& !(tracker.level == 100 && !SHOW_100_PERCENT)) {
|
|
mTextPaint.setColor(getColorForLevel(level));
|
|
mTextPaint.setTextSize(height *
|
|
(SINGLE_DIGIT_PERCENT ? 0.75f
|
|
: (tracker.level == 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 (!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);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean hasOverlappingRendering() {
|
|
return false;
|
|
}
|
|
|
|
private boolean mDemoMode;
|
|
private BatteryTracker mDemoTracker = new BatteryTracker();
|
|
|
|
@Override
|
|
public void dispatchDemoCommand(String command, Bundle args) {
|
|
if (!mDemoMode && command.equals(COMMAND_ENTER)) {
|
|
mDemoMode = true;
|
|
mDemoTracker.level = mTracker.level;
|
|
mDemoTracker.plugged = mTracker.plugged;
|
|
} else if (mDemoMode && command.equals(COMMAND_EXIT)) {
|
|
mDemoMode = false;
|
|
postInvalidate();
|
|
} else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
|
|
String level = args.getString("level");
|
|
String plugged = args.getString("plugged");
|
|
if (level != null) {
|
|
mDemoTracker.level = Math.min(Math.max(Integer.parseInt(level), 0), 100);
|
|
}
|
|
if (plugged != null) {
|
|
mDemoTracker.plugged = Boolean.parseBoolean(plugged);
|
|
}
|
|
postInvalidate();
|
|
}
|
|
}
|
|
}
|