Falls back to dialogs when heads-up are N/A. Add new ongoing notification if battery saver mode is active. Offer to start battery saver on warnings, if not already started. True up BatteryMeterView's levels to the latest threshold levels. 15% for first warning. Bug:13329308 Change-Id: Id8ad11a1997079ee7165ae003a8fa1c744462ab3
430 lines
17 KiB
Java
Executable File
430 lines
17 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.Paint;
|
|
import android.graphics.Path;
|
|
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;
|
|
|
|
public class BatteryMeterView extends View implements DemoMode {
|
|
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 SUBPIXEL = 0.4f; // inset rects for softer edges
|
|
private static final float BOLT_LEVEL_THRESHOLD = 0.3f; // opaque bolt below this fraction
|
|
|
|
private final int[] mColors;
|
|
|
|
boolean mShowPercent = true;
|
|
private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
|
|
private float mTextHeight, mWarningTextHeight;
|
|
|
|
private int mHeight;
|
|
private int mWidth;
|
|
private String mWarningString;
|
|
private final int mCriticalLevel;
|
|
private final 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 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);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDetachedFromWindow() {
|
|
super.onDetachedFromWindow();
|
|
|
|
getContext().unregisterReceiver(mTracker);
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
}
|
|
|
|
@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 * 0.12f);
|
|
|
|
mFrame.set(0, 0, width, height);
|
|
mFrame.offset(pl, pt);
|
|
|
|
// button-frame: area above the battery body
|
|
mButtonFrame.set(
|
|
mFrame.left + width * 0.25f,
|
|
mFrame.top,
|
|
mFrame.right - width * 0.25f,
|
|
mFrame.top + buttonHeight);
|
|
|
|
mButtonFrame.top += SUBPIXEL;
|
|
mButtonFrame.left += SUBPIXEL;
|
|
mButtonFrame.right -= SUBPIXEL;
|
|
|
|
// frame: battery body area
|
|
mFrame.top += buttonHeight;
|
|
mFrame.left += SUBPIXEL;
|
|
mFrame.top += SUBPIXEL;
|
|
mFrame.right -= SUBPIXEL;
|
|
mFrame.bottom -= SUBPIXEL;
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|