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.Intent;
20 import android.graphics.Bitmap;
21 import android.graphics.DashPathEffect;
22 import android.os.BatteryManager;
23 import android.provider.Settings;
24 import android.text.format.DateFormat;
25 import android.text.format.Formatter;
26 import android.util.Log;
27 import android.util.TimeUtils;
28 import com.android.settings.R;
29 import com.android.settings.Utils;
31 import android.content.Context;
32 import android.content.res.ColorStateList;
33 import android.content.res.TypedArray;
34 import android.graphics.Canvas;
35 import android.graphics.Paint;
36 import android.graphics.Path;
37 import android.graphics.Typeface;
38 import android.os.BatteryStats;
39 import android.os.SystemClock;
40 import android.os.BatteryStats.HistoryItem;
41 import android.telephony.ServiceState;
42 import android.text.TextPaint;
43 import android.util.AttributeSet;
44 import android.util.TypedValue;
45 import android.view.View;
46 import libcore.icu.LocaleData;
48 import java.util.ArrayList;
49 import java.util.Calendar;
50 import java.util.Locale;
52 public class BatteryHistoryChart extends View {
53 static final boolean DEBUG = false;
54 static final String TAG = "BatteryHistoryChart";
56 static final int CHART_DATA_X_MASK = 0x0000ffff;
57 static final int CHART_DATA_BIN_MASK = 0xffff0000;
58 static final int CHART_DATA_BIN_SHIFT = 16;
60 static class ChartData {
68 void setColors(int[] colors) {
70 mPaints = new Paint[colors.length];
71 for (int i=0; i<colors.length; i++) {
72 mPaints[i] = new Paint();
73 mPaints[i].setColor(colors[i]);
74 mPaints[i].setStyle(Paint.Style.FILL);
78 void init(int width) {
80 mTicks = new int[width*2];
88 void addTick(int x, int bin) {
89 if (bin != mLastBin && mNumTicks < mTicks.length) {
90 mTicks[mNumTicks] = (x&CHART_DATA_X_MASK) | (bin<<CHART_DATA_BIN_SHIFT);
96 void finish(int width) {
102 void draw(Canvas canvas, int top, int height) {
103 int lastBin=0, lastX=0;
104 int bottom = top + height;
105 for (int i=0; i<mNumTicks; i++) {
106 int tick = mTicks[i];
107 int x = tick&CHART_DATA_X_MASK;
108 int bin = (tick&CHART_DATA_BIN_MASK) >> CHART_DATA_BIN_SHIFT;
110 canvas.drawRect(lastX, top, x, bottom, mPaints[lastBin]);
119 static final int SANS = 1;
120 static final int SERIF = 2;
121 static final int MONOSPACE = 3;
123 // First value if for phone off; first value is "scanning"; following values
124 // are battery stats signal strength buckets.
125 static final int NUM_PHONE_SIGNALS = 7;
127 final Paint mBatteryBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
128 final Paint mBatteryGoodPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
129 final Paint mBatteryWarnPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
130 final Paint mBatteryCriticalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
131 final Paint mTimeRemainPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
132 final Paint mChargingPaint = new Paint();
133 final Paint mScreenOnPaint = new Paint();
134 final Paint mGpsOnPaint = 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 mWifiRunningPath = new Path();
152 final Path mCpuRunningPath = new Path();
153 final Path mDateLinePath = new Path();
156 Intent mBatteryBroadcast;
159 String mMaxPercentLabelString;
160 String mMinPercentLabelString;
161 String mDurationString;
162 String mChargeLabelString;
163 String mChargeDurationString;
165 String mChargingLabel;
166 String mScreenOnLabel;
168 String mWifiRunningLabel;
169 String mCpuRunningLabel;
170 String mPhoneSignalLabel;
175 int mBatteryWarnLevel;
176 int mBatteryCriticalLevel;
180 int mHeaderTextAscent;
181 int mHeaderTextDescent;
182 int mMaxPercentLabelStringWidth;
183 int mMinPercentLabelStringWidth;
184 int mDurationStringWidth;
185 int mChargeLabelStringWidth;
186 int mChargeDurationStringWidth;
187 int mDrainStringWidth;
192 int mLastHeight = -1;
199 int mWifiRunningOffset;
200 int mCpuRunningOffset;
201 int mPhoneSignalOffset;
213 long mEndDataWallTime;
215 boolean mDischarging;
220 boolean mHavePhoneSignal;
222 final ArrayList<TimeLabel> mTimeLabels = new ArrayList<TimeLabel>();
223 final ArrayList<DateLabel> mDateLabels = new ArrayList<DateLabel>();
228 static class TextAttrs {
229 ColorStateList textColor = null;
231 int typefaceIndex = -1;
234 void retrieve(Context context, TypedArray from, int index) {
235 TypedArray appearance = null;
236 int ap = from.getResourceId(index, -1);
238 appearance = context.obtainStyledAttributes(ap,
239 com.android.internal.R.styleable.TextAppearance);
241 if (appearance != null) {
242 int n = appearance.getIndexCount();
243 for (int i = 0; i < n; i++) {
244 int attr = appearance.getIndex(i);
247 case com.android.internal.R.styleable.TextAppearance_textColor:
248 textColor = appearance.getColorStateList(attr);
251 case com.android.internal.R.styleable.TextAppearance_textSize:
252 textSize = appearance.getDimensionPixelSize(attr, textSize);
255 case com.android.internal.R.styleable.TextAppearance_typeface:
256 typefaceIndex = appearance.getInt(attr, -1);
259 case com.android.internal.R.styleable.TextAppearance_textStyle:
260 styleIndex = appearance.getInt(attr, -1);
265 appearance.recycle();
269 void apply(Context context, TextPaint paint) {
270 paint.density = context.getResources().getDisplayMetrics().density;
271 paint.setCompatibilityScaling(
272 context.getResources().getCompatibilityInfo().applicationScale);
274 paint.setColor(textColor.getDefaultColor());
275 paint.setTextSize(textSize);
278 switch (typefaceIndex) {
280 tf = Typeface.SANS_SERIF;
288 tf = Typeface.MONOSPACE;
292 setTypeface(paint, tf, styleIndex);
295 public void setTypeface(TextPaint paint, Typeface tf, int style) {
298 tf = Typeface.defaultFromStyle(style);
300 tf = Typeface.create(tf, style);
303 paint.setTypeface(tf);
304 // now compute what (if any) algorithmic styling is needed
305 int typefaceStyle = tf != null ? tf.getStyle() : 0;
306 int need = style & ~typefaceStyle;
307 paint.setFakeBoldText((need & Typeface.BOLD) != 0);
308 paint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
310 paint.setFakeBoldText(false);
311 paint.setTextSkewX(0);
312 paint.setTypeface(tf);
317 static class TimeLabel {
322 TimeLabel(TextPaint paint, int x, Calendar cal, boolean use24hr) {
324 final String bestFormat = DateFormat.getBestDateTimePattern(
325 Locale.getDefault(), use24hr ? "km" : "ha");
326 label = DateFormat.format(bestFormat, cal).toString();
327 width = (int)paint.measureText(label);
331 static class DateLabel {
336 DateLabel(TextPaint paint, int x, Calendar cal, boolean dayFirst) {
338 final String bestFormat = DateFormat.getBestDateTimePattern(
339 Locale.getDefault(), dayFirst ? "dM" : "Md");
340 label = DateFormat.format(bestFormat, cal).toString();
341 width = (int)paint.measureText(label);
345 public BatteryHistoryChart(Context context, AttributeSet attrs) {
346 super(context, attrs);
348 if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!");
350 mBatteryWarnLevel = mContext.getResources().getInteger(
351 com.android.internal.R.integer.config_lowBatteryWarningLevel);
352 mBatteryCriticalLevel = mContext.getResources().getInteger(
353 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
355 mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
356 2, getResources().getDisplayMetrics());
358 mBatteryBackgroundPaint.setColor(0xFF009688);
359 mBatteryBackgroundPaint.setStyle(Paint.Style.FILL);
360 mBatteryGoodPaint.setARGB(128, 0, 128, 0);
361 mBatteryGoodPaint.setStyle(Paint.Style.STROKE);
362 mBatteryWarnPaint.setARGB(128, 128, 128, 0);
363 mBatteryWarnPaint.setStyle(Paint.Style.STROKE);
364 mBatteryCriticalPaint.setARGB(192, 128, 0, 0);
365 mBatteryCriticalPaint.setStyle(Paint.Style.STROKE);
366 mTimeRemainPaint.setColor(0xFFCED7BB);
367 mTimeRemainPaint.setStyle(Paint.Style.FILL);
368 mChargingPaint.setStyle(Paint.Style.STROKE);
369 mScreenOnPaint.setStyle(Paint.Style.STROKE);
370 mGpsOnPaint.setStyle(Paint.Style.STROKE);
371 mWifiRunningPaint.setStyle(Paint.Style.STROKE);
372 mCpuRunningPaint.setStyle(Paint.Style.STROKE);
373 mPhoneSignalChart.setColors(com.android.settings.Utils.BADNESS_COLORS);
374 mDebugRectPaint.setARGB(255, 255, 0, 0);
375 mDebugRectPaint.setStyle(Paint.Style.STROKE);
376 mScreenOnPaint.setColor(0xFF009688);
377 mGpsOnPaint.setColor(0xFF009688);
378 mWifiRunningPaint.setColor(0xFF009688);
379 mCpuRunningPaint.setColor(0xFF009688);
380 mChargingPaint.setColor(0xFF009688);
383 context.obtainStyledAttributes(
384 attrs, R.styleable.BatteryHistoryChart, 0, 0);
386 final TextAttrs mainTextAttrs = new TextAttrs();
387 final TextAttrs headTextAttrs = new TextAttrs();
388 mainTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_android_textAppearance);
389 headTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_headerAppearance);
392 float dx=0, dy=0, r=0;
394 int n = a.getIndexCount();
395 for (int i = 0; i < n; i++) {
396 int attr = a.getIndex(i);
399 case R.styleable.BatteryHistoryChart_android_shadowColor:
400 shadowcolor = a.getInt(attr, 0);
403 case R.styleable.BatteryHistoryChart_android_shadowDx:
404 dx = a.getFloat(attr, 0);
407 case R.styleable.BatteryHistoryChart_android_shadowDy:
408 dy = a.getFloat(attr, 0);
411 case R.styleable.BatteryHistoryChart_android_shadowRadius:
412 r = a.getFloat(attr, 0);
415 case R.styleable.BatteryHistoryChart_android_textColor:
416 mainTextAttrs.textColor = a.getColorStateList(attr);
417 headTextAttrs.textColor = a.getColorStateList(attr);
420 case R.styleable.BatteryHistoryChart_android_textSize:
421 mainTextAttrs.textSize = a.getDimensionPixelSize(attr, mainTextAttrs.textSize);
422 headTextAttrs.textSize = a.getDimensionPixelSize(attr, headTextAttrs.textSize);
425 case R.styleable.BatteryHistoryChart_android_typeface:
426 mainTextAttrs.typefaceIndex = a.getInt(attr, mainTextAttrs.typefaceIndex);
427 headTextAttrs.typefaceIndex = a.getInt(attr, headTextAttrs.typefaceIndex);
430 case R.styleable.BatteryHistoryChart_android_textStyle:
431 mainTextAttrs.styleIndex = a.getInt(attr, mainTextAttrs.styleIndex);
432 headTextAttrs.styleIndex = a.getInt(attr, headTextAttrs.styleIndex);
435 case R.styleable.BatteryHistoryChart_barPrimaryColor:
436 mBatteryBackgroundPaint.setColor(a.getInt(attr, 0));
437 mScreenOnPaint.setColor(a.getInt(attr, 0));
438 mGpsOnPaint.setColor(a.getInt(attr, 0));
439 mWifiRunningPaint.setColor(a.getInt(attr, 0));
440 mCpuRunningPaint.setColor(a.getInt(attr, 0));
441 mChargingPaint.setColor(a.getInt(attr, 0));
444 case R.styleable.BatteryHistoryChart_barPredictionColor:
445 mTimeRemainPaint.setColor(a.getInt(attr, 0));
448 case R.styleable.BatteryHistoryChart_chartMinHeight:
449 mChartMinHeight = a.getDimensionPixelSize(attr, 0);
456 mainTextAttrs.apply(context, mTextPaint);
457 headTextAttrs.apply(context, mHeaderTextPaint);
459 mDateLinePaint.set(mTextPaint);
460 mDateLinePaint.setStyle(Paint.Style.STROKE);
461 int hairlineWidth = mThinLineWidth/2;
462 if (hairlineWidth < 1) {
465 mDateLinePaint.setStrokeWidth(hairlineWidth);
466 mDateLinePaint.setPathEffect(new DashPathEffect(new float[] {
467 mThinLineWidth * 2, mThinLineWidth * 2 }, 0));
469 if (shadowcolor != 0) {
470 mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
471 mHeaderTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
475 void setStats(BatteryStats stats, Intent broadcast) {
477 mBatteryBroadcast = broadcast;
479 if (DEBUG) Log.d(TAG, "Setting stats...");
481 final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
483 long uSecTime = mStats.computeBatteryRealtime(elapsedRealtimeUs,
484 BatteryStats.STATS_SINCE_CHARGED);
485 mStatsPeriod = uSecTime;
486 mChargingLabel = getContext().getString(R.string.battery_stats_charging_label);
487 mScreenOnLabel = getContext().getString(R.string.battery_stats_screen_on_label);
488 mGpsOnLabel = getContext().getString(R.string.battery_stats_gps_on_label);
489 mWifiRunningLabel = getContext().getString(R.string.battery_stats_wifi_running_label);
490 mCpuRunningLabel = getContext().getString(R.string.battery_stats_wake_lock_label);
491 mPhoneSignalLabel = getContext().getString(R.string.battery_stats_phone_signal_label);
493 mMaxPercentLabelString = Utils.formatPercentage(100);
494 mMinPercentLabelString = Utils.formatPercentage(0);
496 mBatteryLevel = com.android.settings.Utils.getBatteryLevel(mBatteryBroadcast);
497 String batteryPercentString = Utils.formatPercentage(mBatteryLevel);
498 long remainingTimeUs = 0;
500 if (mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) == 0) {
501 final long drainTime = mStats.computeBatteryTimeRemaining(elapsedRealtimeUs);
503 remainingTimeUs = drainTime;
504 String timeString = Formatter.formatShortElapsedTime(getContext(),
506 mChargeLabelString = getContext().getResources().getString(
507 R.string.power_discharging_duration, batteryPercentString, timeString);
509 mChargeLabelString = batteryPercentString;
512 final long chargeTime = mStats.computeChargeTimeRemaining(elapsedRealtimeUs);
513 final String statusLabel = com.android.settings.Utils.getBatteryStatus(getResources(),
515 final int status = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
516 BatteryManager.BATTERY_STATUS_UNKNOWN);
517 if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
518 mDischarging = false;
519 remainingTimeUs = chargeTime;
520 String timeString = Formatter.formatShortElapsedTime(getContext(),
522 int plugType = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
524 if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
525 resId = R.string.power_charging_duration_ac;
526 } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
527 resId = R.string.power_charging_duration_usb;
528 } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
529 resId = R.string.power_charging_duration_wireless;
531 resId = R.string.power_charging_duration;
533 mChargeLabelString = getContext().getResources().getString(
534 resId, batteryPercentString, timeString);
536 mChargeLabelString = getContext().getResources().getString(
537 R.string.power_charging, batteryPercentString, statusLabel);
541 mChargeDurationString = "";
542 setContentDescription(mChargeLabelString);
545 int lastInteresting = 0;
550 mEndDataWallTime = 0;
554 long lastWallTime = 0;
555 long lastRealtime = 0;
558 boolean first = true;
559 if (stats.startIteratingHistoryLocked()) {
560 final HistoryItem rec = new HistoryItem();
561 while (stats.getNextHistoryLocked(rec)) {
565 mHistStart = rec.time;
567 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
568 || rec.cmd == HistoryItem.CMD_RESET) {
569 // If there is a ridiculously large jump in time, then we won't be
570 // able to create a good chart with that data, so just ignore the
571 // times we got before and pretend like our data extends back from
572 // the time we have now.
573 // Also, if we are getting a time change and we are less than 5 minutes
574 // since the start of the history real time, then also use this new
575 // time to compute the base time, since whatever time we had before is
576 // pretty much just noise.
577 if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L))
578 || rec.time < (mHistStart+(5*60*1000L))) {
581 lastWallTime = rec.currentTime;
582 lastRealtime = rec.time;
583 if (mStartWallTime == 0) {
584 mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
587 if (rec.isDeltaData()) {
588 if (rec.batteryLevel != lastLevel || pos == 1) {
589 lastLevel = rec.batteryLevel;
591 lastInteresting = pos;
592 mHistDataEnd = rec.time;
593 aggrStates |= rec.states;
594 aggrStates2 |= rec.states2;
598 mHistEnd = mHistDataEnd + (remainingTimeUs/1000);
599 mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
600 mEndWallTime = mEndDataWallTime + (remainingTimeUs/1000);
601 mNumHist = lastInteresting;
602 mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
603 mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
604 || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
605 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
606 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
607 if (!com.android.settings.Utils.isWifiOnly(getContext())) {
608 mHavePhoneSignal = true;
610 if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
614 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
615 mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString);
616 mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString);
617 mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString);
618 mChargeLabelStringWidth = (int)mHeaderTextPaint.measureText(mChargeLabelString);
619 mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString);
620 mTextAscent = (int)mTextPaint.ascent();
621 mTextDescent = (int)mTextPaint.descent();
622 mHeaderTextAscent = (int)mHeaderTextPaint.ascent();
623 mHeaderTextDescent = (int)mHeaderTextPaint.descent();
624 int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent;
625 mHeaderHeight = headerTextHeight*2 - mTextAscent;
626 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
627 getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec));
630 void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
631 int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
632 boolean lastWifiRunning, boolean lastCpuRunning, Path lastPath) {
633 if (curLevelPath != null) {
634 if (lastX >= 0 && lastX < w) {
635 if (lastPath != null) {
636 lastPath.lineTo(w, y);
638 curLevelPath.lineTo(w, y);
640 curLevelPath.lineTo(w, mLevelTop+levelh);
641 curLevelPath.lineTo(startX, mLevelTop+levelh);
642 curLevelPath.close();
646 mChargingPath.lineTo(w, h-mChargingOffset);
649 mScreenOnPath.lineTo(w, h-mScreenOnOffset);
652 mGpsOnPath.lineTo(w, h-mGpsOnOffset);
654 if (lastWifiRunning) {
655 mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
657 if (lastCpuRunning) {
658 mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
660 if (mHavePhoneSignal) {
661 mPhoneSignalChart.finish(w);
665 private boolean is24Hour() {
666 return DateFormat.is24HourFormat(getContext());
669 private boolean isDayFirst() {
670 LocaleData d = LocaleData.get(mContext.getResources().getConfiguration().locale);
671 String value = d.shortDateFormat4;
672 return value.indexOf('M') > value.indexOf('d');
676 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
677 super.onSizeChanged(w, h, oldw, oldh);
679 if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
681 if (mLastWidth == w && mLastHeight == h) {
685 if (mLastWidth == 0 || mLastHeight == 0) {
689 if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
696 int textHeight = mTextDescent - mTextAscent;
697 if (h > ((textHeight*10)+mChartMinHeight)) {
699 if (h > (textHeight*15)) {
700 // Plenty of room for the chart.
701 mLineWidth = textHeight/2;
703 // Compress lines to make more room for chart.
704 mLineWidth = textHeight/3;
708 mLineWidth = mThinLineWidth;
710 if (mLineWidth <= 0) mLineWidth = 1;
712 mLevelTop = mHeaderHeight;
713 mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
715 int levelWidth = mLevelRight-mLevelLeft;
717 mTextPaint.setStrokeWidth(mThinLineWidth);
718 mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
719 mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
720 mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth);
721 mChargingPaint.setStrokeWidth(mLineWidth);
722 mScreenOnPaint.setStrokeWidth(mLineWidth);
723 mGpsOnPaint.setStrokeWidth(mLineWidth);
724 mWifiRunningPaint.setStrokeWidth(mLineWidth);
725 mCpuRunningPaint.setStrokeWidth(mLineWidth);
726 mDebugRectPaint.setStrokeWidth(1);
728 int fullBarOffset = textHeight + mLineWidth;
731 mChargingOffset = mLineWidth;
732 mScreenOnOffset = mChargingOffset + fullBarOffset;
733 mCpuRunningOffset = mScreenOnOffset + fullBarOffset;
734 mWifiRunningOffset = mCpuRunningOffset + fullBarOffset;
735 mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0);
736 mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0);
737 mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0)
738 + mLineWidth*2 + mLineWidth/2;
739 if (mHavePhoneSignal) {
740 mPhoneSignalChart.init(w);
743 mScreenOnOffset = mGpsOnOffset = mWifiRunningOffset
744 = mCpuRunningOffset = mChargingOffset = mPhoneSignalOffset = 0;
745 mLevelOffset = fullBarOffset + mThinLineWidth*4;
746 if (mHavePhoneSignal) {
747 mPhoneSignalChart.init(0);
751 mBatLevelPath.reset();
752 mBatGoodPath.reset();
753 mBatWarnPath.reset();
754 mTimeRemainPath.reset();
755 mBatCriticalPath.reset();
756 mScreenOnPath.reset();
758 mWifiRunningPath.reset();
759 mCpuRunningPath.reset();
760 mChargingPath.reset();
765 final long walltimeStart = mStartWallTime;
766 final long walltimeChange = mEndWallTime > walltimeStart
767 ? (mEndWallTime-walltimeStart) : 1;
768 long curWalltime = mStartWallTime;
769 long lastRealtime = 0;
771 final int batLow = mBatLow;
772 final int batChange = mBatHigh-mBatLow;
774 final int levelh = h - mLevelOffset - mLevelTop;
775 mLevelBottom = mLevelTop + levelh;
777 int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
779 Path curLevelPath = null;
780 Path lastLinePath = null;
781 boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
782 boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false;
783 int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
784 final int N = mNumHist;
785 if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) {
786 final HistoryItem rec = new HistoryItem();
787 while (mStats.getNextHistoryLocked(rec) && i < N) {
788 if (rec.isDeltaData()) {
789 curWalltime += rec.time-lastRealtime;
790 lastRealtime = rec.time;
791 x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange);
796 StringBuilder sb = new StringBuilder(128);
797 sb.append("walloff=");
798 TimeUtils.formatDuration(curWalltime - walltimeStart, sb);
799 sb.append(" wallchange=");
800 TimeUtils.formatDuration(walltimeChange, sb);
803 Log.d("foo", sb.toString());
805 y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
808 // We have moved by at least a pixel.
810 // Don't plot changes within a pixel.
812 byte value = rec.batteryLevel;
813 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
814 else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
815 else path = null; //mBatGoodPath;
817 if (path != lastLinePath) {
818 if (lastLinePath != null) {
819 lastLinePath.lineTo(x, y);
825 } else if (path != null) {
829 if (curLevelPath == null) {
830 curLevelPath = mBatLevelPath;
831 curLevelPath.moveTo(x, y);
834 curLevelPath.lineTo(x, y);
842 final boolean charging =
843 (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
844 if (charging != lastCharging) {
846 mChargingPath.moveTo(x, h-mChargingOffset);
848 mChargingPath.lineTo(x, h-mChargingOffset);
850 lastCharging = charging;
853 final boolean screenOn =
854 (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
855 if (screenOn != lastScreenOn) {
857 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
859 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
861 lastScreenOn = screenOn;
864 final boolean gpsOn =
865 (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
866 if (gpsOn != lastGpsOn) {
868 mGpsOnPath.moveTo(x, h-mGpsOnOffset);
870 mGpsOnPath.lineTo(x, h-mGpsOnOffset);
875 final int wifiSupplState =
876 ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
877 >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
879 if (lastWifiSupplState != wifiSupplState) {
880 lastWifiSupplState = wifiSupplState;
881 switch (wifiSupplState) {
882 case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED:
883 case BatteryStats.WIFI_SUPPL_STATE_DORMANT:
884 case BatteryStats.WIFI_SUPPL_STATE_INACTIVE:
885 case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
886 case BatteryStats.WIFI_SUPPL_STATE_INVALID:
887 case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED:
888 wifiRunning = lastWifiSupplRunning = false;
891 wifiRunning = lastWifiSupplRunning = true;
895 wifiRunning = lastWifiSupplRunning;
897 if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
898 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
899 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
902 if (wifiRunning != lastWifiRunning) {
904 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
906 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
908 lastWifiRunning = wifiRunning;
911 final boolean cpuRunning =
912 (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
913 if (cpuRunning != lastCpuRunning) {
915 mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
917 mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
919 lastCpuRunning = cpuRunning;
922 if (mLargeMode && mHavePhoneSignal) {
924 if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
925 >> HistoryItem.STATE_PHONE_STATE_SHIFT)
926 == ServiceState.STATE_POWER_OFF) {
928 } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
931 bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
932 >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
935 mPhoneSignalChart.addTick(x, bin);
940 long lastWalltime = curWalltime;
941 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
942 || rec.cmd == HistoryItem.CMD_RESET) {
943 if (rec.currentTime >= mStartWallTime) {
944 curWalltime = rec.currentTime;
946 curWalltime = mStartWallTime + (rec.time-mHistStart);
948 lastRealtime = rec.time;
951 if (rec.cmd != HistoryItem.CMD_OVERFLOW
952 && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
953 || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) {
954 if (curLevelPath != null) {
955 finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
956 lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
957 lastCpuRunning, lastLinePath);
961 lastCharging = lastScreenOn = lastGpsOn = lastCpuRunning = false;
968 mStats.finishIteratingHistoryLocked();
971 if (lastY < 0 || lastX < 0) {
972 // Didn't get any data...
973 x = lastX = mLevelLeft;
974 y = lastY = mLevelTop + levelh - ((mBatteryLevel-batLow)*(levelh-1))/batChange;
976 byte value = (byte)mBatteryLevel;
977 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
978 else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
979 else path = null; //mBatGoodPath;
984 mBatLevelPath.moveTo(x, y);
985 curLevelPath = mBatLevelPath;
988 // Figure out where the actual data ends on the screen.
989 x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
995 finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
996 lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
997 lastCpuRunning, lastLinePath);
1000 // If we reserved room for the remaining time, create a final path to draw
1001 // that part of the UI.
1002 mTimeRemainPath.moveTo(x, lastY);
1003 int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange;
1004 int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange;
1006 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1008 mTimeRemainPath.lineTo(mLevelRight, fullY);
1009 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1011 mTimeRemainPath.lineTo(x, emptyY);
1012 mTimeRemainPath.close();
1015 if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) {
1016 // Create the time labels at the bottom.
1017 boolean is24hr = is24Hour();
1018 Calendar calStart = Calendar.getInstance();
1019 calStart.setTimeInMillis(mStartWallTime);
1020 calStart.set(Calendar.MILLISECOND, 0);
1021 calStart.set(Calendar.SECOND, 0);
1022 calStart.set(Calendar.MINUTE, 0);
1023 long startRoundTime = calStart.getTimeInMillis();
1024 if (startRoundTime < mStartWallTime) {
1025 calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1);
1026 startRoundTime = calStart.getTimeInMillis();
1028 Calendar calEnd = Calendar.getInstance();
1029 calEnd.setTimeInMillis(mEndWallTime);
1030 calEnd.set(Calendar.MILLISECOND, 0);
1031 calEnd.set(Calendar.SECOND, 0);
1032 calEnd.set(Calendar.MINUTE, 0);
1033 long endRoundTime = calEnd.getTimeInMillis();
1034 if (startRoundTime < endRoundTime) {
1035 addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr);
1036 Calendar calMid = Calendar.getInstance();
1037 calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2));
1038 calMid.set(Calendar.MILLISECOND, 0);
1039 calMid.set(Calendar.SECOND, 0);
1040 calMid.set(Calendar.MINUTE, 0);
1041 long calMidMillis = calMid.getTimeInMillis();
1042 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1043 addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr);
1045 addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
1048 // Create the date labels if the chart includes multiple days
1049 if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) ||
1050 calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) {
1051 boolean isDayFirst = isDayFirst();
1052 calStart.set(Calendar.HOUR_OF_DAY, 0);
1053 startRoundTime = calStart.getTimeInMillis();
1054 if (startRoundTime < mStartWallTime) {
1055 calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1);
1056 startRoundTime = calStart.getTimeInMillis();
1058 calEnd.set(Calendar.HOUR_OF_DAY, 0);
1059 endRoundTime = calEnd.getTimeInMillis();
1060 if (startRoundTime < endRoundTime) {
1061 addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst);
1062 Calendar calMid = Calendar.getInstance();
1063 calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2));
1064 calMid.set(Calendar.HOUR_OF_DAY, 0);
1065 long calMidMillis = calMid.getTimeInMillis();
1066 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1067 addDateLabel(calMid, mLevelLeft, mLevelRight, isDayFirst);
1070 addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
1074 if (mTimeLabels.size() < 2) {
1075 // If there are fewer than 2 time labels, then they are useless. Just
1076 // show an axis label giving the entire duration.
1077 mDurationString = Formatter.formatShortElapsedTime(getContext(),
1078 mEndWallTime - mStartWallTime);
1079 mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
1081 mDurationString = null;
1082 mDurationStringWidth = 0;
1086 void addTimeLabel(Calendar cal, int levelLeft, int levelRight, boolean is24hr) {
1087 final long walltimeStart = mStartWallTime;
1088 final long walltimeChange = mEndWallTime-walltimeStart;
1089 mTimeLabels.add(new TimeLabel(mTextPaint,
1090 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1095 void addDateLabel(Calendar cal, int levelLeft, int levelRight, boolean isDayFirst) {
1096 final long walltimeStart = mStartWallTime;
1097 final long walltimeChange = mEndWallTime-walltimeStart;
1098 mDateLabels.add(new DateLabel(mTextPaint,
1099 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1105 protected void onDraw(Canvas canvas) {
1106 super.onDraw(canvas);
1108 final int width = getWidth();
1109 final int height = getHeight();
1111 //buildBitmap(width, height);
1113 if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
1114 //canvas.drawBitmap(mBitmap, 0, 0, null);
1115 drawChart(canvas, width, height);
1118 void buildBitmap(int width, int height) {
1119 if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
1123 if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
1125 mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
1126 Bitmap.Config.ARGB_8888);
1127 mCanvas = new Canvas(mBitmap);
1128 drawChart(mCanvas, width, height);
1131 void drawChart(Canvas canvas, int width, int height) {
1132 final boolean layoutRtl = isLayoutRtl();
1133 final int textStartX = layoutRtl ? width : 0;
1134 final int textEndX = layoutRtl ? 0 : width;
1135 final Paint.Align textAlignLeft = layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT;
1136 final Paint.Align textAlignRight = layoutRtl ? Paint.Align.LEFT : Paint.Align.RIGHT;
1139 canvas.drawRect(1, 1, width, height, mDebugRectPaint);
1142 if (DEBUG) Log.d(TAG, "Drawing level path.");
1143 canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
1144 if (!mTimeRemainPath.isEmpty()) {
1145 if (DEBUG) Log.d(TAG, "Drawing time remain path.");
1146 canvas.drawPath(mTimeRemainPath, mTimeRemainPaint);
1148 if (mTimeLabels.size() > 1) {
1149 int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1150 int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2);
1151 mTextPaint.setTextAlign(Paint.Align.LEFT);
1153 for (int i=0; i<mTimeLabels.size(); i++) {
1154 TimeLabel label = mTimeLabels.get(i);
1156 int x = label.x - label.width/2;
1160 if (DEBUG) Log.d(TAG, "Drawing left label: " + label.label + " @ " + x);
1161 canvas.drawText(label.label, x, y, mTextPaint);
1162 canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1163 lastX = x + label.width;
1164 } else if (i < (mTimeLabels.size()-1)) {
1165 int x = label.x - label.width/2;
1166 if (x < (lastX+mTextAscent)) {
1169 TimeLabel nextLabel = mTimeLabels.get(i+1);
1170 if (x > (width-nextLabel.width-mTextAscent)) {
1173 if (DEBUG) Log.d(TAG, "Drawing middle label: " + label.label + " @ " + x);
1174 canvas.drawText(label.label, x, y, mTextPaint);
1175 canvas.drawLine(label.x, ytick, label.x, ytick + mThinLineWidth, mTextPaint);
1176 lastX = x + label.width;
1178 int x = label.x - label.width/2;
1179 if ((x+label.width) >= width) {
1180 x = width-1-label.width;
1182 if (DEBUG) Log.d(TAG, "Drawing right label: " + label.label + " @ " + x);
1183 canvas.drawText(label.label, x, y, mTextPaint);
1184 canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1187 } else if (mDurationString != null) {
1188 int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1189 mTextPaint.setTextAlign(Paint.Align.LEFT);
1190 canvas.drawText(mDurationString,
1191 mLevelLeft + (mLevelRight-mLevelLeft)/2 - mDurationStringWidth/2,
1195 int headerTop = -mHeaderTextAscent + (mHeaderTextDescent-mHeaderTextAscent)/3;
1196 mHeaderTextPaint.setTextAlign(textAlignLeft);
1197 if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mChargeLabelString);
1198 canvas.drawText(mChargeLabelString, textStartX, headerTop, mHeaderTextPaint);
1199 int stringHalfWidth = mChargeDurationStringWidth / 2;
1200 if (layoutRtl) stringHalfWidth = -stringHalfWidth;
1201 int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2)
1202 + (layoutRtl ? mDrainStringWidth : mChargeLabelStringWidth);
1203 if (DEBUG) Log.d(TAG, "Drawing charge duration string: " + mChargeDurationString);
1204 canvas.drawText(mChargeDurationString, headerCenter - stringHalfWidth, headerTop,
1206 mHeaderTextPaint.setTextAlign(textAlignRight);
1207 if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString);
1208 canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint);
1210 if (!mBatGoodPath.isEmpty()) {
1211 if (DEBUG) Log.d(TAG, "Drawing good battery path");
1212 canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
1214 if (!mBatWarnPath.isEmpty()) {
1215 if (DEBUG) Log.d(TAG, "Drawing warn battery path");
1216 canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
1218 if (!mBatCriticalPath.isEmpty()) {
1219 if (DEBUG) Log.d(TAG, "Drawing critical battery path");
1220 canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
1222 if (mHavePhoneSignal) {
1223 if (DEBUG) Log.d(TAG, "Drawing phone signal path");
1224 int top = height-mPhoneSignalOffset - (mLineWidth/2);
1225 mPhoneSignalChart.draw(canvas, top, mLineWidth);
1227 if (!mScreenOnPath.isEmpty()) {
1228 if (DEBUG) Log.d(TAG, "Drawing screen on path");
1229 canvas.drawPath(mScreenOnPath, mScreenOnPaint);
1231 if (!mChargingPath.isEmpty()) {
1232 if (DEBUG) Log.d(TAG, "Drawing charging path");
1233 canvas.drawPath(mChargingPath, mChargingPaint);
1236 if (!mGpsOnPath.isEmpty()) {
1237 if (DEBUG) Log.d(TAG, "Drawing gps path");
1238 canvas.drawPath(mGpsOnPath, mGpsOnPaint);
1242 if (!mWifiRunningPath.isEmpty()) {
1243 if (DEBUG) Log.d(TAG, "Drawing wifi path");
1244 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
1247 if (!mCpuRunningPath.isEmpty()) {
1248 if (DEBUG) Log.d(TAG, "Drawing running path");
1249 canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
1253 if (DEBUG) Log.d(TAG, "Drawing large mode labels");
1254 Paint.Align align = mTextPaint.getTextAlign();
1255 mTextPaint.setTextAlign(textAlignLeft); // large-mode labels always aligned to start
1256 if (mHavePhoneSignal) {
1257 canvas.drawText(mPhoneSignalLabel, textStartX,
1258 height - mPhoneSignalOffset - mTextDescent, mTextPaint);
1261 canvas.drawText(mGpsOnLabel, textStartX,
1262 height - mGpsOnOffset - mTextDescent, mTextPaint);
1265 canvas.drawText(mWifiRunningLabel, textStartX,
1266 height - mWifiRunningOffset - mTextDescent, mTextPaint);
1268 canvas.drawText(mCpuRunningLabel, textStartX,
1269 height - mCpuRunningOffset - mTextDescent, mTextPaint);
1270 canvas.drawText(mChargingLabel, textStartX,
1271 height - mChargingOffset - mTextDescent, mTextPaint);
1272 canvas.drawText(mScreenOnLabel, textStartX,
1273 height - mScreenOnOffset - mTextDescent, mTextPaint);
1274 mTextPaint.setTextAlign(align);
1277 canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
1278 mLevelBottom+(mThinLineWidth/2), mTextPaint);
1280 for (int i=0; i<10; i++) {
1281 int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10;
1282 canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y,
1283 mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint);
1286 if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth
1287 + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString));
1288 canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint);
1289 canvas.drawText(mMinPercentLabelString,
1290 mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth,
1291 mLevelBottom - mThinLineWidth, mTextPaint);
1292 canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width,
1293 mLevelBottom+mThinLineWidth, mTextPaint);
1295 if (mDateLabels.size() > 0) {
1296 int ytop = mLevelTop + mTextAscent;
1297 int ybottom = mLevelBottom;
1298 int lastLeft = mLevelRight;
1299 mTextPaint.setTextAlign(Paint.Align.LEFT);
1300 for (int i=mDateLabels.size()-1; i>=0; i--) {
1301 DateLabel label = mDateLabels.get(i);
1302 int left = label.x - mThinLineWidth;
1303 int x = label.x + mThinLineWidth*2;
1304 if ((x+label.width) >= lastLeft) {
1305 x = label.x - mThinLineWidth*2 - label.width;
1306 left = x - mThinLineWidth;
1307 if (left >= lastLeft) {
1312 if (left < mLevelLeft) {
1313 // Won't fit on left, give up.
1316 mDateLinePath.reset();
1317 mDateLinePath.moveTo(label.x, ytop);
1318 mDateLinePath.lineTo(label.x, ybottom);
1319 canvas.drawPath(mDateLinePath, mDateLinePaint);
1320 canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint);