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 mBatteryBackgroundPaint.setColor(0xFF009688);
367 mBatteryBackgroundPaint.setStyle(Paint.Style.FILL);
368 mBatteryGoodPaint.setARGB(128, 0, 128, 0);
369 mBatteryGoodPaint.setStyle(Paint.Style.STROKE);
370 mBatteryWarnPaint.setARGB(128, 128, 128, 0);
371 mBatteryWarnPaint.setStyle(Paint.Style.STROKE);
372 mBatteryCriticalPaint.setARGB(192, 128, 0, 0);
373 mBatteryCriticalPaint.setStyle(Paint.Style.STROKE);
374 mTimeRemainPaint.setColor(0xFFCED7BB);
375 mTimeRemainPaint.setStyle(Paint.Style.FILL);
376 mChargingPaint.setStyle(Paint.Style.STROKE);
377 mScreenOnPaint.setStyle(Paint.Style.STROKE);
378 mGpsOnPaint.setStyle(Paint.Style.STROKE);
379 mCameraOnPaint.setStyle(Paint.Style.STROKE);
380 mFlashlightOnPaint.setStyle(Paint.Style.STROKE);
381 mWifiRunningPaint.setStyle(Paint.Style.STROKE);
382 mCpuRunningPaint.setStyle(Paint.Style.STROKE);
383 mPhoneSignalChart.setColors(com.android.settings.Utils.BADNESS_COLORS);
384 mDebugRectPaint.setARGB(255, 255, 0, 0);
385 mDebugRectPaint.setStyle(Paint.Style.STROKE);
386 mScreenOnPaint.setColor(0xFF009688);
387 mGpsOnPaint.setColor(0xFF009688);
388 mCameraOnPaint.setColor(0xFF009688);
389 mFlashlightOnPaint.setColor(0xFF009688);
390 mWifiRunningPaint.setColor(0xFF009688);
391 mCpuRunningPaint.setColor(0xFF009688);
392 mChargingPaint.setColor(0xFF009688);
395 context.obtainStyledAttributes(
396 attrs, R.styleable.BatteryHistoryChart, 0, 0);
398 final TextAttrs mainTextAttrs = new TextAttrs();
399 final TextAttrs headTextAttrs = new TextAttrs();
400 mainTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_android_textAppearance);
401 headTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_headerAppearance);
404 float dx=0, dy=0, r=0;
406 int n = a.getIndexCount();
407 for (int i = 0; i < n; i++) {
408 int attr = a.getIndex(i);
411 case R.styleable.BatteryHistoryChart_android_shadowColor:
412 shadowcolor = a.getInt(attr, 0);
415 case R.styleable.BatteryHistoryChart_android_shadowDx:
416 dx = a.getFloat(attr, 0);
419 case R.styleable.BatteryHistoryChart_android_shadowDy:
420 dy = a.getFloat(attr, 0);
423 case R.styleable.BatteryHistoryChart_android_shadowRadius:
424 r = a.getFloat(attr, 0);
427 case R.styleable.BatteryHistoryChart_android_textColor:
428 mainTextAttrs.textColor = a.getColorStateList(attr);
429 headTextAttrs.textColor = a.getColorStateList(attr);
432 case R.styleable.BatteryHistoryChart_android_textSize:
433 mainTextAttrs.textSize = a.getDimensionPixelSize(attr, mainTextAttrs.textSize);
434 headTextAttrs.textSize = a.getDimensionPixelSize(attr, headTextAttrs.textSize);
437 case R.styleable.BatteryHistoryChart_android_typeface:
438 mainTextAttrs.typefaceIndex = a.getInt(attr, mainTextAttrs.typefaceIndex);
439 headTextAttrs.typefaceIndex = a.getInt(attr, headTextAttrs.typefaceIndex);
442 case R.styleable.BatteryHistoryChart_android_textStyle:
443 mainTextAttrs.styleIndex = a.getInt(attr, mainTextAttrs.styleIndex);
444 headTextAttrs.styleIndex = a.getInt(attr, headTextAttrs.styleIndex);
447 case R.styleable.BatteryHistoryChart_barPrimaryColor:
448 mBatteryBackgroundPaint.setColor(a.getInt(attr, 0));
449 mScreenOnPaint.setColor(a.getInt(attr, 0));
450 mGpsOnPaint.setColor(a.getInt(attr, 0));
451 mCameraOnPaint.setColor(a.getInt(attr, 0));
452 mFlashlightOnPaint.setColor(a.getInt(attr, 0));
453 mWifiRunningPaint.setColor(a.getInt(attr, 0));
454 mCpuRunningPaint.setColor(a.getInt(attr, 0));
455 mChargingPaint.setColor(a.getInt(attr, 0));
458 case R.styleable.BatteryHistoryChart_barPredictionColor:
459 mTimeRemainPaint.setColor(a.getInt(attr, 0));
462 case R.styleable.BatteryHistoryChart_chartMinHeight:
463 mChartMinHeight = a.getDimensionPixelSize(attr, 0);
470 mainTextAttrs.apply(context, mTextPaint);
471 headTextAttrs.apply(context, mHeaderTextPaint);
473 mDateLinePaint.set(mTextPaint);
474 mDateLinePaint.setStyle(Paint.Style.STROKE);
475 int hairlineWidth = mThinLineWidth/2;
476 if (hairlineWidth < 1) {
479 mDateLinePaint.setStrokeWidth(hairlineWidth);
480 mDateLinePaint.setPathEffect(new DashPathEffect(new float[] {
481 mThinLineWidth * 2, mThinLineWidth * 2 }, 0));
483 if (shadowcolor != 0) {
484 mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
485 mHeaderTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
489 void setStats(BatteryStats stats, Intent broadcast) {
491 mBatteryBroadcast = broadcast;
493 if (DEBUG) Log.d(TAG, "Setting stats...");
495 final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
497 long uSecTime = mStats.computeBatteryRealtime(elapsedRealtimeUs,
498 BatteryStats.STATS_SINCE_CHARGED);
499 mStatsPeriod = uSecTime;
500 mChargingLabel = getContext().getString(R.string.battery_stats_charging_label);
501 mScreenOnLabel = getContext().getString(R.string.battery_stats_screen_on_label);
502 mGpsOnLabel = getContext().getString(R.string.battery_stats_gps_on_label);
503 mCameraOnLabel = getContext().getString(R.string.battery_stats_camera_on_label);
504 mFlashlightOnLabel = getContext().getString(R.string.battery_stats_flashlight_on_label);
505 mWifiRunningLabel = getContext().getString(R.string.battery_stats_wifi_running_label);
506 mCpuRunningLabel = getContext().getString(R.string.battery_stats_wake_lock_label);
507 mPhoneSignalLabel = getContext().getString(R.string.battery_stats_phone_signal_label);
509 mMaxPercentLabelString = Utils.formatPercentage(100);
510 mMinPercentLabelString = Utils.formatPercentage(0);
511 BatteryInfo.getBatteryInfo(getContext(), info -> {
514 mChargeDurationString = "";
515 setContentDescription(mInfo.chargeLabel);
518 int lastInteresting = 0;
523 mEndDataWallTime = 0;
527 long lastWallTime = 0;
528 long lastRealtime = 0;
531 boolean first = true;
532 if (stats.startIteratingHistoryLocked()) {
533 final HistoryItem rec = new HistoryItem();
534 while (stats.getNextHistoryLocked(rec)) {
538 mHistStart = rec.time;
540 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
541 || rec.cmd == HistoryItem.CMD_RESET) {
542 // If there is a ridiculously large jump in time, then we won't be
543 // able to create a good chart with that data, so just ignore the
544 // times we got before and pretend like our data extends back from
545 // the time we have now.
546 // Also, if we are getting a time change and we are less than 5 minutes
547 // since the start of the history real time, then also use this new
548 // time to compute the base time, since whatever time we had before is
549 // pretty much just noise.
550 if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L))
551 || rec.time < (mHistStart+(5*60*1000L))) {
554 lastWallTime = rec.currentTime;
555 lastRealtime = rec.time;
556 if (mStartWallTime == 0) {
557 mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
560 if (rec.isDeltaData()) {
561 if (rec.batteryLevel != lastLevel || pos == 1) {
562 lastLevel = rec.batteryLevel;
564 lastInteresting = pos;
565 mHistDataEnd = rec.time;
566 aggrStates |= rec.states;
567 aggrStates2 |= rec.states2;
571 mHistEnd = mHistDataEnd + (mInfo.remainingTimeUs/1000);
572 mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
573 mEndWallTime = mEndDataWallTime + (mInfo.remainingTimeUs/1000);
574 mNumHist = lastInteresting;
575 mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
576 mHaveFlashlight = (aggrStates2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
577 mHaveCamera = (aggrStates2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
578 mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
579 || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
580 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
581 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
582 if (!com.android.settingslib.Utils.isWifiOnly(getContext())) {
583 mHavePhoneSignal = true;
585 if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
586 }, mStats, false /* shortString */);
590 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
591 mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString);
592 mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString);
593 mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString);
594 mChargeLabelStringWidth = (int) mHeaderTextPaint.measureText(
595 mInfo.chargeLabel.toString());
596 mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString);
597 mTextAscent = (int)mTextPaint.ascent();
598 mTextDescent = (int)mTextPaint.descent();
599 mHeaderTextAscent = (int)mHeaderTextPaint.ascent();
600 mHeaderTextDescent = (int)mHeaderTextPaint.descent();
601 int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent;
602 mHeaderHeight = headerTextHeight*2 - mTextAscent;
603 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
604 getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec));
607 void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
608 int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
609 boolean lastFlashlightOn, boolean lastCameraOn, boolean lastWifiRunning,
610 boolean lastCpuRunning, Path lastPath) {
611 if (curLevelPath != null) {
612 if (lastX >= 0 && lastX < w) {
613 if (lastPath != null) {
614 lastPath.lineTo(w, y);
616 curLevelPath.lineTo(w, y);
618 curLevelPath.lineTo(w, mLevelTop+levelh);
619 curLevelPath.lineTo(startX, mLevelTop+levelh);
620 curLevelPath.close();
624 mChargingPath.lineTo(w, h-mChargingOffset);
627 mScreenOnPath.lineTo(w, h-mScreenOnOffset);
630 mGpsOnPath.lineTo(w, h-mGpsOnOffset);
632 if (lastFlashlightOn) {
633 mFlashlightOnPath.lineTo(w, h-mFlashlightOnOffset);
636 mCameraOnPath.lineTo(w, h-mCameraOnOffset);
638 if (lastWifiRunning) {
639 mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
641 if (lastCpuRunning) {
642 mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
644 if (mHavePhoneSignal) {
645 mPhoneSignalChart.finish(w);
649 private boolean is24Hour() {
650 return DateFormat.is24HourFormat(getContext());
653 private boolean isDayFirst() {
654 final String value = LocaleData.get(getResources().getConfiguration().locale)
655 .getDateFormat(java.text.DateFormat.SHORT);
656 return value.indexOf('M') > value.indexOf('d');
660 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
661 super.onSizeChanged(w, h, oldw, oldh);
663 if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
665 if (mLastWidth == w && mLastHeight == h) {
669 if (mLastWidth == 0 || mLastHeight == 0) {
673 if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
680 int textHeight = mTextDescent - mTextAscent;
681 if (h > ((textHeight*10)+mChartMinHeight)) {
683 if (h > (textHeight*15)) {
684 // Plenty of room for the chart.
685 mLineWidth = textHeight/2;
687 // Compress lines to make more room for chart.
688 mLineWidth = textHeight/3;
692 mLineWidth = mThinLineWidth;
694 if (mLineWidth <= 0) mLineWidth = 1;
696 mLevelTop = mHeaderHeight;
697 mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
699 int levelWidth = mLevelRight-mLevelLeft;
701 mTextPaint.setStrokeWidth(mThinLineWidth);
702 mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
703 mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
704 mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth);
705 mChargingPaint.setStrokeWidth(mLineWidth);
706 mScreenOnPaint.setStrokeWidth(mLineWidth);
707 mGpsOnPaint.setStrokeWidth(mLineWidth);
708 mCameraOnPaint.setStrokeWidth(mLineWidth);
709 mFlashlightOnPaint.setStrokeWidth(mLineWidth);
710 mWifiRunningPaint.setStrokeWidth(mLineWidth);
711 mCpuRunningPaint.setStrokeWidth(mLineWidth);
712 mDebugRectPaint.setStrokeWidth(1);
714 int fullBarOffset = textHeight + mLineWidth;
717 mChargingOffset = mLineWidth;
718 mScreenOnOffset = mChargingOffset + fullBarOffset;
719 mCpuRunningOffset = mScreenOnOffset + fullBarOffset;
720 mWifiRunningOffset = mCpuRunningOffset + fullBarOffset;
721 mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0);
722 mFlashlightOnOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0);
723 mCameraOnOffset = mFlashlightOnOffset + (mHaveFlashlight ? fullBarOffset : 0);
724 mPhoneSignalOffset = mCameraOnOffset + (mHaveCamera ? fullBarOffset : 0);
725 mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0)
726 + mLineWidth*2 + mLineWidth/2;
727 if (mHavePhoneSignal) {
728 mPhoneSignalChart.init(w);
731 mScreenOnOffset = mGpsOnOffset = mCameraOnOffset = mFlashlightOnOffset =
732 mWifiRunningOffset = mCpuRunningOffset = mChargingOffset =
733 mPhoneSignalOffset = 0;
734 mLevelOffset = fullBarOffset + mThinLineWidth*4;
735 if (mHavePhoneSignal) {
736 mPhoneSignalChart.init(0);
740 mBatLevelPath.reset();
741 mBatGoodPath.reset();
742 mBatWarnPath.reset();
743 mTimeRemainPath.reset();
744 mBatCriticalPath.reset();
745 mScreenOnPath.reset();
747 mFlashlightOnPath.reset();
748 mCameraOnPath.reset();
749 mWifiRunningPath.reset();
750 mCpuRunningPath.reset();
751 mChargingPath.reset();
756 final long walltimeStart = mStartWallTime;
757 final long walltimeChange = mEndWallTime > walltimeStart
758 ? (mEndWallTime-walltimeStart) : 1;
759 long curWalltime = mStartWallTime;
760 long lastRealtime = 0;
762 final int batLow = mBatLow;
763 final int batChange = mBatHigh-mBatLow;
765 final int levelh = h - mLevelOffset - mLevelTop;
766 mLevelBottom = mLevelTop + levelh;
768 int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
770 Path curLevelPath = null;
771 Path lastLinePath = null;
772 boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
773 boolean lastFlashlightOn = false, lastCameraOn = false;
774 boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false;
775 int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
776 final int N = mNumHist;
777 if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) {
778 final HistoryItem rec = new HistoryItem();
779 while (mStats.getNextHistoryLocked(rec) && i < N) {
780 if (rec.isDeltaData()) {
781 curWalltime += rec.time-lastRealtime;
782 lastRealtime = rec.time;
783 x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange);
788 StringBuilder sb = new StringBuilder(128);
789 sb.append("walloff=");
790 TimeUtils.formatDuration(curWalltime - walltimeStart, sb);
791 sb.append(" wallchange=");
792 TimeUtils.formatDuration(walltimeChange, sb);
795 Log.d("foo", sb.toString());
797 y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
800 // We have moved by at least a pixel.
802 // Don't plot changes within a pixel.
804 byte value = rec.batteryLevel;
805 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
806 else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
807 else path = null; //mBatGoodPath;
809 if (path != lastLinePath) {
810 if (lastLinePath != null) {
811 lastLinePath.lineTo(x, y);
817 } else if (path != null) {
821 if (curLevelPath == null) {
822 curLevelPath = mBatLevelPath;
823 curLevelPath.moveTo(x, y);
826 curLevelPath.lineTo(x, y);
834 final boolean charging =
835 (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
836 if (charging != lastCharging) {
838 mChargingPath.moveTo(x, h-mChargingOffset);
840 mChargingPath.lineTo(x, h-mChargingOffset);
842 lastCharging = charging;
845 final boolean screenOn =
846 (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
847 if (screenOn != lastScreenOn) {
849 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
851 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
853 lastScreenOn = screenOn;
856 final boolean gpsOn =
857 (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
858 if (gpsOn != lastGpsOn) {
860 mGpsOnPath.moveTo(x, h-mGpsOnOffset);
862 mGpsOnPath.lineTo(x, h-mGpsOnOffset);
867 final boolean flashlightOn =
868 (rec.states2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
869 if (flashlightOn != lastFlashlightOn) {
871 mFlashlightOnPath.moveTo(x, h-mFlashlightOnOffset);
873 mFlashlightOnPath.lineTo(x, h-mFlashlightOnOffset);
875 lastFlashlightOn = flashlightOn;
878 final boolean cameraOn =
879 (rec.states2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
880 if (cameraOn != lastCameraOn) {
882 mCameraOnPath.moveTo(x, h-mCameraOnOffset);
884 mCameraOnPath.lineTo(x, h-mCameraOnOffset);
886 lastCameraOn = cameraOn;
889 final int wifiSupplState =
890 ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
891 >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
893 if (lastWifiSupplState != wifiSupplState) {
894 lastWifiSupplState = wifiSupplState;
895 switch (wifiSupplState) {
896 case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED:
897 case BatteryStats.WIFI_SUPPL_STATE_DORMANT:
898 case BatteryStats.WIFI_SUPPL_STATE_INACTIVE:
899 case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
900 case BatteryStats.WIFI_SUPPL_STATE_INVALID:
901 case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED:
902 wifiRunning = lastWifiSupplRunning = false;
905 wifiRunning = lastWifiSupplRunning = true;
909 wifiRunning = lastWifiSupplRunning;
911 if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
912 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
913 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
916 if (wifiRunning != lastWifiRunning) {
918 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
920 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
922 lastWifiRunning = wifiRunning;
925 final boolean cpuRunning =
926 (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
927 if (cpuRunning != lastCpuRunning) {
929 mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
931 mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
933 lastCpuRunning = cpuRunning;
936 if (mLargeMode && mHavePhoneSignal) {
938 if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
939 >> HistoryItem.STATE_PHONE_STATE_SHIFT)
940 == ServiceState.STATE_POWER_OFF) {
942 } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
945 bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
946 >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
949 mPhoneSignalChart.addTick(x, bin);
954 long lastWalltime = curWalltime;
955 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
956 || rec.cmd == HistoryItem.CMD_RESET) {
957 if (rec.currentTime >= mStartWallTime) {
958 curWalltime = rec.currentTime;
960 curWalltime = mStartWallTime + (rec.time-mHistStart);
962 lastRealtime = rec.time;
965 if (rec.cmd != HistoryItem.CMD_OVERFLOW
966 && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
967 || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) {
968 if (curLevelPath != null) {
969 finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
970 lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn,
971 lastCameraOn, lastWifiRunning, lastCpuRunning, lastLinePath);
975 lastCharging = lastScreenOn = lastGpsOn = lastFlashlightOn =
976 lastCameraOn = lastCpuRunning = false;
983 mStats.finishIteratingHistoryLocked();
986 if (lastY < 0 || lastX < 0) {
987 // Didn't get any data...
988 x = lastX = mLevelLeft;
989 y = lastY = mLevelTop + levelh - ((mInfo.batteryLevel -batLow)*(levelh-1))/batChange;
991 byte value = (byte)mInfo.batteryLevel;
992 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
993 else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
994 else path = null; //mBatGoodPath;
999 mBatLevelPath.moveTo(x, y);
1000 curLevelPath = mBatLevelPath;
1003 // Figure out where the actual data ends on the screen.
1004 x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
1010 finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
1011 lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn, lastCameraOn,
1012 lastWifiRunning, lastCpuRunning, lastLinePath);
1015 // If we reserved room for the remaining time, create a final path to draw
1016 // that part of the UI.
1017 mTimeRemainPath.moveTo(x, lastY);
1018 int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange;
1019 int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange;
1020 if (mInfo.discharging) {
1021 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1023 mTimeRemainPath.lineTo(mLevelRight, fullY);
1024 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1026 mTimeRemainPath.lineTo(x, emptyY);
1027 mTimeRemainPath.close();
1030 if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) {
1031 // Create the time labels at the bottom.
1032 boolean is24hr = is24Hour();
1033 Calendar calStart = Calendar.getInstance();
1034 calStart.setTimeInMillis(mStartWallTime);
1035 calStart.set(Calendar.MILLISECOND, 0);
1036 calStart.set(Calendar.SECOND, 0);
1037 calStart.set(Calendar.MINUTE, 0);
1038 long startRoundTime = calStart.getTimeInMillis();
1039 if (startRoundTime < mStartWallTime) {
1040 calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1);
1041 startRoundTime = calStart.getTimeInMillis();
1043 Calendar calEnd = Calendar.getInstance();
1044 calEnd.setTimeInMillis(mEndWallTime);
1045 calEnd.set(Calendar.MILLISECOND, 0);
1046 calEnd.set(Calendar.SECOND, 0);
1047 calEnd.set(Calendar.MINUTE, 0);
1048 long endRoundTime = calEnd.getTimeInMillis();
1049 if (startRoundTime < endRoundTime) {
1050 addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr);
1051 Calendar calMid = Calendar.getInstance();
1052 calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2));
1053 calMid.set(Calendar.MILLISECOND, 0);
1054 calMid.set(Calendar.SECOND, 0);
1055 calMid.set(Calendar.MINUTE, 0);
1056 long calMidMillis = calMid.getTimeInMillis();
1057 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1058 addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr);
1060 addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
1063 // Create the date labels if the chart includes multiple days
1064 if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) ||
1065 calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) {
1066 boolean isDayFirst = isDayFirst();
1067 calStart.set(Calendar.HOUR_OF_DAY, 0);
1068 startRoundTime = calStart.getTimeInMillis();
1069 if (startRoundTime < mStartWallTime) {
1070 calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1);
1071 startRoundTime = calStart.getTimeInMillis();
1073 calEnd.set(Calendar.HOUR_OF_DAY, 0);
1074 endRoundTime = calEnd.getTimeInMillis();
1075 if (startRoundTime < endRoundTime) {
1076 addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst);
1077 Calendar calMid = Calendar.getInstance();
1079 // The middle between two beginnings of days can be anywhere between -1 to 13
1080 // after the beginning of the "median" day.
1081 calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2)
1082 + 2 * 60 * 60 * 1000);
1083 calMid.set(Calendar.HOUR_OF_DAY, 0);
1084 calMid.set(Calendar.MINUTE, 0);
1085 long calMidMillis = calMid.getTimeInMillis();
1086 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1087 addDateLabel(calMid, mLevelLeft, mLevelRight, isDayFirst);
1090 addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
1094 if (mTimeLabels.size() < 2) {
1095 // If there are fewer than 2 time labels, then they are useless. Just
1096 // show an axis label giving the entire duration.
1097 mDurationString = Formatter.formatShortElapsedTime(getContext(),
1098 mEndWallTime - mStartWallTime);
1099 mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
1101 mDurationString = null;
1102 mDurationStringWidth = 0;
1106 void addTimeLabel(Calendar cal, int levelLeft, int levelRight, boolean is24hr) {
1107 final long walltimeStart = mStartWallTime;
1108 final long walltimeChange = mEndWallTime-walltimeStart;
1109 mTimeLabels.add(new TimeLabel(mTextPaint,
1110 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1115 void addDateLabel(Calendar cal, int levelLeft, int levelRight, boolean isDayFirst) {
1116 final long walltimeStart = mStartWallTime;
1117 final long walltimeChange = mEndWallTime-walltimeStart;
1118 mDateLabels.add(new DateLabel(mTextPaint,
1119 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1125 protected void onDraw(Canvas canvas) {
1126 super.onDraw(canvas);
1128 final int width = getWidth();
1129 final int height = getHeight();
1131 //buildBitmap(width, height);
1133 if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
1134 //canvas.drawBitmap(mBitmap, 0, 0, null);
1135 drawChart(canvas, width, height);
1138 void buildBitmap(int width, int height) {
1139 if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
1143 if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
1145 mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
1146 Bitmap.Config.ARGB_8888);
1147 mCanvas = new Canvas(mBitmap);
1148 drawChart(mCanvas, width, height);
1151 void drawChart(Canvas canvas, int width, int height) {
1152 final boolean layoutRtl = isLayoutRtl();
1153 final int textStartX = layoutRtl ? width : 0;
1154 final int textEndX = layoutRtl ? 0 : width;
1155 final Paint.Align textAlignLeft = layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT;
1156 final Paint.Align textAlignRight = layoutRtl ? Paint.Align.LEFT : Paint.Align.RIGHT;
1159 canvas.drawRect(1, 1, width, height, mDebugRectPaint);
1162 if (DEBUG) Log.d(TAG, "Drawing level path.");
1163 canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
1164 if (!mTimeRemainPath.isEmpty()) {
1165 if (DEBUG) Log.d(TAG, "Drawing time remain path.");
1166 canvas.drawPath(mTimeRemainPath, mTimeRemainPaint);
1168 if (mTimeLabels.size() > 1) {
1169 int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1170 int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2);
1171 mTextPaint.setTextAlign(Paint.Align.LEFT);
1173 for (int i=0; i<mTimeLabels.size(); i++) {
1174 TimeLabel label = mTimeLabels.get(i);
1176 int x = label.x - label.width/2;
1180 if (DEBUG) Log.d(TAG, "Drawing left label: " + label.label + " @ " + x);
1181 canvas.drawText(label.label, x, y, mTextPaint);
1182 canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1183 lastX = x + label.width;
1184 } else if (i < (mTimeLabels.size()-1)) {
1185 int x = label.x - label.width/2;
1186 if (x < (lastX+mTextAscent)) {
1189 TimeLabel nextLabel = mTimeLabels.get(i+1);
1190 if (x > (width-nextLabel.width-mTextAscent)) {
1193 if (DEBUG) Log.d(TAG, "Drawing middle label: " + label.label + " @ " + x);
1194 canvas.drawText(label.label, x, y, mTextPaint);
1195 canvas.drawLine(label.x, ytick, label.x, ytick + mThinLineWidth, mTextPaint);
1196 lastX = x + label.width;
1198 int x = label.x - label.width/2;
1199 if ((x+label.width) >= width) {
1200 x = width-1-label.width;
1202 if (DEBUG) Log.d(TAG, "Drawing right label: " + label.label + " @ " + x);
1203 canvas.drawText(label.label, x, y, mTextPaint);
1204 canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1207 } else if (mDurationString != null) {
1208 int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1209 mTextPaint.setTextAlign(Paint.Align.LEFT);
1210 canvas.drawText(mDurationString,
1211 mLevelLeft + (mLevelRight-mLevelLeft)/2 - mDurationStringWidth/2,
1215 int headerTop = -mHeaderTextAscent + (mHeaderTextDescent-mHeaderTextAscent)/3;
1216 mHeaderTextPaint.setTextAlign(textAlignLeft);
1217 if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mInfo.chargeLabel);
1218 canvas.drawText(mInfo.chargeLabel.toString(), textStartX, headerTop,
1220 int stringHalfWidth = mChargeDurationStringWidth / 2;
1221 if (layoutRtl) stringHalfWidth = -stringHalfWidth;
1222 int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2)
1223 + (layoutRtl ? mDrainStringWidth : mChargeLabelStringWidth);
1224 if (DEBUG) Log.d(TAG, "Drawing charge duration string: " + mChargeDurationString);
1225 canvas.drawText(mChargeDurationString, headerCenter - stringHalfWidth, headerTop,
1227 mHeaderTextPaint.setTextAlign(textAlignRight);
1228 if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString);
1229 canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint);
1231 if (!mBatGoodPath.isEmpty()) {
1232 if (DEBUG) Log.d(TAG, "Drawing good battery path");
1233 canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
1235 if (!mBatWarnPath.isEmpty()) {
1236 if (DEBUG) Log.d(TAG, "Drawing warn battery path");
1237 canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
1239 if (!mBatCriticalPath.isEmpty()) {
1240 if (DEBUG) Log.d(TAG, "Drawing critical battery path");
1241 canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
1243 if (mHavePhoneSignal) {
1244 if (DEBUG) Log.d(TAG, "Drawing phone signal path");
1245 int top = height-mPhoneSignalOffset - (mLineWidth/2);
1246 mPhoneSignalChart.draw(canvas, top, mLineWidth);
1248 if (!mScreenOnPath.isEmpty()) {
1249 if (DEBUG) Log.d(TAG, "Drawing screen on path");
1250 canvas.drawPath(mScreenOnPath, mScreenOnPaint);
1252 if (!mChargingPath.isEmpty()) {
1253 if (DEBUG) Log.d(TAG, "Drawing charging path");
1254 canvas.drawPath(mChargingPath, mChargingPaint);
1257 if (!mGpsOnPath.isEmpty()) {
1258 if (DEBUG) Log.d(TAG, "Drawing gps path");
1259 canvas.drawPath(mGpsOnPath, mGpsOnPaint);
1262 if (mHaveFlashlight) {
1263 if (!mFlashlightOnPath.isEmpty()) {
1264 if (DEBUG) Log.d(TAG, "Drawing flashlight path");
1265 canvas.drawPath(mFlashlightOnPath, mFlashlightOnPaint);
1269 if (!mCameraOnPath.isEmpty()) {
1270 if (DEBUG) Log.d(TAG, "Drawing camera path");
1271 canvas.drawPath(mCameraOnPath, mCameraOnPaint);
1275 if (!mWifiRunningPath.isEmpty()) {
1276 if (DEBUG) Log.d(TAG, "Drawing wifi path");
1277 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
1280 if (!mCpuRunningPath.isEmpty()) {
1281 if (DEBUG) Log.d(TAG, "Drawing running path");
1282 canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
1286 if (DEBUG) Log.d(TAG, "Drawing large mode labels");
1287 Paint.Align align = mTextPaint.getTextAlign();
1288 mTextPaint.setTextAlign(textAlignLeft); // large-mode labels always aligned to start
1289 if (mHavePhoneSignal) {
1290 canvas.drawText(mPhoneSignalLabel, textStartX,
1291 height - mPhoneSignalOffset - mTextDescent, mTextPaint);
1294 canvas.drawText(mGpsOnLabel, textStartX,
1295 height - mGpsOnOffset - mTextDescent, mTextPaint);
1297 if (mHaveFlashlight) {
1298 canvas.drawText(mFlashlightOnLabel, textStartX,
1299 height - mFlashlightOnOffset - mTextDescent, mTextPaint);
1302 canvas.drawText(mCameraOnLabel, textStartX,
1303 height - mCameraOnOffset - mTextDescent, mTextPaint);
1306 canvas.drawText(mWifiRunningLabel, textStartX,
1307 height - mWifiRunningOffset - mTextDescent, mTextPaint);
1309 canvas.drawText(mCpuRunningLabel, textStartX,
1310 height - mCpuRunningOffset - mTextDescent, mTextPaint);
1311 canvas.drawText(mChargingLabel, textStartX,
1312 height - mChargingOffset - mTextDescent, mTextPaint);
1313 canvas.drawText(mScreenOnLabel, textStartX,
1314 height - mScreenOnOffset - mTextDescent, mTextPaint);
1315 mTextPaint.setTextAlign(align);
1318 canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
1319 mLevelBottom+(mThinLineWidth/2), mTextPaint);
1321 for (int i=0; i<10; i++) {
1322 int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10;
1323 canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y,
1324 mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint);
1327 if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth
1328 + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString));
1329 canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint);
1330 canvas.drawText(mMinPercentLabelString,
1331 mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth,
1332 mLevelBottom - mThinLineWidth, mTextPaint);
1333 canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width,
1334 mLevelBottom+mThinLineWidth, mTextPaint);
1336 if (mDateLabels.size() > 0) {
1337 int ytop = mLevelTop + mTextAscent;
1338 int ybottom = mLevelBottom;
1339 int lastLeft = mLevelRight;
1340 mTextPaint.setTextAlign(Paint.Align.LEFT);
1341 for (int i=mDateLabels.size()-1; i>=0; i--) {
1342 DateLabel label = mDateLabels.get(i);
1343 int left = label.x - mThinLineWidth;
1344 int x = label.x + mThinLineWidth*2;
1345 if ((x+label.width) >= lastLeft) {
1346 x = label.x - mThinLineWidth*2 - label.width;
1347 left = x - mThinLineWidth;
1348 if (left >= lastLeft) {
1353 if (left < mLevelLeft) {
1354 // Won't fit on left, give up.
1357 mDateLinePath.reset();
1358 mDateLinePath.moveTo(label.x, ytop);
1359 mDateLinePath.lineTo(label.x, ybottom);
1360 canvas.drawPath(mDateLinePath, mDateLinePaint);
1361 canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint);