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 android.content.Context;
20 import android.content.Intent;
21 import android.content.res.ColorStateList;
22 import android.content.res.TypedArray;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.DashPathEffect;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.Typeface;
29 import android.os.BatteryStats;
30 import android.os.BatteryStats.HistoryItem;
31 import android.os.SystemClock;
32 import android.telephony.ServiceState;
33 import android.text.TextPaint;
34 import android.text.format.DateFormat;
35 import android.text.format.Formatter;
36 import android.util.AttributeSet;
37 import android.util.Log;
38 import android.util.TimeUtils;
39 import android.util.TypedValue;
40 import android.view.View;
41 import com.android.settings.R;
42 import com.android.settings.Utils;
43 import com.android.settingslib.BatteryInfo;
44 import libcore.icu.LocaleData;
46 import java.util.ArrayList;
47 import java.util.Calendar;
48 import java.util.Locale;
50 public class BatteryHistoryChart extends View {
51 static final boolean DEBUG = false;
52 static final String TAG = "BatteryHistoryChart";
54 static final int CHART_DATA_X_MASK = 0x0000ffff;
55 static final int CHART_DATA_BIN_MASK = 0xffff0000;
56 static final int CHART_DATA_BIN_SHIFT = 16;
58 static class ChartData {
66 void setColors(int[] colors) {
68 mPaints = new Paint[colors.length];
69 for (int i=0; i<colors.length; i++) {
70 mPaints[i] = new Paint();
71 mPaints[i].setColor(colors[i]);
72 mPaints[i].setStyle(Paint.Style.FILL);
76 void init(int width) {
78 mTicks = new int[width*2];
86 void addTick(int x, int bin) {
87 if (bin != mLastBin && mNumTicks < mTicks.length) {
88 mTicks[mNumTicks] = (x&CHART_DATA_X_MASK) | (bin<<CHART_DATA_BIN_SHIFT);
94 void finish(int width) {
100 void draw(Canvas canvas, int top, int height) {
101 int lastBin=0, lastX=0;
102 int bottom = top + height;
103 for (int i=0; i<mNumTicks; i++) {
104 int tick = mTicks[i];
105 int x = tick&CHART_DATA_X_MASK;
106 int bin = (tick&CHART_DATA_BIN_MASK) >> CHART_DATA_BIN_SHIFT;
108 canvas.drawRect(lastX, top, x, bottom, mPaints[lastBin]);
117 static final int SANS = 1;
118 static final int SERIF = 2;
119 static final int MONOSPACE = 3;
121 // First value if for phone off; first value is "scanning"; following values
122 // are battery stats signal strength buckets.
123 static final int NUM_PHONE_SIGNALS = 7;
125 final Paint mBatteryBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
126 final Paint mBatteryGoodPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
127 final Paint mBatteryWarnPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
128 final Paint mBatteryCriticalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
129 final Paint mTimeRemainPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
130 final Paint mChargingPaint = new Paint();
131 final Paint mScreenOnPaint = new Paint();
132 final Paint mGpsOnPaint = new Paint();
133 final Paint mFlashlightOnPaint = new Paint();
134 final Paint mCameraOnPaint = new Paint();
135 final Paint mWifiRunningPaint = new Paint();
136 final Paint mCpuRunningPaint = new Paint();
137 final Paint mDateLinePaint = new Paint();
138 final ChartData mPhoneSignalChart = new ChartData();
139 final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
140 final TextPaint mHeaderTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
141 final Paint mDebugRectPaint = new Paint();
143 final Path mBatLevelPath = new Path();
144 final Path mBatGoodPath = new Path();
145 final Path mBatWarnPath = new Path();
146 final Path mBatCriticalPath = new Path();
147 final Path mTimeRemainPath = new Path();
148 final Path mChargingPath = new Path();
149 final Path mScreenOnPath = new Path();
150 final Path mGpsOnPath = new Path();
151 final Path mFlashlightOnPath = new Path();
152 final Path mCameraOnPath = new Path();
153 final Path mWifiRunningPath = new Path();
154 final Path mCpuRunningPath = new Path();
155 final Path mDateLinePath = new Path();
158 Intent mBatteryBroadcast;
160 String mMaxPercentLabelString;
161 String mMinPercentLabelString;
162 String mDurationString;
163 String mChargeDurationString;
165 String mChargingLabel;
166 String mScreenOnLabel;
168 String mCameraOnLabel;
169 String mFlashlightOnLabel;
170 String mWifiRunningLabel;
171 String mCpuRunningLabel;
172 String mPhoneSignalLabel;
179 int mBatteryWarnLevel;
180 int mBatteryCriticalLevel;
184 int mHeaderTextAscent;
185 int mHeaderTextDescent;
186 int mMaxPercentLabelStringWidth;
187 int mMinPercentLabelStringWidth;
188 int mDurationStringWidth;
189 int mChargeLabelStringWidth;
190 int mChargeDurationStringWidth;
191 int mDrainStringWidth;
196 int mLastHeight = -1;
203 int mFlashlightOnOffset;
205 int mWifiRunningOffset;
206 int mCpuRunningOffset;
207 int mPhoneSignalOffset;
219 long mEndDataWallTime;
225 boolean mHavePhoneSignal;
227 boolean mHaveFlashlight;
229 final ArrayList<TimeLabel> mTimeLabels = new ArrayList<TimeLabel>();
230 final ArrayList<DateLabel> mDateLabels = new ArrayList<DateLabel>();
235 static class TextAttrs {
236 ColorStateList textColor = null;
238 int typefaceIndex = -1;
241 void retrieve(Context context, TypedArray from, int index) {
242 TypedArray appearance = null;
243 int ap = from.getResourceId(index, -1);
245 appearance = context.obtainStyledAttributes(ap,
246 com.android.internal.R.styleable.TextAppearance);
248 if (appearance != null) {
249 int n = appearance.getIndexCount();
250 for (int i = 0; i < n; i++) {
251 int attr = appearance.getIndex(i);
254 case com.android.internal.R.styleable.TextAppearance_textColor:
255 textColor = appearance.getColorStateList(attr);
258 case com.android.internal.R.styleable.TextAppearance_textSize:
259 textSize = appearance.getDimensionPixelSize(attr, textSize);
262 case com.android.internal.R.styleable.TextAppearance_typeface:
263 typefaceIndex = appearance.getInt(attr, -1);
266 case com.android.internal.R.styleable.TextAppearance_textStyle:
267 styleIndex = appearance.getInt(attr, -1);
272 appearance.recycle();
276 void apply(Context context, TextPaint paint) {
277 paint.density = context.getResources().getDisplayMetrics().density;
278 paint.setCompatibilityScaling(
279 context.getResources().getCompatibilityInfo().applicationScale);
281 paint.setColor(textColor.getDefaultColor());
282 paint.setTextSize(textSize);
285 switch (typefaceIndex) {
287 tf = Typeface.SANS_SERIF;
295 tf = Typeface.MONOSPACE;
299 setTypeface(paint, tf, styleIndex);
302 public void setTypeface(TextPaint paint, Typeface tf, int style) {
305 tf = Typeface.defaultFromStyle(style);
307 tf = Typeface.create(tf, style);
310 paint.setTypeface(tf);
311 // now compute what (if any) algorithmic styling is needed
312 int typefaceStyle = tf != null ? tf.getStyle() : 0;
313 int need = style & ~typefaceStyle;
314 paint.setFakeBoldText((need & Typeface.BOLD) != 0);
315 paint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
317 paint.setFakeBoldText(false);
318 paint.setTextSkewX(0);
319 paint.setTypeface(tf);
324 static class TimeLabel {
329 TimeLabel(TextPaint paint, int x, Calendar cal, boolean use24hr) {
331 final String bestFormat = DateFormat.getBestDateTimePattern(
332 Locale.getDefault(), use24hr ? "km" : "ha");
333 label = DateFormat.format(bestFormat, cal).toString();
334 width = (int)paint.measureText(label);
338 static class DateLabel {
343 DateLabel(TextPaint paint, int x, Calendar cal, boolean dayFirst) {
345 final String bestFormat = DateFormat.getBestDateTimePattern(
346 Locale.getDefault(), dayFirst ? "dM" : "Md");
347 label = DateFormat.format(bestFormat, cal).toString();
348 width = (int)paint.measureText(label);
352 public BatteryHistoryChart(Context context, AttributeSet attrs) {
353 super(context, attrs);
355 if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!");
357 mBatteryWarnLevel = mContext.getResources().getInteger(
358 com.android.internal.R.integer.config_lowBatteryWarningLevel);
359 mBatteryCriticalLevel = mContext.getResources().getInteger(
360 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
362 mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
363 2, getResources().getDisplayMetrics());
365 mBatteryBackgroundPaint.setColor(0xFF009688);
366 mBatteryBackgroundPaint.setStyle(Paint.Style.FILL);
367 mBatteryGoodPaint.setARGB(128, 0, 128, 0);
368 mBatteryGoodPaint.setStyle(Paint.Style.STROKE);
369 mBatteryWarnPaint.setARGB(128, 128, 128, 0);
370 mBatteryWarnPaint.setStyle(Paint.Style.STROKE);
371 mBatteryCriticalPaint.setARGB(192, 128, 0, 0);
372 mBatteryCriticalPaint.setStyle(Paint.Style.STROKE);
373 mTimeRemainPaint.setColor(0xFFCED7BB);
374 mTimeRemainPaint.setStyle(Paint.Style.FILL);
375 mChargingPaint.setStyle(Paint.Style.STROKE);
376 mScreenOnPaint.setStyle(Paint.Style.STROKE);
377 mGpsOnPaint.setStyle(Paint.Style.STROKE);
378 mCameraOnPaint.setStyle(Paint.Style.STROKE);
379 mFlashlightOnPaint.setStyle(Paint.Style.STROKE);
380 mWifiRunningPaint.setStyle(Paint.Style.STROKE);
381 mCpuRunningPaint.setStyle(Paint.Style.STROKE);
382 mPhoneSignalChart.setColors(com.android.settings.Utils.BADNESS_COLORS);
383 mDebugRectPaint.setARGB(255, 255, 0, 0);
384 mDebugRectPaint.setStyle(Paint.Style.STROKE);
385 mScreenOnPaint.setColor(0xFF009688);
386 mGpsOnPaint.setColor(0xFF009688);
387 mCameraOnPaint.setColor(0xFF009688);
388 mFlashlightOnPaint.setColor(0xFF009688);
389 mWifiRunningPaint.setColor(0xFF009688);
390 mCpuRunningPaint.setColor(0xFF009688);
391 mChargingPaint.setColor(0xFF009688);
394 context.obtainStyledAttributes(
395 attrs, R.styleable.BatteryHistoryChart, 0, 0);
397 final TextAttrs mainTextAttrs = new TextAttrs();
398 final TextAttrs headTextAttrs = new TextAttrs();
399 mainTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_android_textAppearance);
400 headTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_headerAppearance);
403 float dx=0, dy=0, r=0;
405 int n = a.getIndexCount();
406 for (int i = 0; i < n; i++) {
407 int attr = a.getIndex(i);
410 case R.styleable.BatteryHistoryChart_android_shadowColor:
411 shadowcolor = a.getInt(attr, 0);
414 case R.styleable.BatteryHistoryChart_android_shadowDx:
415 dx = a.getFloat(attr, 0);
418 case R.styleable.BatteryHistoryChart_android_shadowDy:
419 dy = a.getFloat(attr, 0);
422 case R.styleable.BatteryHistoryChart_android_shadowRadius:
423 r = a.getFloat(attr, 0);
426 case R.styleable.BatteryHistoryChart_android_textColor:
427 mainTextAttrs.textColor = a.getColorStateList(attr);
428 headTextAttrs.textColor = a.getColorStateList(attr);
431 case R.styleable.BatteryHistoryChart_android_textSize:
432 mainTextAttrs.textSize = a.getDimensionPixelSize(attr, mainTextAttrs.textSize);
433 headTextAttrs.textSize = a.getDimensionPixelSize(attr, headTextAttrs.textSize);
436 case R.styleable.BatteryHistoryChart_android_typeface:
437 mainTextAttrs.typefaceIndex = a.getInt(attr, mainTextAttrs.typefaceIndex);
438 headTextAttrs.typefaceIndex = a.getInt(attr, headTextAttrs.typefaceIndex);
441 case R.styleable.BatteryHistoryChart_android_textStyle:
442 mainTextAttrs.styleIndex = a.getInt(attr, mainTextAttrs.styleIndex);
443 headTextAttrs.styleIndex = a.getInt(attr, headTextAttrs.styleIndex);
446 case R.styleable.BatteryHistoryChart_barPrimaryColor:
447 mBatteryBackgroundPaint.setColor(a.getInt(attr, 0));
448 mScreenOnPaint.setColor(a.getInt(attr, 0));
449 mGpsOnPaint.setColor(a.getInt(attr, 0));
450 mCameraOnPaint.setColor(a.getInt(attr, 0));
451 mFlashlightOnPaint.setColor(a.getInt(attr, 0));
452 mWifiRunningPaint.setColor(a.getInt(attr, 0));
453 mCpuRunningPaint.setColor(a.getInt(attr, 0));
454 mChargingPaint.setColor(a.getInt(attr, 0));
457 case R.styleable.BatteryHistoryChart_barPredictionColor:
458 mTimeRemainPaint.setColor(a.getInt(attr, 0));
461 case R.styleable.BatteryHistoryChart_chartMinHeight:
462 mChartMinHeight = a.getDimensionPixelSize(attr, 0);
469 mainTextAttrs.apply(context, mTextPaint);
470 headTextAttrs.apply(context, mHeaderTextPaint);
472 mDateLinePaint.set(mTextPaint);
473 mDateLinePaint.setStyle(Paint.Style.STROKE);
474 int hairlineWidth = mThinLineWidth/2;
475 if (hairlineWidth < 1) {
478 mDateLinePaint.setStrokeWidth(hairlineWidth);
479 mDateLinePaint.setPathEffect(new DashPathEffect(new float[] {
480 mThinLineWidth * 2, mThinLineWidth * 2 }, 0));
482 if (shadowcolor != 0) {
483 mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
484 mHeaderTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
488 void setStats(BatteryStats stats, Intent broadcast) {
490 mBatteryBroadcast = broadcast;
492 if (DEBUG) Log.d(TAG, "Setting stats...");
494 final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
496 long uSecTime = mStats.computeBatteryRealtime(elapsedRealtimeUs,
497 BatteryStats.STATS_SINCE_CHARGED);
498 mStatsPeriod = uSecTime;
499 mChargingLabel = getContext().getString(R.string.battery_stats_charging_label);
500 mScreenOnLabel = getContext().getString(R.string.battery_stats_screen_on_label);
501 mGpsOnLabel = getContext().getString(R.string.battery_stats_gps_on_label);
502 mCameraOnLabel = getContext().getString(R.string.battery_stats_camera_on_label);
503 mFlashlightOnLabel = getContext().getString(R.string.battery_stats_flashlight_on_label);
504 mWifiRunningLabel = getContext().getString(R.string.battery_stats_wifi_running_label);
505 mCpuRunningLabel = getContext().getString(R.string.battery_stats_wake_lock_label);
506 mPhoneSignalLabel = getContext().getString(R.string.battery_stats_phone_signal_label);
508 mMaxPercentLabelString = Utils.formatPercentage(100);
509 mMinPercentLabelString = Utils.formatPercentage(0);
510 mInfo = BatteryInfo.getBatteryInfo(getContext(), mBatteryBroadcast, mStats,
513 mChargeDurationString = "";
514 setContentDescription(mInfo.mChargeLabelString);
517 int lastInteresting = 0;
522 mEndDataWallTime = 0;
526 long lastWallTime = 0;
527 long lastRealtime = 0;
530 boolean first = true;
531 if (stats.startIteratingHistoryLocked()) {
532 final HistoryItem rec = new HistoryItem();
533 while (stats.getNextHistoryLocked(rec)) {
537 mHistStart = rec.time;
539 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
540 || rec.cmd == HistoryItem.CMD_RESET) {
541 // If there is a ridiculously large jump in time, then we won't be
542 // able to create a good chart with that data, so just ignore the
543 // times we got before and pretend like our data extends back from
544 // the time we have now.
545 // Also, if we are getting a time change and we are less than 5 minutes
546 // since the start of the history real time, then also use this new
547 // time to compute the base time, since whatever time we had before is
548 // pretty much just noise.
549 if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L))
550 || rec.time < (mHistStart+(5*60*1000L))) {
553 lastWallTime = rec.currentTime;
554 lastRealtime = rec.time;
555 if (mStartWallTime == 0) {
556 mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
559 if (rec.isDeltaData()) {
560 if (rec.batteryLevel != lastLevel || pos == 1) {
561 lastLevel = rec.batteryLevel;
563 lastInteresting = pos;
564 mHistDataEnd = rec.time;
565 aggrStates |= rec.states;
566 aggrStates2 |= rec.states2;
570 mHistEnd = mHistDataEnd + (mInfo.remainingTimeUs/1000);
571 mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
572 mEndWallTime = mEndDataWallTime + (mInfo.remainingTimeUs/1000);
573 mNumHist = lastInteresting;
574 mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
575 mHaveFlashlight = (aggrStates2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
576 mHaveCamera = (aggrStates2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
577 mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
578 || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
579 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
580 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
581 if (!com.android.settings.Utils.isWifiOnly(getContext())) {
582 mHavePhoneSignal = true;
584 if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
588 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
589 mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString);
590 mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString);
591 mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString);
592 mChargeLabelStringWidth = (int)mHeaderTextPaint.measureText(mInfo.mChargeLabelString);
593 mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString);
594 mTextAscent = (int)mTextPaint.ascent();
595 mTextDescent = (int)mTextPaint.descent();
596 mHeaderTextAscent = (int)mHeaderTextPaint.ascent();
597 mHeaderTextDescent = (int)mHeaderTextPaint.descent();
598 int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent;
599 mHeaderHeight = headerTextHeight*2 - mTextAscent;
600 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
601 getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec));
604 void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
605 int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
606 boolean lastFlashlightOn, boolean lastCameraOn, boolean lastWifiRunning,
607 boolean lastCpuRunning, Path lastPath) {
608 if (curLevelPath != null) {
609 if (lastX >= 0 && lastX < w) {
610 if (lastPath != null) {
611 lastPath.lineTo(w, y);
613 curLevelPath.lineTo(w, y);
615 curLevelPath.lineTo(w, mLevelTop+levelh);
616 curLevelPath.lineTo(startX, mLevelTop+levelh);
617 curLevelPath.close();
621 mChargingPath.lineTo(w, h-mChargingOffset);
624 mScreenOnPath.lineTo(w, h-mScreenOnOffset);
627 mGpsOnPath.lineTo(w, h-mGpsOnOffset);
629 if (lastFlashlightOn) {
630 mFlashlightOnPath.lineTo(w, h-mFlashlightOnOffset);
633 mCameraOnPath.lineTo(w, h-mCameraOnOffset);
635 if (lastWifiRunning) {
636 mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
638 if (lastCpuRunning) {
639 mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
641 if (mHavePhoneSignal) {
642 mPhoneSignalChart.finish(w);
646 private boolean is24Hour() {
647 return DateFormat.is24HourFormat(getContext());
650 private boolean isDayFirst() {
651 final String value = LocaleData.get(getResources().getConfiguration().locale)
652 .getDateFormat(java.text.DateFormat.SHORT);
653 return value.indexOf('M') > value.indexOf('d');
657 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
658 super.onSizeChanged(w, h, oldw, oldh);
660 if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
662 if (mLastWidth == w && mLastHeight == h) {
666 if (mLastWidth == 0 || mLastHeight == 0) {
670 if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
677 int textHeight = mTextDescent - mTextAscent;
678 if (h > ((textHeight*10)+mChartMinHeight)) {
680 if (h > (textHeight*15)) {
681 // Plenty of room for the chart.
682 mLineWidth = textHeight/2;
684 // Compress lines to make more room for chart.
685 mLineWidth = textHeight/3;
689 mLineWidth = mThinLineWidth;
691 if (mLineWidth <= 0) mLineWidth = 1;
693 mLevelTop = mHeaderHeight;
694 mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
696 int levelWidth = mLevelRight-mLevelLeft;
698 mTextPaint.setStrokeWidth(mThinLineWidth);
699 mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
700 mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
701 mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth);
702 mChargingPaint.setStrokeWidth(mLineWidth);
703 mScreenOnPaint.setStrokeWidth(mLineWidth);
704 mGpsOnPaint.setStrokeWidth(mLineWidth);
705 mCameraOnPaint.setStrokeWidth(mLineWidth);
706 mFlashlightOnPaint.setStrokeWidth(mLineWidth);
707 mWifiRunningPaint.setStrokeWidth(mLineWidth);
708 mCpuRunningPaint.setStrokeWidth(mLineWidth);
709 mDebugRectPaint.setStrokeWidth(1);
711 int fullBarOffset = textHeight + mLineWidth;
714 mChargingOffset = mLineWidth;
715 mScreenOnOffset = mChargingOffset + fullBarOffset;
716 mCpuRunningOffset = mScreenOnOffset + fullBarOffset;
717 mWifiRunningOffset = mCpuRunningOffset + fullBarOffset;
718 mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0);
719 mFlashlightOnOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0);
720 mCameraOnOffset = mFlashlightOnOffset + (mHaveFlashlight ? fullBarOffset : 0);
721 mPhoneSignalOffset = mCameraOnOffset + (mHaveCamera ? fullBarOffset : 0);
722 mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0)
723 + mLineWidth*2 + mLineWidth/2;
724 if (mHavePhoneSignal) {
725 mPhoneSignalChart.init(w);
728 mScreenOnOffset = mGpsOnOffset = mCameraOnOffset = mFlashlightOnOffset =
729 mWifiRunningOffset = mCpuRunningOffset = mChargingOffset =
730 mPhoneSignalOffset = 0;
731 mLevelOffset = fullBarOffset + mThinLineWidth*4;
732 if (mHavePhoneSignal) {
733 mPhoneSignalChart.init(0);
737 mBatLevelPath.reset();
738 mBatGoodPath.reset();
739 mBatWarnPath.reset();
740 mTimeRemainPath.reset();
741 mBatCriticalPath.reset();
742 mScreenOnPath.reset();
744 mFlashlightOnPath.reset();
745 mCameraOnPath.reset();
746 mWifiRunningPath.reset();
747 mCpuRunningPath.reset();
748 mChargingPath.reset();
753 final long walltimeStart = mStartWallTime;
754 final long walltimeChange = mEndWallTime > walltimeStart
755 ? (mEndWallTime-walltimeStart) : 1;
756 long curWalltime = mStartWallTime;
757 long lastRealtime = 0;
759 final int batLow = mBatLow;
760 final int batChange = mBatHigh-mBatLow;
762 final int levelh = h - mLevelOffset - mLevelTop;
763 mLevelBottom = mLevelTop + levelh;
765 int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
767 Path curLevelPath = null;
768 Path lastLinePath = null;
769 boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
770 boolean lastFlashlightOn = false, lastCameraOn = false;
771 boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false;
772 int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
773 final int N = mNumHist;
774 if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) {
775 final HistoryItem rec = new HistoryItem();
776 while (mStats.getNextHistoryLocked(rec) && i < N) {
777 if (rec.isDeltaData()) {
778 curWalltime += rec.time-lastRealtime;
779 lastRealtime = rec.time;
780 x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange);
785 StringBuilder sb = new StringBuilder(128);
786 sb.append("walloff=");
787 TimeUtils.formatDuration(curWalltime - walltimeStart, sb);
788 sb.append(" wallchange=");
789 TimeUtils.formatDuration(walltimeChange, sb);
792 Log.d("foo", sb.toString());
794 y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
797 // We have moved by at least a pixel.
799 // Don't plot changes within a pixel.
801 byte value = rec.batteryLevel;
802 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
803 else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
804 else path = null; //mBatGoodPath;
806 if (path != lastLinePath) {
807 if (lastLinePath != null) {
808 lastLinePath.lineTo(x, y);
814 } else if (path != null) {
818 if (curLevelPath == null) {
819 curLevelPath = mBatLevelPath;
820 curLevelPath.moveTo(x, y);
823 curLevelPath.lineTo(x, y);
831 final boolean charging =
832 (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
833 if (charging != lastCharging) {
835 mChargingPath.moveTo(x, h-mChargingOffset);
837 mChargingPath.lineTo(x, h-mChargingOffset);
839 lastCharging = charging;
842 final boolean screenOn =
843 (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
844 if (screenOn != lastScreenOn) {
846 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
848 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
850 lastScreenOn = screenOn;
853 final boolean gpsOn =
854 (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
855 if (gpsOn != lastGpsOn) {
857 mGpsOnPath.moveTo(x, h-mGpsOnOffset);
859 mGpsOnPath.lineTo(x, h-mGpsOnOffset);
864 final boolean flashlightOn =
865 (rec.states2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
866 if (flashlightOn != lastFlashlightOn) {
868 mFlashlightOnPath.moveTo(x, h-mFlashlightOnOffset);
870 mFlashlightOnPath.lineTo(x, h-mFlashlightOnOffset);
872 lastFlashlightOn = flashlightOn;
875 final boolean cameraOn =
876 (rec.states2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
877 if (cameraOn != lastCameraOn) {
879 mCameraOnPath.moveTo(x, h-mCameraOnOffset);
881 mCameraOnPath.lineTo(x, h-mCameraOnOffset);
883 lastCameraOn = cameraOn;
886 final int wifiSupplState =
887 ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
888 >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
890 if (lastWifiSupplState != wifiSupplState) {
891 lastWifiSupplState = wifiSupplState;
892 switch (wifiSupplState) {
893 case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED:
894 case BatteryStats.WIFI_SUPPL_STATE_DORMANT:
895 case BatteryStats.WIFI_SUPPL_STATE_INACTIVE:
896 case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
897 case BatteryStats.WIFI_SUPPL_STATE_INVALID:
898 case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED:
899 wifiRunning = lastWifiSupplRunning = false;
902 wifiRunning = lastWifiSupplRunning = true;
906 wifiRunning = lastWifiSupplRunning;
908 if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
909 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
910 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
913 if (wifiRunning != lastWifiRunning) {
915 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
917 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
919 lastWifiRunning = wifiRunning;
922 final boolean cpuRunning =
923 (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
924 if (cpuRunning != lastCpuRunning) {
926 mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
928 mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
930 lastCpuRunning = cpuRunning;
933 if (mLargeMode && mHavePhoneSignal) {
935 if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
936 >> HistoryItem.STATE_PHONE_STATE_SHIFT)
937 == ServiceState.STATE_POWER_OFF) {
939 } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
942 bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
943 >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
946 mPhoneSignalChart.addTick(x, bin);
951 long lastWalltime = curWalltime;
952 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
953 || rec.cmd == HistoryItem.CMD_RESET) {
954 if (rec.currentTime >= mStartWallTime) {
955 curWalltime = rec.currentTime;
957 curWalltime = mStartWallTime + (rec.time-mHistStart);
959 lastRealtime = rec.time;
962 if (rec.cmd != HistoryItem.CMD_OVERFLOW
963 && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
964 || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) {
965 if (curLevelPath != null) {
966 finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
967 lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn,
968 lastCameraOn, lastWifiRunning, lastCpuRunning, lastLinePath);
972 lastCharging = lastScreenOn = lastGpsOn = lastFlashlightOn =
973 lastCameraOn = lastCpuRunning = false;
980 mStats.finishIteratingHistoryLocked();
983 if (lastY < 0 || lastX < 0) {
984 // Didn't get any data...
985 x = lastX = mLevelLeft;
986 y = lastY = mLevelTop + levelh - ((mInfo.mBatteryLevel-batLow)*(levelh-1))/batChange;
988 byte value = (byte)mInfo.mBatteryLevel;
989 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
990 else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
991 else path = null; //mBatGoodPath;
996 mBatLevelPath.moveTo(x, y);
997 curLevelPath = mBatLevelPath;
1000 // Figure out where the actual data ends on the screen.
1001 x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
1007 finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
1008 lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn, lastCameraOn,
1009 lastWifiRunning, lastCpuRunning, lastLinePath);
1012 // If we reserved room for the remaining time, create a final path to draw
1013 // that part of the UI.
1014 mTimeRemainPath.moveTo(x, lastY);
1015 int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange;
1016 int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange;
1017 if (mInfo.mDischarging) {
1018 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1020 mTimeRemainPath.lineTo(mLevelRight, fullY);
1021 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1023 mTimeRemainPath.lineTo(x, emptyY);
1024 mTimeRemainPath.close();
1027 if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) {
1028 // Create the time labels at the bottom.
1029 boolean is24hr = is24Hour();
1030 Calendar calStart = Calendar.getInstance();
1031 calStart.setTimeInMillis(mStartWallTime);
1032 calStart.set(Calendar.MILLISECOND, 0);
1033 calStart.set(Calendar.SECOND, 0);
1034 calStart.set(Calendar.MINUTE, 0);
1035 long startRoundTime = calStart.getTimeInMillis();
1036 if (startRoundTime < mStartWallTime) {
1037 calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1);
1038 startRoundTime = calStart.getTimeInMillis();
1040 Calendar calEnd = Calendar.getInstance();
1041 calEnd.setTimeInMillis(mEndWallTime);
1042 calEnd.set(Calendar.MILLISECOND, 0);
1043 calEnd.set(Calendar.SECOND, 0);
1044 calEnd.set(Calendar.MINUTE, 0);
1045 long endRoundTime = calEnd.getTimeInMillis();
1046 if (startRoundTime < endRoundTime) {
1047 addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr);
1048 Calendar calMid = Calendar.getInstance();
1049 calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2));
1050 calMid.set(Calendar.MILLISECOND, 0);
1051 calMid.set(Calendar.SECOND, 0);
1052 calMid.set(Calendar.MINUTE, 0);
1053 long calMidMillis = calMid.getTimeInMillis();
1054 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1055 addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr);
1057 addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
1060 // Create the date labels if the chart includes multiple days
1061 if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) ||
1062 calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) {
1063 boolean isDayFirst = isDayFirst();
1064 calStart.set(Calendar.HOUR_OF_DAY, 0);
1065 startRoundTime = calStart.getTimeInMillis();
1066 if (startRoundTime < mStartWallTime) {
1067 calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1);
1068 startRoundTime = calStart.getTimeInMillis();
1070 calEnd.set(Calendar.HOUR_OF_DAY, 0);
1071 endRoundTime = calEnd.getTimeInMillis();
1072 if (startRoundTime < endRoundTime) {
1073 addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst);
1074 Calendar calMid = Calendar.getInstance();
1076 // The middle between two beginnings of days can be anywhere between -1 to 13
1077 // after the beginning of the "median" day.
1078 calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2)
1079 + 2 * 60 * 60 * 1000);
1080 calMid.set(Calendar.HOUR_OF_DAY, 0);
1081 calMid.set(Calendar.MINUTE, 0);
1082 long calMidMillis = calMid.getTimeInMillis();
1083 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1084 addDateLabel(calMid, mLevelLeft, mLevelRight, isDayFirst);
1087 addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
1091 if (mTimeLabels.size() < 2) {
1092 // If there are fewer than 2 time labels, then they are useless. Just
1093 // show an axis label giving the entire duration.
1094 mDurationString = Formatter.formatShortElapsedTime(getContext(),
1095 mEndWallTime - mStartWallTime);
1096 mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
1098 mDurationString = null;
1099 mDurationStringWidth = 0;
1103 void addTimeLabel(Calendar cal, int levelLeft, int levelRight, boolean is24hr) {
1104 final long walltimeStart = mStartWallTime;
1105 final long walltimeChange = mEndWallTime-walltimeStart;
1106 mTimeLabels.add(new TimeLabel(mTextPaint,
1107 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1112 void addDateLabel(Calendar cal, int levelLeft, int levelRight, boolean isDayFirst) {
1113 final long walltimeStart = mStartWallTime;
1114 final long walltimeChange = mEndWallTime-walltimeStart;
1115 mDateLabels.add(new DateLabel(mTextPaint,
1116 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1122 protected void onDraw(Canvas canvas) {
1123 super.onDraw(canvas);
1125 final int width = getWidth();
1126 final int height = getHeight();
1128 //buildBitmap(width, height);
1130 if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
1131 //canvas.drawBitmap(mBitmap, 0, 0, null);
1132 drawChart(canvas, width, height);
1135 void buildBitmap(int width, int height) {
1136 if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
1140 if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
1142 mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
1143 Bitmap.Config.ARGB_8888);
1144 mCanvas = new Canvas(mBitmap);
1145 drawChart(mCanvas, width, height);
1148 void drawChart(Canvas canvas, int width, int height) {
1149 final boolean layoutRtl = isLayoutRtl();
1150 final int textStartX = layoutRtl ? width : 0;
1151 final int textEndX = layoutRtl ? 0 : width;
1152 final Paint.Align textAlignLeft = layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT;
1153 final Paint.Align textAlignRight = layoutRtl ? Paint.Align.LEFT : Paint.Align.RIGHT;
1156 canvas.drawRect(1, 1, width, height, mDebugRectPaint);
1159 if (DEBUG) Log.d(TAG, "Drawing level path.");
1160 canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
1161 if (!mTimeRemainPath.isEmpty()) {
1162 if (DEBUG) Log.d(TAG, "Drawing time remain path.");
1163 canvas.drawPath(mTimeRemainPath, mTimeRemainPaint);
1165 if (mTimeLabels.size() > 1) {
1166 int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1167 int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2);
1168 mTextPaint.setTextAlign(Paint.Align.LEFT);
1170 for (int i=0; i<mTimeLabels.size(); i++) {
1171 TimeLabel label = mTimeLabels.get(i);
1173 int x = label.x - label.width/2;
1177 if (DEBUG) Log.d(TAG, "Drawing left label: " + label.label + " @ " + x);
1178 canvas.drawText(label.label, x, y, mTextPaint);
1179 canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1180 lastX = x + label.width;
1181 } else if (i < (mTimeLabels.size()-1)) {
1182 int x = label.x - label.width/2;
1183 if (x < (lastX+mTextAscent)) {
1186 TimeLabel nextLabel = mTimeLabels.get(i+1);
1187 if (x > (width-nextLabel.width-mTextAscent)) {
1190 if (DEBUG) Log.d(TAG, "Drawing middle label: " + label.label + " @ " + x);
1191 canvas.drawText(label.label, x, y, mTextPaint);
1192 canvas.drawLine(label.x, ytick, label.x, ytick + mThinLineWidth, mTextPaint);
1193 lastX = x + label.width;
1195 int x = label.x - label.width/2;
1196 if ((x+label.width) >= width) {
1197 x = width-1-label.width;
1199 if (DEBUG) Log.d(TAG, "Drawing right label: " + label.label + " @ " + x);
1200 canvas.drawText(label.label, x, y, mTextPaint);
1201 canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1204 } else if (mDurationString != null) {
1205 int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1206 mTextPaint.setTextAlign(Paint.Align.LEFT);
1207 canvas.drawText(mDurationString,
1208 mLevelLeft + (mLevelRight-mLevelLeft)/2 - mDurationStringWidth/2,
1212 int headerTop = -mHeaderTextAscent + (mHeaderTextDescent-mHeaderTextAscent)/3;
1213 mHeaderTextPaint.setTextAlign(textAlignLeft);
1214 if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mInfo.mChargeLabelString);
1215 canvas.drawText(mInfo.mChargeLabelString, textStartX, headerTop, mHeaderTextPaint);
1216 int stringHalfWidth = mChargeDurationStringWidth / 2;
1217 if (layoutRtl) stringHalfWidth = -stringHalfWidth;
1218 int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2)
1219 + (layoutRtl ? mDrainStringWidth : mChargeLabelStringWidth);
1220 if (DEBUG) Log.d(TAG, "Drawing charge duration string: " + mChargeDurationString);
1221 canvas.drawText(mChargeDurationString, headerCenter - stringHalfWidth, headerTop,
1223 mHeaderTextPaint.setTextAlign(textAlignRight);
1224 if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString);
1225 canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint);
1227 if (!mBatGoodPath.isEmpty()) {
1228 if (DEBUG) Log.d(TAG, "Drawing good battery path");
1229 canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
1231 if (!mBatWarnPath.isEmpty()) {
1232 if (DEBUG) Log.d(TAG, "Drawing warn battery path");
1233 canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
1235 if (!mBatCriticalPath.isEmpty()) {
1236 if (DEBUG) Log.d(TAG, "Drawing critical battery path");
1237 canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
1239 if (mHavePhoneSignal) {
1240 if (DEBUG) Log.d(TAG, "Drawing phone signal path");
1241 int top = height-mPhoneSignalOffset - (mLineWidth/2);
1242 mPhoneSignalChart.draw(canvas, top, mLineWidth);
1244 if (!mScreenOnPath.isEmpty()) {
1245 if (DEBUG) Log.d(TAG, "Drawing screen on path");
1246 canvas.drawPath(mScreenOnPath, mScreenOnPaint);
1248 if (!mChargingPath.isEmpty()) {
1249 if (DEBUG) Log.d(TAG, "Drawing charging path");
1250 canvas.drawPath(mChargingPath, mChargingPaint);
1253 if (!mGpsOnPath.isEmpty()) {
1254 if (DEBUG) Log.d(TAG, "Drawing gps path");
1255 canvas.drawPath(mGpsOnPath, mGpsOnPaint);
1258 if (mHaveFlashlight) {
1259 if (!mFlashlightOnPath.isEmpty()) {
1260 if (DEBUG) Log.d(TAG, "Drawing flashlight path");
1261 canvas.drawPath(mFlashlightOnPath, mFlashlightOnPaint);
1265 if (!mCameraOnPath.isEmpty()) {
1266 if (DEBUG) Log.d(TAG, "Drawing camera path");
1267 canvas.drawPath(mCameraOnPath, mCameraOnPaint);
1271 if (!mWifiRunningPath.isEmpty()) {
1272 if (DEBUG) Log.d(TAG, "Drawing wifi path");
1273 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
1276 if (!mCpuRunningPath.isEmpty()) {
1277 if (DEBUG) Log.d(TAG, "Drawing running path");
1278 canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
1282 if (DEBUG) Log.d(TAG, "Drawing large mode labels");
1283 Paint.Align align = mTextPaint.getTextAlign();
1284 mTextPaint.setTextAlign(textAlignLeft); // large-mode labels always aligned to start
1285 if (mHavePhoneSignal) {
1286 canvas.drawText(mPhoneSignalLabel, textStartX,
1287 height - mPhoneSignalOffset - mTextDescent, mTextPaint);
1290 canvas.drawText(mGpsOnLabel, textStartX,
1291 height - mGpsOnOffset - mTextDescent, mTextPaint);
1293 if (mHaveFlashlight) {
1294 canvas.drawText(mFlashlightOnLabel, textStartX,
1295 height - mFlashlightOnOffset - mTextDescent, mTextPaint);
1298 canvas.drawText(mCameraOnLabel, textStartX,
1299 height - mCameraOnOffset - mTextDescent, mTextPaint);
1302 canvas.drawText(mWifiRunningLabel, textStartX,
1303 height - mWifiRunningOffset - mTextDescent, mTextPaint);
1305 canvas.drawText(mCpuRunningLabel, textStartX,
1306 height - mCpuRunningOffset - mTextDescent, mTextPaint);
1307 canvas.drawText(mChargingLabel, textStartX,
1308 height - mChargingOffset - mTextDescent, mTextPaint);
1309 canvas.drawText(mScreenOnLabel, textStartX,
1310 height - mScreenOnOffset - mTextDescent, mTextPaint);
1311 mTextPaint.setTextAlign(align);
1314 canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
1315 mLevelBottom+(mThinLineWidth/2), mTextPaint);
1317 for (int i=0; i<10; i++) {
1318 int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10;
1319 canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y,
1320 mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint);
1323 if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth
1324 + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString));
1325 canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint);
1326 canvas.drawText(mMinPercentLabelString,
1327 mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth,
1328 mLevelBottom - mThinLineWidth, mTextPaint);
1329 canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width,
1330 mLevelBottom+mThinLineWidth, mTextPaint);
1332 if (mDateLabels.size() > 0) {
1333 int ytop = mLevelTop + mTextAscent;
1334 int ybottom = mLevelBottom;
1335 int lastLeft = mLevelRight;
1336 mTextPaint.setTextAlign(Paint.Align.LEFT);
1337 for (int i=mDateLabels.size()-1; i>=0; i--) {
1338 DateLabel label = mDateLabels.get(i);
1339 int left = label.x - mThinLineWidth;
1340 int x = label.x + mThinLineWidth*2;
1341 if ((x+label.width) >= lastLeft) {
1342 x = label.x - mThinLineWidth*2 - label.width;
1343 left = x - mThinLineWidth;
1344 if (left >= lastLeft) {
1349 if (left < mLevelLeft) {
1350 // Won't fit on left, give up.
1353 mDateLinePath.reset();
1354 mDateLinePath.moveTo(label.x, ytop);
1355 mDateLinePath.lineTo(label.x, ybottom);
1356 canvas.drawPath(mDateLinePath, mDateLinePaint);
1357 canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint);