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;
42 import com.android.settings.R;
43 import com.android.settings.Utils;
45 import libcore.icu.LocaleData;
47 import java.util.ArrayList;
48 import java.util.Calendar;
49 import java.util.Locale;
51 public class BatteryHistoryChart extends View {
52 static final boolean DEBUG = false;
53 static final String TAG = "BatteryHistoryChart";
55 static final int CHART_DATA_X_MASK = 0x0000ffff;
56 static final int CHART_DATA_BIN_MASK = 0xffff0000;
57 static final int CHART_DATA_BIN_SHIFT = 16;
59 static class ChartData {
67 void setColors(int[] colors) {
69 mPaints = new Paint[colors.length];
70 for (int i=0; i<colors.length; i++) {
71 mPaints[i] = new Paint();
72 mPaints[i].setColor(colors[i]);
73 mPaints[i].setStyle(Paint.Style.FILL);
77 void init(int width) {
79 mTicks = new int[width*2];
87 void addTick(int x, int bin) {
88 if (bin != mLastBin && mNumTicks < mTicks.length) {
89 mTicks[mNumTicks] = (x&CHART_DATA_X_MASK) | (bin<<CHART_DATA_BIN_SHIFT);
95 void finish(int width) {
101 void draw(Canvas canvas, int top, int height) {
102 int lastBin=0, lastX=0;
103 int bottom = top + height;
104 for (int i=0; i<mNumTicks; i++) {
105 int tick = mTicks[i];
106 int x = tick&CHART_DATA_X_MASK;
107 int bin = (tick&CHART_DATA_BIN_MASK) >> CHART_DATA_BIN_SHIFT;
109 canvas.drawRect(lastX, top, x, bottom, mPaints[lastBin]);
118 static final int SANS = 1;
119 static final int SERIF = 2;
120 static final int MONOSPACE = 3;
122 // First value if for phone off; first value is "scanning"; following values
123 // are battery stats signal strength buckets.
124 static final int NUM_PHONE_SIGNALS = 7;
126 final Paint mBatteryBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
127 final Paint mBatteryGoodPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
128 final Paint mBatteryWarnPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
129 final Paint mBatteryCriticalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
130 final Paint mTimeRemainPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
131 final Paint mChargingPaint = new Paint();
132 final Paint mScreenOnPaint = new Paint();
133 final Paint mGpsOnPaint = new Paint();
134 final Paint mFlashlightOnPaint = new Paint();
135 final Paint mCameraOnPaint = new Paint();
136 final Paint mWifiRunningPaint = new Paint();
137 final Paint mCpuRunningPaint = new Paint();
138 final Paint mDateLinePaint = new Paint();
139 final ChartData mPhoneSignalChart = new ChartData();
140 final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
141 final TextPaint mHeaderTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
142 final Paint mDebugRectPaint = new Paint();
144 final Path mBatLevelPath = new Path();
145 final Path mBatGoodPath = new Path();
146 final Path mBatWarnPath = new Path();
147 final Path mBatCriticalPath = new Path();
148 final Path mTimeRemainPath = new Path();
149 final Path mChargingPath = new Path();
150 final Path mScreenOnPath = new Path();
151 final Path mGpsOnPath = new Path();
152 final Path mFlashlightOnPath = new Path();
153 final Path mCameraOnPath = new Path();
154 final Path mWifiRunningPath = new Path();
155 final Path mCpuRunningPath = new Path();
156 final Path mDateLinePath = new Path();
159 Intent mBatteryBroadcast;
161 String mMaxPercentLabelString;
162 String mMinPercentLabelString;
163 String mDurationString;
164 String mChargeDurationString;
166 String mChargingLabel;
167 String mScreenOnLabel;
169 String mCameraOnLabel;
170 String mFlashlightOnLabel;
171 String mWifiRunningLabel;
172 String mCpuRunningLabel;
173 String mPhoneSignalLabel;
180 int mBatteryWarnLevel;
181 int mBatteryCriticalLevel;
185 int mHeaderTextAscent;
186 int mHeaderTextDescent;
187 int mMaxPercentLabelStringWidth;
188 int mMinPercentLabelStringWidth;
189 int mDurationStringWidth;
190 int mChargeLabelStringWidth;
191 int mChargeDurationStringWidth;
192 int mDrainStringWidth;
197 int mLastHeight = -1;
204 int mFlashlightOnOffset;
206 int mWifiRunningOffset;
207 int mCpuRunningOffset;
208 int mPhoneSignalOffset;
220 long mEndDataWallTime;
226 boolean mHavePhoneSignal;
228 boolean mHaveFlashlight;
230 final ArrayList<TimeLabel> mTimeLabels = new ArrayList<TimeLabel>();
231 final ArrayList<DateLabel> mDateLabels = new ArrayList<DateLabel>();
236 static class TextAttrs {
237 ColorStateList textColor = null;
239 int typefaceIndex = -1;
242 void retrieve(Context context, TypedArray from, int index) {
243 TypedArray appearance = null;
244 int ap = from.getResourceId(index, -1);
246 appearance = context.obtainStyledAttributes(ap,
247 com.android.internal.R.styleable.TextAppearance);
249 if (appearance != null) {
250 int n = appearance.getIndexCount();
251 for (int i = 0; i < n; i++) {
252 int attr = appearance.getIndex(i);
255 case com.android.internal.R.styleable.TextAppearance_textColor:
256 textColor = appearance.getColorStateList(attr);
259 case com.android.internal.R.styleable.TextAppearance_textSize:
260 textSize = appearance.getDimensionPixelSize(attr, textSize);
263 case com.android.internal.R.styleable.TextAppearance_typeface:
264 typefaceIndex = appearance.getInt(attr, -1);
267 case com.android.internal.R.styleable.TextAppearance_textStyle:
268 styleIndex = appearance.getInt(attr, -1);
273 appearance.recycle();
277 void apply(Context context, TextPaint paint) {
278 paint.density = context.getResources().getDisplayMetrics().density;
279 paint.setCompatibilityScaling(
280 context.getResources().getCompatibilityInfo().applicationScale);
282 paint.setColor(textColor.getDefaultColor());
283 paint.setTextSize(textSize);
286 switch (typefaceIndex) {
288 tf = Typeface.SANS_SERIF;
296 tf = Typeface.MONOSPACE;
300 setTypeface(paint, tf, styleIndex);
303 public void setTypeface(TextPaint paint, Typeface tf, int style) {
306 tf = Typeface.defaultFromStyle(style);
308 tf = Typeface.create(tf, style);
311 paint.setTypeface(tf);
312 // now compute what (if any) algorithmic styling is needed
313 int typefaceStyle = tf != null ? tf.getStyle() : 0;
314 int need = style & ~typefaceStyle;
315 paint.setFakeBoldText((need & Typeface.BOLD) != 0);
316 paint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
318 paint.setFakeBoldText(false);
319 paint.setTextSkewX(0);
320 paint.setTypeface(tf);
325 static class TimeLabel {
330 TimeLabel(TextPaint paint, int x, Calendar cal, boolean use24hr) {
332 final String bestFormat = DateFormat.getBestDateTimePattern(
333 Locale.getDefault(), use24hr ? "km" : "ha");
334 label = DateFormat.format(bestFormat, cal).toString();
335 width = (int)paint.measureText(label);
339 static class DateLabel {
344 DateLabel(TextPaint paint, int x, Calendar cal, boolean dayFirst) {
346 final String bestFormat = DateFormat.getBestDateTimePattern(
347 Locale.getDefault(), dayFirst ? "dM" : "Md");
348 label = DateFormat.format(bestFormat, cal).toString();
349 width = (int)paint.measureText(label);
353 public BatteryHistoryChart(Context context, AttributeSet attrs) {
354 super(context, attrs);
356 if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!");
358 mBatteryWarnLevel = mContext.getResources().getInteger(
359 com.android.internal.R.integer.config_lowBatteryWarningLevel);
360 mBatteryCriticalLevel = mContext.getResources().getInteger(
361 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
363 mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
364 2, getResources().getDisplayMetrics());
366 int accentColor = Utils.getColorAccent(mContext);
367 mBatteryBackgroundPaint.setColor(accentColor);
368 mBatteryBackgroundPaint.setStyle(Paint.Style.FILL);
369 mBatteryGoodPaint.setARGB(128, 0, 128, 0);
370 mBatteryGoodPaint.setStyle(Paint.Style.STROKE);
371 mBatteryWarnPaint.setARGB(128, 128, 128, 0);
372 mBatteryWarnPaint.setStyle(Paint.Style.STROKE);
373 mBatteryCriticalPaint.setARGB(192, 128, 0, 0);
374 mBatteryCriticalPaint.setStyle(Paint.Style.STROKE);
375 mTimeRemainPaint.setColor(0xFFCED7BB);
376 mTimeRemainPaint.setStyle(Paint.Style.FILL);
377 mChargingPaint.setStyle(Paint.Style.STROKE);
378 mScreenOnPaint.setStyle(Paint.Style.STROKE);
379 mGpsOnPaint.setStyle(Paint.Style.STROKE);
380 mCameraOnPaint.setStyle(Paint.Style.STROKE);
381 mFlashlightOnPaint.setStyle(Paint.Style.STROKE);
382 mWifiRunningPaint.setStyle(Paint.Style.STROKE);
383 mCpuRunningPaint.setStyle(Paint.Style.STROKE);
384 mPhoneSignalChart.setColors(com.android.settings.Utils.BADNESS_COLORS);
385 mDebugRectPaint.setARGB(255, 255, 0, 0);
386 mDebugRectPaint.setStyle(Paint.Style.STROKE);
387 mScreenOnPaint.setColor(accentColor);
388 mGpsOnPaint.setColor(accentColor);
389 mCameraOnPaint.setColor(accentColor);
390 mFlashlightOnPaint.setColor(accentColor);
391 mWifiRunningPaint.setColor(accentColor);
392 mCpuRunningPaint.setColor(accentColor);
393 mChargingPaint.setColor(accentColor);
396 context.obtainStyledAttributes(
397 attrs, R.styleable.BatteryHistoryChart, 0, 0);
399 final TextAttrs mainTextAttrs = new TextAttrs();
400 final TextAttrs headTextAttrs = new TextAttrs();
401 mainTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_android_textAppearance);
402 headTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_headerAppearance);
405 float dx=0, dy=0, r=0;
407 int n = a.getIndexCount();
408 for (int i = 0; i < n; i++) {
409 int attr = a.getIndex(i);
412 case R.styleable.BatteryHistoryChart_android_shadowColor:
413 shadowcolor = a.getInt(attr, 0);
416 case R.styleable.BatteryHistoryChart_android_shadowDx:
417 dx = a.getFloat(attr, 0);
420 case R.styleable.BatteryHistoryChart_android_shadowDy:
421 dy = a.getFloat(attr, 0);
424 case R.styleable.BatteryHistoryChart_android_shadowRadius:
425 r = a.getFloat(attr, 0);
428 case R.styleable.BatteryHistoryChart_android_textColor:
429 mainTextAttrs.textColor = a.getColorStateList(attr);
430 headTextAttrs.textColor = a.getColorStateList(attr);
433 case R.styleable.BatteryHistoryChart_android_textSize:
434 mainTextAttrs.textSize = a.getDimensionPixelSize(attr, mainTextAttrs.textSize);
435 headTextAttrs.textSize = a.getDimensionPixelSize(attr, headTextAttrs.textSize);
438 case R.styleable.BatteryHistoryChart_android_typeface:
439 mainTextAttrs.typefaceIndex = a.getInt(attr, mainTextAttrs.typefaceIndex);
440 headTextAttrs.typefaceIndex = a.getInt(attr, headTextAttrs.typefaceIndex);
443 case R.styleable.BatteryHistoryChart_android_textStyle:
444 mainTextAttrs.styleIndex = a.getInt(attr, mainTextAttrs.styleIndex);
445 headTextAttrs.styleIndex = a.getInt(attr, headTextAttrs.styleIndex);
448 case R.styleable.BatteryHistoryChart_barPrimaryColor:
449 mBatteryBackgroundPaint.setColor(a.getInt(attr, 0));
450 mScreenOnPaint.setColor(a.getInt(attr, 0));
451 mGpsOnPaint.setColor(a.getInt(attr, 0));
452 mCameraOnPaint.setColor(a.getInt(attr, 0));
453 mFlashlightOnPaint.setColor(a.getInt(attr, 0));
454 mWifiRunningPaint.setColor(a.getInt(attr, 0));
455 mCpuRunningPaint.setColor(a.getInt(attr, 0));
456 mChargingPaint.setColor(a.getInt(attr, 0));
459 case R.styleable.BatteryHistoryChart_barPredictionColor:
460 mTimeRemainPaint.setColor(a.getInt(attr, 0));
463 case R.styleable.BatteryHistoryChart_chartMinHeight:
464 mChartMinHeight = a.getDimensionPixelSize(attr, 0);
471 mainTextAttrs.apply(context, mTextPaint);
472 headTextAttrs.apply(context, mHeaderTextPaint);
474 mDateLinePaint.set(mTextPaint);
475 mDateLinePaint.setStyle(Paint.Style.STROKE);
476 int hairlineWidth = mThinLineWidth/2;
477 if (hairlineWidth < 1) {
480 mDateLinePaint.setStrokeWidth(hairlineWidth);
481 mDateLinePaint.setPathEffect(new DashPathEffect(new float[] {
482 mThinLineWidth * 2, mThinLineWidth * 2 }, 0));
484 if (shadowcolor != 0) {
485 mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
486 mHeaderTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
490 void setStats(BatteryStats stats, Intent broadcast) {
492 mBatteryBroadcast = broadcast;
494 if (DEBUG) Log.d(TAG, "Setting stats...");
496 final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
498 long uSecTime = mStats.computeBatteryRealtime(elapsedRealtimeUs,
499 BatteryStats.STATS_SINCE_CHARGED);
500 mStatsPeriod = uSecTime;
501 mChargingLabel = getContext().getString(R.string.battery_stats_charging_label);
502 mScreenOnLabel = getContext().getString(R.string.battery_stats_screen_on_label);
503 mGpsOnLabel = getContext().getString(R.string.battery_stats_gps_on_label);
504 mCameraOnLabel = getContext().getString(R.string.battery_stats_camera_on_label);
505 mFlashlightOnLabel = getContext().getString(R.string.battery_stats_flashlight_on_label);
506 mWifiRunningLabel = getContext().getString(R.string.battery_stats_wifi_running_label);
507 mCpuRunningLabel = getContext().getString(R.string.battery_stats_wake_lock_label);
508 mPhoneSignalLabel = getContext().getString(R.string.battery_stats_phone_signal_label);
510 mMaxPercentLabelString = Utils.formatPercentage(100);
511 mMinPercentLabelString = Utils.formatPercentage(0);
512 BatteryInfo.getBatteryInfo(getContext(), info -> {
515 mChargeDurationString = "";
516 setContentDescription(mInfo.chargeLabel);
519 int lastInteresting = 0;
524 mEndDataWallTime = 0;
528 long lastWallTime = 0;
529 long lastRealtime = 0;
532 boolean first = true;
533 if (stats.startIteratingHistoryLocked()) {
534 final HistoryItem rec = new HistoryItem();
535 while (stats.getNextHistoryLocked(rec)) {
539 mHistStart = rec.time;
541 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
542 || rec.cmd == HistoryItem.CMD_RESET) {
543 // If there is a ridiculously large jump in time, then we won't be
544 // able to create a good chart with that data, so just ignore the
545 // times we got before and pretend like our data extends back from
546 // the time we have now.
547 // Also, if we are getting a time change and we are less than 5 minutes
548 // since the start of the history real time, then also use this new
549 // time to compute the base time, since whatever time we had before is
550 // pretty much just noise.
551 if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L))
552 || rec.time < (mHistStart+(5*60*1000L))) {
555 lastWallTime = rec.currentTime;
556 lastRealtime = rec.time;
557 if (mStartWallTime == 0) {
558 mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
561 if (rec.isDeltaData()) {
562 if (rec.batteryLevel != lastLevel || pos == 1) {
563 lastLevel = rec.batteryLevel;
565 lastInteresting = pos;
566 mHistDataEnd = rec.time;
567 aggrStates |= rec.states;
568 aggrStates2 |= rec.states2;
572 mHistEnd = mHistDataEnd + (mInfo.remainingTimeUs/1000);
573 mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
574 mEndWallTime = mEndDataWallTime + (mInfo.remainingTimeUs/1000);
575 mNumHist = lastInteresting;
576 mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
577 mHaveFlashlight = (aggrStates2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
578 mHaveCamera = (aggrStates2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
579 mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
580 || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
581 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
582 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
583 if (!com.android.settingslib.Utils.isWifiOnly(getContext())) {
584 mHavePhoneSignal = true;
586 if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
587 }, mStats, false /* shortString */);
591 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
592 mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString);
593 mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString);
594 mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString);
595 mChargeLabelStringWidth = (int) mHeaderTextPaint.measureText(
596 mInfo.chargeLabel.toString());
597 mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString);
598 mTextAscent = (int)mTextPaint.ascent();
599 mTextDescent = (int)mTextPaint.descent();
600 mHeaderTextAscent = (int)mHeaderTextPaint.ascent();
601 mHeaderTextDescent = (int)mHeaderTextPaint.descent();
602 int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent;
603 mHeaderHeight = headerTextHeight*2 - mTextAscent;
604 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
605 getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec));
608 void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
609 int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
610 boolean lastFlashlightOn, boolean lastCameraOn, boolean lastWifiRunning,
611 boolean lastCpuRunning, Path lastPath) {
612 if (curLevelPath != null) {
613 if (lastX >= 0 && lastX < w) {
614 if (lastPath != null) {
615 lastPath.lineTo(w, y);
617 curLevelPath.lineTo(w, y);
619 curLevelPath.lineTo(w, mLevelTop+levelh);
620 curLevelPath.lineTo(startX, mLevelTop+levelh);
621 curLevelPath.close();
625 mChargingPath.lineTo(w, h-mChargingOffset);
628 mScreenOnPath.lineTo(w, h-mScreenOnOffset);
631 mGpsOnPath.lineTo(w, h-mGpsOnOffset);
633 if (lastFlashlightOn) {
634 mFlashlightOnPath.lineTo(w, h-mFlashlightOnOffset);
637 mCameraOnPath.lineTo(w, h-mCameraOnOffset);
639 if (lastWifiRunning) {
640 mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
642 if (lastCpuRunning) {
643 mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
645 if (mHavePhoneSignal) {
646 mPhoneSignalChart.finish(w);
650 private boolean is24Hour() {
651 return DateFormat.is24HourFormat(getContext());
654 private boolean isDayFirst() {
655 final String value = LocaleData.get(getResources().getConfiguration().locale)
656 .getDateFormat(java.text.DateFormat.SHORT);
657 return value.indexOf('M') > value.indexOf('d');
661 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
662 super.onSizeChanged(w, h, oldw, oldh);
664 if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
666 if (mLastWidth == w && mLastHeight == h) {
670 if (mLastWidth == 0 || mLastHeight == 0) {
674 if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
681 int textHeight = mTextDescent - mTextAscent;
682 if (h > ((textHeight*10)+mChartMinHeight)) {
684 if (h > (textHeight*15)) {
685 // Plenty of room for the chart.
686 mLineWidth = textHeight/2;
688 // Compress lines to make more room for chart.
689 mLineWidth = textHeight/3;
693 mLineWidth = mThinLineWidth;
695 if (mLineWidth <= 0) mLineWidth = 1;
697 mLevelTop = mHeaderHeight;
698 mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
700 int levelWidth = mLevelRight-mLevelLeft;
702 mTextPaint.setStrokeWidth(mThinLineWidth);
703 mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
704 mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
705 mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth);
706 mChargingPaint.setStrokeWidth(mLineWidth);
707 mScreenOnPaint.setStrokeWidth(mLineWidth);
708 mGpsOnPaint.setStrokeWidth(mLineWidth);
709 mCameraOnPaint.setStrokeWidth(mLineWidth);
710 mFlashlightOnPaint.setStrokeWidth(mLineWidth);
711 mWifiRunningPaint.setStrokeWidth(mLineWidth);
712 mCpuRunningPaint.setStrokeWidth(mLineWidth);
713 mDebugRectPaint.setStrokeWidth(1);
715 int fullBarOffset = textHeight + mLineWidth;
718 mChargingOffset = mLineWidth;
719 mScreenOnOffset = mChargingOffset + fullBarOffset;
720 mCpuRunningOffset = mScreenOnOffset + fullBarOffset;
721 mWifiRunningOffset = mCpuRunningOffset + fullBarOffset;
722 mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0);
723 mFlashlightOnOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0);
724 mCameraOnOffset = mFlashlightOnOffset + (mHaveFlashlight ? fullBarOffset : 0);
725 mPhoneSignalOffset = mCameraOnOffset + (mHaveCamera ? fullBarOffset : 0);
726 mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0)
727 + mLineWidth*2 + mLineWidth/2;
728 if (mHavePhoneSignal) {
729 mPhoneSignalChart.init(w);
732 mScreenOnOffset = mGpsOnOffset = mCameraOnOffset = mFlashlightOnOffset =
733 mWifiRunningOffset = mCpuRunningOffset = mChargingOffset =
734 mPhoneSignalOffset = 0;
735 mLevelOffset = fullBarOffset + mThinLineWidth*4;
736 if (mHavePhoneSignal) {
737 mPhoneSignalChart.init(0);
741 mBatLevelPath.reset();
742 mBatGoodPath.reset();
743 mBatWarnPath.reset();
744 mTimeRemainPath.reset();
745 mBatCriticalPath.reset();
746 mScreenOnPath.reset();
748 mFlashlightOnPath.reset();
749 mCameraOnPath.reset();
750 mWifiRunningPath.reset();
751 mCpuRunningPath.reset();
752 mChargingPath.reset();
757 final long walltimeStart = mStartWallTime;
758 final long walltimeChange = mEndWallTime > walltimeStart
759 ? (mEndWallTime-walltimeStart) : 1;
760 long curWalltime = mStartWallTime;
761 long lastRealtime = 0;
763 final int batLow = mBatLow;
764 final int batChange = mBatHigh-mBatLow;
766 final int levelh = h - mLevelOffset - mLevelTop;
767 mLevelBottom = mLevelTop + levelh;
769 int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
771 Path curLevelPath = null;
772 Path lastLinePath = null;
773 boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
774 boolean lastFlashlightOn = false, lastCameraOn = false;
775 boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false;
776 int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
777 final int N = mNumHist;
778 if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) {
779 final HistoryItem rec = new HistoryItem();
780 while (mStats.getNextHistoryLocked(rec) && i < N) {
781 if (rec.isDeltaData()) {
782 curWalltime += rec.time-lastRealtime;
783 lastRealtime = rec.time;
784 x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange);
789 StringBuilder sb = new StringBuilder(128);
790 sb.append("walloff=");
791 TimeUtils.formatDuration(curWalltime - walltimeStart, sb);
792 sb.append(" wallchange=");
793 TimeUtils.formatDuration(walltimeChange, sb);
796 Log.d("foo", sb.toString());
798 y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
801 // We have moved by at least a pixel.
803 // Don't plot changes within a pixel.
805 byte value = rec.batteryLevel;
806 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
807 else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
808 else path = null; //mBatGoodPath;
810 if (path != lastLinePath) {
811 if (lastLinePath != null) {
812 lastLinePath.lineTo(x, y);
818 } else if (path != null) {
822 if (curLevelPath == null) {
823 curLevelPath = mBatLevelPath;
824 curLevelPath.moveTo(x, y);
827 curLevelPath.lineTo(x, y);
835 final boolean charging =
836 (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
837 if (charging != lastCharging) {
839 mChargingPath.moveTo(x, h-mChargingOffset);
841 mChargingPath.lineTo(x, h-mChargingOffset);
843 lastCharging = charging;
846 final boolean screenOn =
847 (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
848 if (screenOn != lastScreenOn) {
850 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
852 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
854 lastScreenOn = screenOn;
857 final boolean gpsOn =
858 (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
859 if (gpsOn != lastGpsOn) {
861 mGpsOnPath.moveTo(x, h-mGpsOnOffset);
863 mGpsOnPath.lineTo(x, h-mGpsOnOffset);
868 final boolean flashlightOn =
869 (rec.states2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
870 if (flashlightOn != lastFlashlightOn) {
872 mFlashlightOnPath.moveTo(x, h-mFlashlightOnOffset);
874 mFlashlightOnPath.lineTo(x, h-mFlashlightOnOffset);
876 lastFlashlightOn = flashlightOn;
879 final boolean cameraOn =
880 (rec.states2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
881 if (cameraOn != lastCameraOn) {
883 mCameraOnPath.moveTo(x, h-mCameraOnOffset);
885 mCameraOnPath.lineTo(x, h-mCameraOnOffset);
887 lastCameraOn = cameraOn;
890 final int wifiSupplState =
891 ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
892 >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
894 if (lastWifiSupplState != wifiSupplState) {
895 lastWifiSupplState = wifiSupplState;
896 switch (wifiSupplState) {
897 case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED:
898 case BatteryStats.WIFI_SUPPL_STATE_DORMANT:
899 case BatteryStats.WIFI_SUPPL_STATE_INACTIVE:
900 case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
901 case BatteryStats.WIFI_SUPPL_STATE_INVALID:
902 case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED:
903 wifiRunning = lastWifiSupplRunning = false;
906 wifiRunning = lastWifiSupplRunning = true;
910 wifiRunning = lastWifiSupplRunning;
912 if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
913 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
914 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
917 if (wifiRunning != lastWifiRunning) {
919 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
921 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
923 lastWifiRunning = wifiRunning;
926 final boolean cpuRunning =
927 (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
928 if (cpuRunning != lastCpuRunning) {
930 mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
932 mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
934 lastCpuRunning = cpuRunning;
937 if (mLargeMode && mHavePhoneSignal) {
939 if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
940 >> HistoryItem.STATE_PHONE_STATE_SHIFT)
941 == ServiceState.STATE_POWER_OFF) {
943 } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
946 bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
947 >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
950 mPhoneSignalChart.addTick(x, bin);
955 long lastWalltime = curWalltime;
956 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
957 || rec.cmd == HistoryItem.CMD_RESET) {
958 if (rec.currentTime >= mStartWallTime) {
959 curWalltime = rec.currentTime;
961 curWalltime = mStartWallTime + (rec.time-mHistStart);
963 lastRealtime = rec.time;
966 if (rec.cmd != HistoryItem.CMD_OVERFLOW
967 && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
968 || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) {
969 if (curLevelPath != null) {
970 finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
971 lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn,
972 lastCameraOn, lastWifiRunning, lastCpuRunning, lastLinePath);
976 lastCharging = lastScreenOn = lastGpsOn = lastFlashlightOn =
977 lastCameraOn = lastCpuRunning = false;
984 mStats.finishIteratingHistoryLocked();
987 if (lastY < 0 || lastX < 0) {
988 // Didn't get any data...
989 x = lastX = mLevelLeft;
990 y = lastY = mLevelTop + levelh - ((mInfo.batteryLevel -batLow)*(levelh-1))/batChange;
992 byte value = (byte)mInfo.batteryLevel;
993 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
994 else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
995 else path = null; //mBatGoodPath;
1000 mBatLevelPath.moveTo(x, y);
1001 curLevelPath = mBatLevelPath;
1004 // Figure out where the actual data ends on the screen.
1005 x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
1011 finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
1012 lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn, lastCameraOn,
1013 lastWifiRunning, lastCpuRunning, lastLinePath);
1016 // If we reserved room for the remaining time, create a final path to draw
1017 // that part of the UI.
1018 mTimeRemainPath.moveTo(x, lastY);
1019 int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange;
1020 int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange;
1021 if (mInfo.discharging) {
1022 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1024 mTimeRemainPath.lineTo(mLevelRight, fullY);
1025 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1027 mTimeRemainPath.lineTo(x, emptyY);
1028 mTimeRemainPath.close();
1031 if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) {
1032 // Create the time labels at the bottom.
1033 boolean is24hr = is24Hour();
1034 Calendar calStart = Calendar.getInstance();
1035 calStart.setTimeInMillis(mStartWallTime);
1036 calStart.set(Calendar.MILLISECOND, 0);
1037 calStart.set(Calendar.SECOND, 0);
1038 calStart.set(Calendar.MINUTE, 0);
1039 long startRoundTime = calStart.getTimeInMillis();
1040 if (startRoundTime < mStartWallTime) {
1041 calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1);
1042 startRoundTime = calStart.getTimeInMillis();
1044 Calendar calEnd = Calendar.getInstance();
1045 calEnd.setTimeInMillis(mEndWallTime);
1046 calEnd.set(Calendar.MILLISECOND, 0);
1047 calEnd.set(Calendar.SECOND, 0);
1048 calEnd.set(Calendar.MINUTE, 0);
1049 long endRoundTime = calEnd.getTimeInMillis();
1050 if (startRoundTime < endRoundTime) {
1051 addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr);
1052 Calendar calMid = Calendar.getInstance();
1053 calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2));
1054 calMid.set(Calendar.MILLISECOND, 0);
1055 calMid.set(Calendar.SECOND, 0);
1056 calMid.set(Calendar.MINUTE, 0);
1057 long calMidMillis = calMid.getTimeInMillis();
1058 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1059 addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr);
1061 addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
1064 // Create the date labels if the chart includes multiple days
1065 if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) ||
1066 calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) {
1067 boolean isDayFirst = isDayFirst();
1068 calStart.set(Calendar.HOUR_OF_DAY, 0);
1069 startRoundTime = calStart.getTimeInMillis();
1070 if (startRoundTime < mStartWallTime) {
1071 calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1);
1072 startRoundTime = calStart.getTimeInMillis();
1074 calEnd.set(Calendar.HOUR_OF_DAY, 0);
1075 endRoundTime = calEnd.getTimeInMillis();
1076 if (startRoundTime < endRoundTime) {
1077 addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst);
1078 Calendar calMid = Calendar.getInstance();
1080 // The middle between two beginnings of days can be anywhere between -1 to 13
1081 // after the beginning of the "median" day.
1082 calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2)
1083 + 2 * 60 * 60 * 1000);
1084 calMid.set(Calendar.HOUR_OF_DAY, 0);
1085 calMid.set(Calendar.MINUTE, 0);
1086 long calMidMillis = calMid.getTimeInMillis();
1087 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1088 addDateLabel(calMid, mLevelLeft, mLevelRight, isDayFirst);
1091 addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
1095 if (mTimeLabels.size() < 2) {
1096 // If there are fewer than 2 time labels, then they are useless. Just
1097 // show an axis label giving the entire duration.
1098 mDurationString = Formatter.formatShortElapsedTime(getContext(),
1099 mEndWallTime - mStartWallTime);
1100 mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
1102 mDurationString = null;
1103 mDurationStringWidth = 0;
1107 void addTimeLabel(Calendar cal, int levelLeft, int levelRight, boolean is24hr) {
1108 final long walltimeStart = mStartWallTime;
1109 final long walltimeChange = mEndWallTime-walltimeStart;
1110 mTimeLabels.add(new TimeLabel(mTextPaint,
1111 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1116 void addDateLabel(Calendar cal, int levelLeft, int levelRight, boolean isDayFirst) {
1117 final long walltimeStart = mStartWallTime;
1118 final long walltimeChange = mEndWallTime-walltimeStart;
1119 mDateLabels.add(new DateLabel(mTextPaint,
1120 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1126 protected void onDraw(Canvas canvas) {
1127 super.onDraw(canvas);
1129 final int width = getWidth();
1130 final int height = getHeight();
1132 //buildBitmap(width, height);
1134 if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
1135 //canvas.drawBitmap(mBitmap, 0, 0, null);
1136 drawChart(canvas, width, height);
1139 void buildBitmap(int width, int height) {
1140 if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
1144 if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
1146 mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
1147 Bitmap.Config.ARGB_8888);
1148 mCanvas = new Canvas(mBitmap);
1149 drawChart(mCanvas, width, height);
1152 void drawChart(Canvas canvas, int width, int height) {
1153 final boolean layoutRtl = isLayoutRtl();
1154 final int textStartX = layoutRtl ? width : 0;
1155 final int textEndX = layoutRtl ? 0 : width;
1156 final Paint.Align textAlignLeft = layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT;
1157 final Paint.Align textAlignRight = layoutRtl ? Paint.Align.LEFT : Paint.Align.RIGHT;
1160 canvas.drawRect(1, 1, width, height, mDebugRectPaint);
1163 if (DEBUG) Log.d(TAG, "Drawing level path.");
1164 canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
1165 if (!mTimeRemainPath.isEmpty()) {
1166 if (DEBUG) Log.d(TAG, "Drawing time remain path.");
1167 canvas.drawPath(mTimeRemainPath, mTimeRemainPaint);
1169 if (mTimeLabels.size() > 1) {
1170 int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1171 int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2);
1172 mTextPaint.setTextAlign(Paint.Align.LEFT);
1174 for (int i=0; i<mTimeLabels.size(); i++) {
1175 TimeLabel label = mTimeLabels.get(i);
1177 int x = label.x - label.width/2;
1181 if (DEBUG) Log.d(TAG, "Drawing left label: " + label.label + " @ " + x);
1182 canvas.drawText(label.label, x, y, mTextPaint);
1183 canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1184 lastX = x + label.width;
1185 } else if (i < (mTimeLabels.size()-1)) {
1186 int x = label.x - label.width/2;
1187 if (x < (lastX+mTextAscent)) {
1190 TimeLabel nextLabel = mTimeLabels.get(i+1);
1191 if (x > (width-nextLabel.width-mTextAscent)) {
1194 if (DEBUG) Log.d(TAG, "Drawing middle label: " + label.label + " @ " + x);
1195 canvas.drawText(label.label, x, y, mTextPaint);
1196 canvas.drawLine(label.x, ytick, label.x, ytick + mThinLineWidth, mTextPaint);
1197 lastX = x + label.width;
1199 int x = label.x - label.width/2;
1200 if ((x+label.width) >= width) {
1201 x = width-1-label.width;
1203 if (DEBUG) Log.d(TAG, "Drawing right label: " + label.label + " @ " + x);
1204 canvas.drawText(label.label, x, y, mTextPaint);
1205 canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1208 } else if (mDurationString != null) {
1209 int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1210 mTextPaint.setTextAlign(Paint.Align.LEFT);
1211 canvas.drawText(mDurationString,
1212 mLevelLeft + (mLevelRight-mLevelLeft)/2 - mDurationStringWidth/2,
1216 int headerTop = -mHeaderTextAscent + (mHeaderTextDescent-mHeaderTextAscent)/3;
1217 mHeaderTextPaint.setTextAlign(textAlignLeft);
1218 if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mInfo.chargeLabel);
1219 canvas.drawText(mInfo.chargeLabel.toString(), textStartX, headerTop,
1221 int stringHalfWidth = mChargeDurationStringWidth / 2;
1222 if (layoutRtl) stringHalfWidth = -stringHalfWidth;
1223 int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2)
1224 + (layoutRtl ? mDrainStringWidth : mChargeLabelStringWidth);
1225 if (DEBUG) Log.d(TAG, "Drawing charge duration string: " + mChargeDurationString);
1226 canvas.drawText(mChargeDurationString, headerCenter - stringHalfWidth, headerTop,
1228 mHeaderTextPaint.setTextAlign(textAlignRight);
1229 if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString);
1230 canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint);
1232 if (!mBatGoodPath.isEmpty()) {
1233 if (DEBUG) Log.d(TAG, "Drawing good battery path");
1234 canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
1236 if (!mBatWarnPath.isEmpty()) {
1237 if (DEBUG) Log.d(TAG, "Drawing warn battery path");
1238 canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
1240 if (!mBatCriticalPath.isEmpty()) {
1241 if (DEBUG) Log.d(TAG, "Drawing critical battery path");
1242 canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
1244 if (mHavePhoneSignal) {
1245 if (DEBUG) Log.d(TAG, "Drawing phone signal path");
1246 int top = height-mPhoneSignalOffset - (mLineWidth/2);
1247 mPhoneSignalChart.draw(canvas, top, mLineWidth);
1249 if (!mScreenOnPath.isEmpty()) {
1250 if (DEBUG) Log.d(TAG, "Drawing screen on path");
1251 canvas.drawPath(mScreenOnPath, mScreenOnPaint);
1253 if (!mChargingPath.isEmpty()) {
1254 if (DEBUG) Log.d(TAG, "Drawing charging path");
1255 canvas.drawPath(mChargingPath, mChargingPaint);
1258 if (!mGpsOnPath.isEmpty()) {
1259 if (DEBUG) Log.d(TAG, "Drawing gps path");
1260 canvas.drawPath(mGpsOnPath, mGpsOnPaint);
1263 if (mHaveFlashlight) {
1264 if (!mFlashlightOnPath.isEmpty()) {
1265 if (DEBUG) Log.d(TAG, "Drawing flashlight path");
1266 canvas.drawPath(mFlashlightOnPath, mFlashlightOnPaint);
1270 if (!mCameraOnPath.isEmpty()) {
1271 if (DEBUG) Log.d(TAG, "Drawing camera path");
1272 canvas.drawPath(mCameraOnPath, mCameraOnPaint);
1276 if (!mWifiRunningPath.isEmpty()) {
1277 if (DEBUG) Log.d(TAG, "Drawing wifi path");
1278 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
1281 if (!mCpuRunningPath.isEmpty()) {
1282 if (DEBUG) Log.d(TAG, "Drawing running path");
1283 canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
1287 if (DEBUG) Log.d(TAG, "Drawing large mode labels");
1288 Paint.Align align = mTextPaint.getTextAlign();
1289 mTextPaint.setTextAlign(textAlignLeft); // large-mode labels always aligned to start
1290 if (mHavePhoneSignal) {
1291 canvas.drawText(mPhoneSignalLabel, textStartX,
1292 height - mPhoneSignalOffset - mTextDescent, mTextPaint);
1295 canvas.drawText(mGpsOnLabel, textStartX,
1296 height - mGpsOnOffset - mTextDescent, mTextPaint);
1298 if (mHaveFlashlight) {
1299 canvas.drawText(mFlashlightOnLabel, textStartX,
1300 height - mFlashlightOnOffset - mTextDescent, mTextPaint);
1303 canvas.drawText(mCameraOnLabel, textStartX,
1304 height - mCameraOnOffset - mTextDescent, mTextPaint);
1307 canvas.drawText(mWifiRunningLabel, textStartX,
1308 height - mWifiRunningOffset - mTextDescent, mTextPaint);
1310 canvas.drawText(mCpuRunningLabel, textStartX,
1311 height - mCpuRunningOffset - mTextDescent, mTextPaint);
1312 canvas.drawText(mChargingLabel, textStartX,
1313 height - mChargingOffset - mTextDescent, mTextPaint);
1314 canvas.drawText(mScreenOnLabel, textStartX,
1315 height - mScreenOnOffset - mTextDescent, mTextPaint);
1316 mTextPaint.setTextAlign(align);
1319 canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
1320 mLevelBottom+(mThinLineWidth/2), mTextPaint);
1322 for (int i=0; i<10; i++) {
1323 int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10;
1324 canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y,
1325 mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint);
1328 if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth
1329 + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString));
1330 canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint);
1331 canvas.drawText(mMinPercentLabelString,
1332 mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth,
1333 mLevelBottom - mThinLineWidth, mTextPaint);
1334 canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width,
1335 mLevelBottom+mThinLineWidth, mTextPaint);
1337 if (mDateLabels.size() > 0) {
1338 int ytop = mLevelTop + mTextAscent;
1339 int ybottom = mLevelBottom;
1340 int lastLeft = mLevelRight;
1341 mTextPaint.setTextAlign(Paint.Align.LEFT);
1342 for (int i=mDateLabels.size()-1; i>=0; i--) {
1343 DateLabel label = mDateLabels.get(i);
1344 int left = label.x - mThinLineWidth;
1345 int x = label.x + mThinLineWidth*2;
1346 if ((x+label.width) >= lastLeft) {
1347 x = label.x - mThinLineWidth*2 - label.width;
1348 left = x - mThinLineWidth;
1349 if (left >= lastLeft) {
1354 if (left < mLevelLeft) {
1355 // Won't fit on left, give up.
1358 mDateLinePath.reset();
1359 mDateLinePath.moveTo(label.x, ytop);
1360 mDateLinePath.lineTo(label.x, ybottom);
1361 canvas.drawPath(mDateLinePath, mDateLinePaint);
1362 canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint);