blob: 19d06bef8865d181cbb01451f7370168d84ba092 [file] [log] [blame]
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -04001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.res.Resources;
24import android.content.res.TypedArray;
25import android.graphics.Canvas;
26import android.graphics.Paint;
John Spurlockfceb7ed2013-09-07 12:39:49 -040027import android.graphics.Path;
28import android.graphics.PorterDuff;
29import android.graphics.PorterDuffXfermode;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040030import android.graphics.RectF;
31import android.graphics.Typeface;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040032import android.os.BatteryManager;
John Spurlock3c875662013-08-31 15:07:25 -040033import android.os.Bundle;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040034import android.provider.Settings;
35import android.util.AttributeSet;
36import android.view.View;
37
John Spurlock3c875662013-08-31 15:07:25 -040038public class BatteryMeterView extends View implements DemoMode {
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040039 public static final String TAG = BatteryMeterView.class.getSimpleName();
40 public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
41
John Spurlock4b786ff2013-12-17 15:19:16 -050042 private static final boolean ENABLE_PERCENT = true;
43 private static final boolean SINGLE_DIGIT_PERCENT = false;
44 private static final boolean SHOW_100_PERCENT = false;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040045
John Spurlock4b786ff2013-12-17 15:19:16 -050046 private static final int FULL = 96;
47 private static final int EMPTY = 4;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040048
John Spurlock4b786ff2013-12-17 15:19:16 -050049 private static final float SUBPIXEL = 0.4f; // inset rects for softer edges
50 private static final float BOLT_LEVEL_THRESHOLD = 0.3f; // opaque bolt below this fraction
John Spurlockcfab9902013-09-25 18:07:52 -040051
John Spurlock4b786ff2013-12-17 15:19:16 -050052 private final int[] mColors;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040053
54 boolean mShowPercent = true;
John Spurlock4b786ff2013-12-17 15:19:16 -050055 private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040056 private float mTextHeight, mWarningTextHeight;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040057
58 private int mHeight;
59 private int mWidth;
60 private String mWarningString;
John Spurlocke189f662013-08-25 10:38:32 -040061 private final int mChargeColor;
John Spurlockfceb7ed2013-09-07 12:39:49 -040062 private final float[] mBoltPoints;
63 private final Path mBoltPath = new Path();
64
65 private final RectF mFrame = new RectF();
66 private final RectF mButtonFrame = new RectF();
John Spurlockd8595fd2013-11-05 11:55:04 -050067 private final RectF mBoltFrame = new RectF();
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040068
John Spurlock4b786ff2013-12-17 15:19:16 -050069 private final Path mShapePath = new Path();
70 private final Path mClipPath = new Path();
John Spurlock1fd0b932014-02-05 11:56:17 -050071 private final Path mTextPath = new Path();
John Spurlock4b786ff2013-12-17 15:19:16 -050072
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040073 private class BatteryTracker extends BroadcastReceiver {
Daniel Sandler05021e52013-10-14 10:27:45 -040074 public static final int UNKNOWN_LEVEL = -1;
75
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040076 // current battery status
Daniel Sandler05021e52013-10-14 10:27:45 -040077 int level = UNKNOWN_LEVEL;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040078 String percentStr;
79 int plugType;
80 boolean plugged;
81 int health;
82 int status;
83 String technology;
84 int voltage;
85 int temperature;
86 boolean testmode = false;
87
88 @Override
89 public void onReceive(Context context, Intent intent) {
90 final String action = intent.getAction();
91 if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
92 if (testmode && ! intent.getBooleanExtra("testmode", false)) return;
93
94 level = (int)(100f
95 * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
96 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
97
98 plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
99 plugged = plugType != 0;
100 health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,
101 BatteryManager.BATTERY_HEALTH_UNKNOWN);
102 status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
103 BatteryManager.BATTERY_STATUS_UNKNOWN);
104 technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
105 voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
106 temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
107
108 setContentDescription(
109 context.getString(R.string.accessibility_battery_level, level));
110 postInvalidate();
111 } else if (action.equals(ACTION_LEVEL_TEST)) {
112 testmode = true;
113 post(new Runnable() {
114 int curLevel = 0;
115 int incr = 1;
116 int saveLevel = level;
117 int savePlugged = plugType;
118 Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
119 @Override
120 public void run() {
121 if (curLevel < 0) {
122 testmode = false;
123 dummy.putExtra("level", saveLevel);
124 dummy.putExtra("plugged", savePlugged);
125 dummy.putExtra("testmode", false);
126 } else {
127 dummy.putExtra("level", curLevel);
128 dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC : 0);
129 dummy.putExtra("testmode", true);
130 }
131 getContext().sendBroadcast(dummy);
132
133 if (!testmode) return;
134
135 curLevel += incr;
136 if (curLevel == 100) {
137 incr *= -1;
138 }
139 postDelayed(this, 200);
140 }
141 });
142 }
143 }
144 }
145
146 BatteryTracker mTracker = new BatteryTracker();
147
148 @Override
149 public void onAttachedToWindow() {
150 super.onAttachedToWindow();
151
152 IntentFilter filter = new IntentFilter();
153 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
154 filter.addAction(ACTION_LEVEL_TEST);
Daniel Sandler05021e52013-10-14 10:27:45 -0400155 final Intent sticky = getContext().registerReceiver(mTracker, filter);
156 if (sticky != null) {
157 // preload the battery level
158 mTracker.onReceive(getContext(), sticky);
159 }
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400160 }
161
162 @Override
163 public void onDetachedFromWindow() {
164 super.onDetachedFromWindow();
165
166 getContext().unregisterReceiver(mTracker);
167 }
168
169 public BatteryMeterView(Context context) {
170 this(context, null, 0);
171 }
172
173 public BatteryMeterView(Context context, AttributeSet attrs) {
174 this(context, attrs, 0);
175 }
176
177 public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
178 super(context, attrs, defStyle);
179
180 final Resources res = context.getResources();
John Spurlock29786fc2014-02-04 17:55:47 -0500181 TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
182 defStyle, 0);
183 final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
184 res.getColor(R.color.batterymeter_frame_color));
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400185 TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels);
186 TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values);
187
188 final int N = levels.length();
189 mColors = new int[2*N];
190 for (int i=0; i<N; i++) {
191 mColors[2*i] = levels.getInt(i, 0);
192 mColors[2*i+1] = colors.getColor(i, 0);
193 }
John Spurlockfceb7ed2013-09-07 12:39:49 -0400194 levels.recycle();
195 colors.recycle();
John Spurlock29786fc2014-02-04 17:55:47 -0500196 atts.recycle();
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400197 mShowPercent = ENABLE_PERCENT && 0 != Settings.System.getInt(
198 context.getContentResolver(), "status_bar_show_battery_percent", 0);
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400199 mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol);
200
201 mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
John Spurlock29786fc2014-02-04 17:55:47 -0500202 mFramePaint.setColor(frameColor);
John Spurlockcfab9902013-09-25 18:07:52 -0400203 mFramePaint.setDither(true);
204 mFramePaint.setStrokeWidth(0);
205 mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE);
John Spurlockcfab9902013-09-25 18:07:52 -0400206
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400207 mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
John Spurlockcfab9902013-09-25 18:07:52 -0400208 mBatteryPaint.setDither(true);
209 mBatteryPaint.setStrokeWidth(0);
210 mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE);
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400211
212 mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
John Spurlock4b786ff2013-12-17 15:19:16 -0500213 Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD);
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400214 mTextPaint.setTypeface(font);
215 mTextPaint.setTextAlign(Paint.Align.CENTER);
216
217 mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
218 mWarningTextPaint.setColor(mColors[1]);
219 font = Typeface.create("sans-serif", Typeface.BOLD);
220 mWarningTextPaint.setTypeface(font);
221 mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
222
John Spurlocke189f662013-08-25 10:38:32 -0400223 mChargeColor = getResources().getColor(R.color.batterymeter_charge_color);
John Spurlockfceb7ed2013-09-07 12:39:49 -0400224
John Spurlock4b786ff2013-12-17 15:19:16 -0500225 mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
John Spurlockcfab9902013-09-25 18:07:52 -0400226 mBoltPaint.setColor(res.getColor(R.color.batterymeter_bolt_color));
John Spurlockfceb7ed2013-09-07 12:39:49 -0400227 mBoltPoints = loadBoltPoints(res);
228 }
229
230 private static float[] loadBoltPoints(Resources res) {
231 final int[] pts = res.getIntArray(R.array.batterymeter_bolt_points);
232 int maxX = 0, maxY = 0;
233 for (int i = 0; i < pts.length; i += 2) {
234 maxX = Math.max(maxX, pts[i]);
235 maxY = Math.max(maxY, pts[i + 1]);
236 }
237 final float[] ptsF = new float[pts.length];
238 for (int i = 0; i < pts.length; i += 2) {
239 ptsF[i] = (float)pts[i] / maxX;
240 ptsF[i + 1] = (float)pts[i + 1] / maxY;
241 }
242 return ptsF;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400243 }
244
245 @Override
246 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
247 mHeight = h;
248 mWidth = w;
249 mWarningTextPaint.setTextSize(h * 0.75f);
250 mWarningTextHeight = -mWarningTextPaint.getFontMetrics().ascent;
251 }
252
253 private int getColorForLevel(int percent) {
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400254 int thresh, color = 0;
255 for (int i=0; i<mColors.length; i+=2) {
256 thresh = mColors[i];
257 color = mColors[i+1];
258 if (percent <= thresh) return color;
259 }
260 return color;
261 }
262
263 @Override
264 public void draw(Canvas c) {
John Spurlock3c875662013-08-31 15:07:25 -0400265 BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
266 final int level = tracker.level;
Daniel Sandler05021e52013-10-14 10:27:45 -0400267
268 if (level == BatteryTracker.UNKNOWN_LEVEL) return;
269
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400270 float drawFrac = (float) level / 100f;
271 final int pt = getPaddingTop();
272 final int pl = getPaddingLeft();
273 final int pr = getPaddingRight();
274 final int pb = getPaddingBottom();
John Spurlock4b786ff2013-12-17 15:19:16 -0500275 final int height = mHeight - pt - pb;
276 final int width = mWidth - pl - pr;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400277
John Spurlock4b786ff2013-12-17 15:19:16 -0500278 final int buttonHeight = (int) (height * 0.12f);
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400279
John Spurlockfceb7ed2013-09-07 12:39:49 -0400280 mFrame.set(0, 0, width, height);
281 mFrame.offset(pl, pt);
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400282
John Spurlock4b786ff2013-12-17 15:19:16 -0500283 // button-frame: area above the battery body
John Spurlockfceb7ed2013-09-07 12:39:49 -0400284 mButtonFrame.set(
285 mFrame.left + width * 0.25f,
286 mFrame.top,
287 mFrame.right - width * 0.25f,
John Spurlock4b786ff2013-12-17 15:19:16 -0500288 mFrame.top + buttonHeight);
John Spurlockcfab9902013-09-25 18:07:52 -0400289
290 mButtonFrame.top += SUBPIXEL;
291 mButtonFrame.left += SUBPIXEL;
292 mButtonFrame.right -= SUBPIXEL;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400293
John Spurlock4b786ff2013-12-17 15:19:16 -0500294 // frame: battery body area
295 mFrame.top += buttonHeight;
John Spurlockcfab9902013-09-25 18:07:52 -0400296 mFrame.left += SUBPIXEL;
297 mFrame.top += SUBPIXEL;
298 mFrame.right -= SUBPIXEL;
299 mFrame.bottom -= SUBPIXEL;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400300
John Spurlock4b786ff2013-12-17 15:19:16 -0500301 // set the battery charging color
302 mBatteryPaint.setColor(tracker.plugged ? mChargeColor : getColorForLevel(level));
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400303
304 if (level >= FULL) {
305 drawFrac = 1f;
306 } else if (level <= EMPTY) {
307 drawFrac = 0f;
308 }
309
John Spurlock4b786ff2013-12-17 15:19:16 -0500310 final float levelTop = drawFrac == 1f ? mButtonFrame.top
311 : (mFrame.top + (mFrame.height() * (1f - drawFrac)));
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400312
John Spurlock4b786ff2013-12-17 15:19:16 -0500313 // define the battery shape
314 mShapePath.reset();
315 mShapePath.moveTo(mButtonFrame.left, mButtonFrame.top);
316 mShapePath.lineTo(mButtonFrame.right, mButtonFrame.top);
317 mShapePath.lineTo(mButtonFrame.right, mFrame.top);
318 mShapePath.lineTo(mFrame.right, mFrame.top);
319 mShapePath.lineTo(mFrame.right, mFrame.bottom);
320 mShapePath.lineTo(mFrame.left, mFrame.bottom);
321 mShapePath.lineTo(mFrame.left, mFrame.top);
322 mShapePath.lineTo(mButtonFrame.left, mFrame.top);
323 mShapePath.lineTo(mButtonFrame.left, mButtonFrame.top);
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400324
John Spurlockd19877f2013-09-17 11:03:03 -0400325 if (tracker.plugged) {
John Spurlock4b786ff2013-12-17 15:19:16 -0500326 // define the bolt shape
John Spurlockd8595fd2013-11-05 11:55:04 -0500327 final float bl = mFrame.left + mFrame.width() / 4.5f;
328 final float bt = mFrame.top + mFrame.height() / 6f;
329 final float br = mFrame.right - mFrame.width() / 7f;
330 final float bb = mFrame.bottom - mFrame.height() / 10f;
John Spurlockfceb7ed2013-09-07 12:39:49 -0400331 if (mBoltFrame.left != bl || mBoltFrame.top != bt
332 || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
333 mBoltFrame.set(bl, bt, br, bb);
334 mBoltPath.reset();
335 mBoltPath.moveTo(
336 mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
337 mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
338 for (int i = 2; i < mBoltPoints.length; i += 2) {
339 mBoltPath.lineTo(
340 mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(),
341 mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height());
342 }
343 mBoltPath.lineTo(
344 mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
345 mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
346 }
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400347
John Spurlock4b786ff2013-12-17 15:19:16 -0500348 float boltPct = (mBoltFrame.bottom - levelTop) / (mBoltFrame.bottom - mBoltFrame.top);
349 boltPct = Math.min(Math.max(boltPct, 0), 1);
350 if (boltPct <= BOLT_LEVEL_THRESHOLD) {
351 // draw the bolt if opaque
352 c.drawPath(mBoltPath, mBoltPaint);
353 } else {
354 // otherwise cut the bolt out of the overall shape
355 mShapePath.op(mBoltPath, Path.Op.DIFFERENCE);
356 }
357 }
358
John Spurlock1fd0b932014-02-05 11:56:17 -0500359 // compute percentage text
360 boolean pctOpaque = false;
361 float pctX = 0, pctY = 0;
362 String pctText = null;
363 if (!tracker.plugged && level > EMPTY && mShowPercent
364 && !(tracker.level == 100 && !SHOW_100_PERCENT)) {
365 mTextPaint.setColor(getColorForLevel(level));
366 mTextPaint.setTextSize(height *
367 (SINGLE_DIGIT_PERCENT ? 0.75f
368 : (tracker.level == 100 ? 0.38f : 0.5f)));
369 mTextHeight = -mTextPaint.getFontMetrics().ascent;
370 pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level);
371 pctX = mWidth * 0.5f;
372 pctY = (mHeight + mTextHeight) * 0.47f;
373 pctOpaque = levelTop > pctY;
374 if (!pctOpaque) {
375 mTextPath.reset();
376 mTextPaint.getTextPath(pctText, 0, pctText.length(), pctX, pctY, mTextPath);
377 // cut the percentage text out of the overall shape
378 mShapePath.op(mTextPath, Path.Op.DIFFERENCE);
379 }
380 }
381
John Spurlock4b786ff2013-12-17 15:19:16 -0500382 // draw the battery shape background
383 c.drawPath(mShapePath, mFramePaint);
384
385 // draw the battery shape, clipped to charging level
386 mFrame.top = levelTop;
387 mClipPath.reset();
388 mClipPath.addRect(mFrame, Path.Direction.CCW);
389 mShapePath.op(mClipPath, Path.Op.INTERSECT);
390 c.drawPath(mShapePath, mBatteryPaint);
391
392 if (!tracker.plugged) {
393 if (level <= EMPTY) {
394 // draw the warning text
395 final float x = mWidth * 0.5f;
396 final float y = (mHeight + mWarningTextHeight) * 0.48f;
397 c.drawText(mWarningString, x, y, mWarningTextPaint);
John Spurlock1fd0b932014-02-05 11:56:17 -0500398 } else if (pctOpaque) {
John Spurlock4b786ff2013-12-17 15:19:16 -0500399 // draw the percentage text
John Spurlock1fd0b932014-02-05 11:56:17 -0500400 c.drawText(pctText, pctX, pctY, mTextPaint);
John Spurlock4b786ff2013-12-17 15:19:16 -0500401 }
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400402 }
403 }
John Spurlock3c875662013-08-31 15:07:25 -0400404
405 private boolean mDemoMode;
406 private BatteryTracker mDemoTracker = new BatteryTracker();
407
408 @Override
409 public void dispatchDemoCommand(String command, Bundle args) {
410 if (!mDemoMode && command.equals(COMMAND_ENTER)) {
411 mDemoMode = true;
412 mDemoTracker.level = mTracker.level;
413 mDemoTracker.plugged = mTracker.plugged;
414 } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
415 mDemoMode = false;
416 postInvalidate();
417 } else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
418 String level = args.getString("level");
419 String plugged = args.getString("plugged");
420 if (level != null) {
421 mDemoTracker.level = Math.min(Math.max(Integer.parseInt(level), 0), 100);
422 }
423 if (plugged != null) {
424 mDemoTracker.plugged = Boolean.parseBoolean(plugged);
425 }
426 postInvalidate();
427 }
428 }
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400429}