2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.fuelgauge;
19 import com.android.settings.R;
21 import android.content.Context;
22 import android.content.res.ColorStateList;
23 import android.content.res.TypedArray;
24 import android.graphics.Canvas;
25 import android.graphics.Paint;
26 import android.graphics.Path;
27 import android.graphics.Typeface;
28 import android.os.BatteryStats;
29 import android.os.SystemClock;
30 import android.os.BatteryStats.HistoryItem;
31 import android.text.TextPaint;
32 import android.util.AttributeSet;
33 import android.util.TypedValue;
34 import android.view.View;
36 import java.util.ArrayList;
38 public class BatteryHistoryChart extends View {
39 static final int SANS = 1;
40 static final int SERIF = 2;
41 static final int MONOSPACE = 3;
43 static final int BATTERY_WARN = 29;
44 static final int BATTERY_CRITICAL = 14;
46 final Paint mBatteryBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
47 final Paint mBatteryGoodPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
48 final Paint mBatteryWarnPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
49 final Paint mBatteryCriticalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
50 final Paint mChargingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
51 final Paint mScreenOnPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
52 final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
54 final Path mBatLevelPath = new Path();
55 final Path mBatGoodPath = new Path();
56 final Path mBatWarnPath = new Path();
57 final Path mBatCriticalPath = new Path();
58 final Path mChargingPath = new Path();
59 final Path mScreenOnPath = new Path();
65 String mDurationString;
73 int mDurationStringWidth;
76 BatteryStats.HistoryItem mHistFirst;
82 public BatteryHistoryChart(Context context, AttributeSet attrs) {
83 super(context, attrs);
85 mBatteryBackgroundPaint.setARGB(255, 128, 128, 128);
86 mBatteryBackgroundPaint.setStyle(Paint.Style.FILL);
87 mBatteryGoodPaint.setARGB(128, 0, 255, 0);
88 int lineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
89 2, getResources().getDisplayMetrics());
90 if (lineWidth <= 0) lineWidth = 1;
91 mBatteryGoodPaint.setStrokeWidth(lineWidth);
92 mBatteryGoodPaint.setStyle(Paint.Style.STROKE);
93 mBatteryWarnPaint.setARGB(128, 255, 255, 0);
94 mBatteryWarnPaint.setStrokeWidth(lineWidth);
95 mBatteryWarnPaint.setStyle(Paint.Style.STROKE);
96 mBatteryCriticalPaint.setARGB(192, 255, 0, 0);
97 mBatteryCriticalPaint.setStrokeWidth(lineWidth);
98 mBatteryCriticalPaint.setStyle(Paint.Style.STROKE);
99 mChargingPaint.setARGB(255, 0, 128, 0);
100 mChargingPaint.setStrokeWidth(lineWidth);
101 mChargingPaint.setStyle(Paint.Style.STROKE);
102 mScreenOnPaint.setARGB(255, 0, 0, 255);
103 mScreenOnPaint.setStrokeWidth(lineWidth);
104 mScreenOnPaint.setStyle(Paint.Style.STROKE);
106 mScreenOnOffset = lineWidth;
107 mChargingOffset = lineWidth*2;
108 mLevelOffset = lineWidth*3;
110 mTextPaint.density = getResources().getDisplayMetrics().density;
111 mTextPaint.setCompatibilityScaling(
112 getResources().getCompatibilityInfo().applicationScale);
115 context.obtainStyledAttributes(
116 attrs, R.styleable.BatteryHistoryChart, 0, 0);
118 ColorStateList textColor = null;
120 int typefaceIndex = -1;
123 TypedArray appearance = null;
124 int ap = a.getResourceId(R.styleable.BatteryHistoryChart_android_textAppearance, -1);
126 appearance = context.obtainStyledAttributes(ap,
127 com.android.internal.R.styleable.
130 if (appearance != null) {
131 int n = appearance.getIndexCount();
132 for (int i = 0; i < n; i++) {
133 int attr = appearance.getIndex(i);
136 case com.android.internal.R.styleable.TextAppearance_textColor:
137 textColor = appearance.getColorStateList(attr);
140 case com.android.internal.R.styleable.TextAppearance_textSize:
141 textSize = appearance.getDimensionPixelSize(attr, textSize);
144 case com.android.internal.R.styleable.TextAppearance_typeface:
145 typefaceIndex = appearance.getInt(attr, -1);
148 case com.android.internal.R.styleable.TextAppearance_textStyle:
149 styleIndex = appearance.getInt(attr, -1);
154 appearance.recycle();
158 float dx=0, dy=0, r=0;
160 int n = a.getIndexCount();
161 for (int i = 0; i < n; i++) {
162 int attr = a.getIndex(i);
165 case R.styleable.BatteryHistoryChart_android_shadowColor:
166 shadowcolor = a.getInt(attr, 0);
169 case R.styleable.BatteryHistoryChart_android_shadowDx:
170 dx = a.getFloat(attr, 0);
173 case R.styleable.BatteryHistoryChart_android_shadowDy:
174 dy = a.getFloat(attr, 0);
177 case R.styleable.BatteryHistoryChart_android_shadowRadius:
178 r = a.getFloat(attr, 0);
181 case R.styleable.BatteryHistoryChart_android_textColor:
182 textColor = a.getColorStateList(attr);
185 case R.styleable.BatteryHistoryChart_android_textSize:
186 textSize = a.getDimensionPixelSize(attr, textSize);
189 case R.styleable.BatteryHistoryChart_android_typeface:
190 typefaceIndex = a.getInt(attr, typefaceIndex);
193 case R.styleable.BatteryHistoryChart_android_textStyle:
194 styleIndex = a.getInt(attr, styleIndex);
199 mTextPaint.setColor(textColor.getDefaultColor());
200 mTextPaint.setTextSize(textSize);
203 switch (typefaceIndex) {
205 tf = Typeface.SANS_SERIF;
213 tf = Typeface.MONOSPACE;
217 setTypeface(tf, styleIndex);
219 if (shadowcolor != 0) {
220 mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
224 public void setTypeface(Typeface tf, int style) {
227 tf = Typeface.defaultFromStyle(style);
229 tf = Typeface.create(tf, style);
232 mTextPaint.setTypeface(tf);
233 // now compute what (if any) algorithmic styling is needed
234 int typefaceStyle = tf != null ? tf.getStyle() : 0;
235 int need = style & ~typefaceStyle;
236 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
237 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
239 mTextPaint.setFakeBoldText(false);
240 mTextPaint.setTextSkewX(0);
241 mTextPaint.setTypeface(tf);
245 void setStats(BatteryStats stats) {
248 long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000,
249 BatteryStats.STATS_SINCE_CHARGED);
250 mStatsPeriod = uSecTime;
251 String durationString = Utils.formatElapsedTime(getContext(), mStatsPeriod / 1000);
252 mDurationString = getContext().getString(R.string.battery_stats_on_battery,
255 BatteryStats.HistoryItem rec = stats.getHistory();
258 int lastInteresting = 0;
262 while (rec != null) {
264 if (rec.cmd == HistoryItem.CMD_UPDATE) {
265 if (mHistFirst == null) {
267 mHistStart = rec.time;
269 if (rec.batteryLevel != lastLevel || pos == 1) {
270 lastLevel = rec.batteryLevel;
271 lastInteresting = pos;
277 mNumHist = lastInteresting;
279 if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
283 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
284 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
285 mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
286 mTextAscent = (int)mTextPaint.ascent();
287 mTextDescent = (int)mTextPaint.descent();
290 void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
291 int lastX, boolean lastCharging, boolean lastScreenOn, Path lastPath) {
292 if (curLevelPath != null) {
293 if (lastX >= 0 && lastX < w) {
294 if (lastPath != null) {
295 lastPath.lineTo(w, y);
297 curLevelPath.lineTo(w, y);
299 curLevelPath.lineTo(w, levelh);
300 curLevelPath.lineTo(startX, levelh);
301 curLevelPath.close();
304 if (lastCharging && lastX < w) {
305 mChargingPath.lineTo(w, h-mChargingOffset);
307 if (lastScreenOn && lastX < w) {
308 mScreenOnPath.lineTo(w, h-mScreenOnOffset);
313 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
314 super.onSizeChanged(w, h, oldw, oldh);
316 mBatLevelPath.reset();
317 mBatGoodPath.reset();
318 mBatWarnPath.reset();
319 mBatCriticalPath.reset();
320 mScreenOnPath.reset();
321 mChargingPath.reset();
323 final long timeStart = mHistStart;
324 final long timeChange = mHistEnd-mHistStart;
326 final int batLow = mBatLow;
327 final int batChange = mBatHigh-mBatLow;
329 final int levelh = h - mLevelOffset;
331 BatteryStats.HistoryItem rec = mHistFirst;
332 int x = 0, y = 0, startX = 0, lastX = -1, lastY = -1;
334 Path curLevelPath = null;
335 Path lastLinePath = null;
336 boolean lastCharging = false, lastScreenOn = false;
337 final int N = mNumHist;
338 while (rec != null && i < N) {
339 if (rec.cmd == BatteryStats.HistoryItem.CMD_UPDATE) {
340 x = (int)(((rec.time-timeStart)*w)/timeChange);
341 y = levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
344 // We have moved by at least a pixel.
346 // Don't plot changes within a pixel.
348 byte value = rec.batteryLevel;
349 if (value <= BATTERY_CRITICAL) path = mBatCriticalPath;
350 else if (value <= BATTERY_WARN) path = mBatWarnPath;
351 else path = mBatGoodPath;
353 if (path != lastLinePath) {
354 if (lastLinePath != null) {
355 lastLinePath.lineTo(x, y);
363 if (curLevelPath == null) {
364 curLevelPath = mBatLevelPath;
365 curLevelPath.moveTo(x, y);
368 curLevelPath.lineTo(x, y);
373 final boolean charging =
374 (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
375 if (charging != lastCharging) {
377 mChargingPath.moveTo(x, h-mChargingOffset);
379 mChargingPath.lineTo(x, h-mChargingOffset);
381 lastCharging = charging;
384 final boolean screenOn =
385 (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
386 if (screenOn != lastScreenOn) {
388 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
390 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
392 lastScreenOn = screenOn;
397 } else if (curLevelPath != null) {
398 finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
399 lastCharging, lastScreenOn, lastLinePath);
403 lastCharging = lastScreenOn = false;
410 finishPaths(w, h, levelh, startX, lastY, curLevelPath, lastX,
411 lastCharging, lastScreenOn, lastLinePath);
415 protected void onDraw(Canvas canvas) {
416 super.onDraw(canvas);
418 final int width = getWidth();
419 final int height = getHeight();
421 canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
422 canvas.drawText(mDurationString, (width/2) - (mDurationStringWidth/2),
423 (height/2) - ((mTextDescent-mTextAscent)/2) - mTextAscent, mTextPaint);
424 if (!mBatGoodPath.isEmpty()) {
425 canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
427 if (!mBatWarnPath.isEmpty()) {
428 canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
430 if (!mBatCriticalPath.isEmpty()) {
431 canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
433 if (!mChargingPath.isEmpty()) {
434 canvas.drawPath(mChargingPath, mChargingPaint);
436 if (!mScreenOnPath.isEmpty()) {
437 canvas.drawPath(mScreenOnPath, mScreenOnPaint);