OSDN Git Service

d44f9f8b2f7cf1e04c7b23e6b5e40b560c18c0fa
[android-x86/packages-apps-Settings.git] / src / com / android / settings / fuelgauge / BatteryHistoryChart.java
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.settings.fuelgauge;
18
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.res.ColorStateList;
22 import android.content.res.TypedArray;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.DashPathEffect;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.Typeface;
29 import android.os.BatteryStats;
30 import android.os.BatteryStats.HistoryItem;
31 import android.os.SystemClock;
32 import android.telephony.ServiceState;
33 import android.text.TextPaint;
34 import android.text.format.DateFormat;
35 import android.text.format.Formatter;
36 import android.util.AttributeSet;
37 import android.util.Log;
38 import android.util.TimeUtils;
39 import android.util.TypedValue;
40 import android.view.View;
41
42 import com.android.settings.R;
43 import com.android.settings.Utils;
44
45 import libcore.icu.LocaleData;
46
47 import java.util.ArrayList;
48 import java.util.Calendar;
49 import java.util.Locale;
50
51 public class BatteryHistoryChart extends View {
52     static final boolean DEBUG = false;
53     static final String TAG = "BatteryHistoryChart";
54
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;
58
59     static class ChartData {
60         int[] mColors;
61         Paint[] mPaints;
62
63         int mNumTicks;
64         int[] mTicks;
65         int mLastBin;
66
67         void setColors(int[] colors) {
68             mColors = 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);
74             }
75         }
76
77         void init(int width) {
78             if (width > 0) {
79                 mTicks = new int[width*2];
80             } else {
81                 mTicks = null;
82             }
83             mNumTicks = 0;
84             mLastBin = 0;
85         }
86
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);
90                 mNumTicks++;
91                 mLastBin = bin;
92             }
93         }
94
95         void finish(int width) {
96             if (mLastBin != 0) {
97                 addTick(width, 0);
98             }
99         }
100
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;
108                 if (lastBin != 0) {
109                     canvas.drawRect(lastX, top, x, bottom, mPaints[lastBin]);
110                 }
111                 lastBin = bin;
112                 lastX = x;
113             }
114
115         }
116     }
117
118     static final int SANS = 1;
119     static final int SERIF = 2;
120     static final int MONOSPACE = 3;
121
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;
125
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();
143
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();
157
158     BatteryStats mStats;
159     Intent mBatteryBroadcast;
160     long mStatsPeriod;
161     String mMaxPercentLabelString;
162     String mMinPercentLabelString;
163     String mDurationString;
164     String mChargeDurationString;
165     String mDrainString;
166     String mChargingLabel;
167     String mScreenOnLabel;
168     String mGpsOnLabel;
169     String mCameraOnLabel;
170     String mFlashlightOnLabel;
171     String mWifiRunningLabel;
172     String mCpuRunningLabel;
173     String mPhoneSignalLabel;
174
175     BatteryInfo mInfo;
176
177     int mChartMinHeight;
178     int mHeaderHeight;
179
180     int mBatteryWarnLevel;
181     int mBatteryCriticalLevel;
182
183     int mTextAscent;
184     int mTextDescent;
185     int mHeaderTextAscent;
186     int mHeaderTextDescent;
187     int mMaxPercentLabelStringWidth;
188     int mMinPercentLabelStringWidth;
189     int mDurationStringWidth;
190     int mChargeLabelStringWidth;
191     int mChargeDurationStringWidth;
192     int mDrainStringWidth;
193
194     boolean mLargeMode;
195
196     int mLastWidth = -1;
197     int mLastHeight = -1;
198
199     int mLineWidth;
200     int mThinLineWidth;
201     int mChargingOffset;
202     int mScreenOnOffset;
203     int mGpsOnOffset;
204     int mFlashlightOnOffset;
205     int mCameraOnOffset;
206     int mWifiRunningOffset;
207     int mCpuRunningOffset;
208     int mPhoneSignalOffset;
209     int mLevelOffset;
210     int mLevelTop;
211     int mLevelBottom;
212     int mLevelLeft;
213     int mLevelRight;
214
215     int mNumHist;
216     long mHistStart;
217     long mHistDataEnd;
218     long mHistEnd;
219     long mStartWallTime;
220     long mEndDataWallTime;
221     long mEndWallTime;
222     int mBatLow;
223     int mBatHigh;
224     boolean mHaveWifi;
225     boolean mHaveGps;
226     boolean mHavePhoneSignal;
227     boolean mHaveCamera;
228     boolean mHaveFlashlight;
229
230     final ArrayList<TimeLabel> mTimeLabels = new ArrayList<TimeLabel>();
231     final ArrayList<DateLabel> mDateLabels = new ArrayList<DateLabel>();
232
233     Bitmap mBitmap;
234     Canvas mCanvas;
235
236     static class TextAttrs {
237         ColorStateList textColor = null;
238         int textSize = 15;
239         int typefaceIndex = -1;
240         int styleIndex = -1;
241
242         void retrieve(Context context, TypedArray from, int index) {
243             TypedArray appearance = null;
244             int ap = from.getResourceId(index, -1);
245             if (ap != -1) {
246                 appearance = context.obtainStyledAttributes(ap,
247                                     com.android.internal.R.styleable.TextAppearance);
248             }
249             if (appearance != null) {
250                 int n = appearance.getIndexCount();
251                 for (int i = 0; i < n; i++) {
252                     int attr = appearance.getIndex(i);
253
254                     switch (attr) {
255                     case com.android.internal.R.styleable.TextAppearance_textColor:
256                         textColor = appearance.getColorStateList(attr);
257                         break;
258
259                     case com.android.internal.R.styleable.TextAppearance_textSize:
260                         textSize = appearance.getDimensionPixelSize(attr, textSize);
261                         break;
262
263                     case com.android.internal.R.styleable.TextAppearance_typeface:
264                         typefaceIndex = appearance.getInt(attr, -1);
265                         break;
266
267                     case com.android.internal.R.styleable.TextAppearance_textStyle:
268                         styleIndex = appearance.getInt(attr, -1);
269                         break;
270                     }
271                 }
272
273                 appearance.recycle();
274             }
275         }
276
277         void apply(Context context, TextPaint paint) {
278             paint.density = context.getResources().getDisplayMetrics().density;
279             paint.setCompatibilityScaling(
280                     context.getResources().getCompatibilityInfo().applicationScale);
281
282             paint.setColor(textColor.getDefaultColor());
283             paint.setTextSize(textSize);
284
285             Typeface tf = null;
286             switch (typefaceIndex) {
287                 case SANS:
288                     tf = Typeface.SANS_SERIF;
289                     break;
290
291                 case SERIF:
292                     tf = Typeface.SERIF;
293                     break;
294
295                 case MONOSPACE:
296                     tf = Typeface.MONOSPACE;
297                     break;
298             }
299
300             setTypeface(paint, tf, styleIndex);
301         }
302
303         public void setTypeface(TextPaint paint, Typeface tf, int style) {
304             if (style > 0) {
305                 if (tf == null) {
306                     tf = Typeface.defaultFromStyle(style);
307                 } else {
308                     tf = Typeface.create(tf, style);
309                 }
310
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);
317             } else {
318                 paint.setFakeBoldText(false);
319                 paint.setTextSkewX(0);
320                 paint.setTypeface(tf);
321             }
322         }
323     }
324
325     static class TimeLabel {
326         final int x;
327         final String label;
328         final int width;
329
330         TimeLabel(TextPaint paint, int x, Calendar cal, boolean use24hr) {
331             this.x = x;
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);
336         }
337     }
338
339     static class DateLabel {
340         final int x;
341         final String label;
342         final int width;
343
344         DateLabel(TextPaint paint, int x, Calendar cal, boolean dayFirst) {
345             this.x = x;
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);
350         }
351     }
352
353     public BatteryHistoryChart(Context context, AttributeSet attrs) {
354         super(context, attrs);
355
356         if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!");
357
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);
362
363         mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
364                 2, getResources().getDisplayMetrics());
365
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);
393
394         TypedArray a =
395             context.obtainStyledAttributes(
396                 attrs, R.styleable.BatteryHistoryChart, 0, 0);
397
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);
402
403         int shadowcolor = 0;
404         float dx=0, dy=0, r=0;
405         
406         int n = a.getIndexCount();
407         for (int i = 0; i < n; i++) {
408             int attr = a.getIndex(i);
409
410             switch (attr) {
411                 case R.styleable.BatteryHistoryChart_android_shadowColor:
412                     shadowcolor = a.getInt(attr, 0);
413                     break;
414
415                 case R.styleable.BatteryHistoryChart_android_shadowDx:
416                     dx = a.getFloat(attr, 0);
417                     break;
418
419                 case R.styleable.BatteryHistoryChart_android_shadowDy:
420                     dy = a.getFloat(attr, 0);
421                     break;
422
423                 case R.styleable.BatteryHistoryChart_android_shadowRadius:
424                     r = a.getFloat(attr, 0);
425                     break;
426
427                 case R.styleable.BatteryHistoryChart_android_textColor:
428                     mainTextAttrs.textColor = a.getColorStateList(attr);
429                     headTextAttrs.textColor = a.getColorStateList(attr);
430                     break;
431
432                 case R.styleable.BatteryHistoryChart_android_textSize:
433                     mainTextAttrs.textSize = a.getDimensionPixelSize(attr, mainTextAttrs.textSize);
434                     headTextAttrs.textSize = a.getDimensionPixelSize(attr, headTextAttrs.textSize);
435                     break;
436
437                 case R.styleable.BatteryHistoryChart_android_typeface:
438                     mainTextAttrs.typefaceIndex = a.getInt(attr, mainTextAttrs.typefaceIndex);
439                     headTextAttrs.typefaceIndex = a.getInt(attr, headTextAttrs.typefaceIndex);
440                     break;
441
442                 case R.styleable.BatteryHistoryChart_android_textStyle:
443                     mainTextAttrs.styleIndex = a.getInt(attr, mainTextAttrs.styleIndex);
444                     headTextAttrs.styleIndex = a.getInt(attr, headTextAttrs.styleIndex);
445                     break;
446
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));
456                     break;
457
458                 case R.styleable.BatteryHistoryChart_barPredictionColor:
459                     mTimeRemainPaint.setColor(a.getInt(attr, 0));
460                     break;
461
462                 case R.styleable.BatteryHistoryChart_chartMinHeight:
463                     mChartMinHeight = a.getDimensionPixelSize(attr, 0);
464                     break;
465             }
466         }
467         
468         a.recycle();
469         
470         mainTextAttrs.apply(context, mTextPaint);
471         headTextAttrs.apply(context, mHeaderTextPaint);
472
473         mDateLinePaint.set(mTextPaint);
474         mDateLinePaint.setStyle(Paint.Style.STROKE);
475         int hairlineWidth = mThinLineWidth/2;
476         if (hairlineWidth < 1) {
477             hairlineWidth = 1;
478         }
479         mDateLinePaint.setStrokeWidth(hairlineWidth);
480         mDateLinePaint.setPathEffect(new DashPathEffect(new float[] {
481                 mThinLineWidth * 2, mThinLineWidth * 2 }, 0));
482
483         if (shadowcolor != 0) {
484             mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
485             mHeaderTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
486         }
487     }
488
489     void setStats(BatteryStats stats, Intent broadcast) {
490         mStats = stats;
491         mBatteryBroadcast = broadcast;
492
493         if (DEBUG) Log.d(TAG, "Setting stats...");
494
495         final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
496
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);
508
509         mMaxPercentLabelString = Utils.formatPercentage(100);
510         mMinPercentLabelString = Utils.formatPercentage(0);
511         BatteryInfo.getBatteryInfo(getContext(), info -> {
512             mInfo = info;
513             mDrainString = "";
514             mChargeDurationString = "";
515             setContentDescription(mInfo.chargeLabel);
516
517             int pos = 0;
518             int lastInteresting = 0;
519             byte lastLevel = -1;
520             mBatLow = 0;
521             mBatHigh = 100;
522             mStartWallTime = 0;
523             mEndDataWallTime = 0;
524             mEndWallTime = 0;
525             mHistStart = 0;
526             mHistEnd = 0;
527             long lastWallTime = 0;
528             long lastRealtime = 0;
529             int aggrStates = 0;
530             int aggrStates2 = 0;
531             boolean first = true;
532             if (stats.startIteratingHistoryLocked()) {
533                 final HistoryItem rec = new HistoryItem();
534                 while (stats.getNextHistoryLocked(rec)) {
535                     pos++;
536                     if (first) {
537                         first = false;
538                         mHistStart = rec.time;
539                     }
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))) {
552                             mStartWallTime = 0;
553                         }
554                         lastWallTime = rec.currentTime;
555                         lastRealtime = rec.time;
556                         if (mStartWallTime == 0) {
557                             mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
558                         }
559                     }
560                     if (rec.isDeltaData()) {
561                         if (rec.batteryLevel != lastLevel || pos == 1) {
562                             lastLevel = rec.batteryLevel;
563                         }
564                         lastInteresting = pos;
565                         mHistDataEnd = rec.time;
566                         aggrStates |= rec.states;
567                         aggrStates2 |= rec.states2;
568                     }
569                 }
570             }
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;
584             }
585             if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
586         }, mStats, false /* shortString */);
587     }
588
589     @Override
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));
605     }
606
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);
615                 }
616                 curLevelPath.lineTo(w, y);
617             }
618             curLevelPath.lineTo(w, mLevelTop+levelh);
619             curLevelPath.lineTo(startX, mLevelTop+levelh);
620             curLevelPath.close();
621         }
622         
623         if (lastCharging) {
624             mChargingPath.lineTo(w, h-mChargingOffset);
625         }
626         if (lastScreenOn) {
627             mScreenOnPath.lineTo(w, h-mScreenOnOffset);
628         }
629         if (lastGpsOn) {
630             mGpsOnPath.lineTo(w, h-mGpsOnOffset);
631         }
632         if (lastFlashlightOn) {
633             mFlashlightOnPath.lineTo(w, h-mFlashlightOnOffset);
634         }
635         if (lastCameraOn) {
636             mCameraOnPath.lineTo(w, h-mCameraOnOffset);
637         }
638         if (lastWifiRunning) {
639             mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
640         }
641         if (lastCpuRunning) {
642             mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
643         }
644         if (mHavePhoneSignal) {
645             mPhoneSignalChart.finish(w);
646         }
647     }
648
649     private boolean is24Hour() {
650         return DateFormat.is24HourFormat(getContext());
651     }
652
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');
657     }
658
659     @Override
660     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
661         super.onSizeChanged(w, h, oldw, oldh);
662
663         if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
664
665         if (mLastWidth == w && mLastHeight == h) {
666             return;
667         }
668
669         if (mLastWidth == 0 || mLastHeight == 0) {
670             return;
671         }
672
673         if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
674
675         mLastWidth = w;
676         mLastHeight = h;
677         mBitmap = null;
678         mCanvas = null;
679
680         int textHeight = mTextDescent - mTextAscent;
681         if (h > ((textHeight*10)+mChartMinHeight)) {
682             mLargeMode = true;
683             if (h > (textHeight*15)) {
684                 // Plenty of room for the chart.
685                 mLineWidth = textHeight/2;
686             } else {
687                 // Compress lines to make more room for chart.
688                 mLineWidth = textHeight/3;
689             }
690         } else {
691             mLargeMode = false;
692             mLineWidth = mThinLineWidth;
693         }
694         if (mLineWidth <= 0) mLineWidth = 1;
695
696         mLevelTop = mHeaderHeight;
697         mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
698         mLevelRight = w;
699         int levelWidth = mLevelRight-mLevelLeft;
700
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);
713
714         int fullBarOffset = textHeight + mLineWidth;
715
716         if (mLargeMode) {
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);
729             }
730         } else {
731             mScreenOnOffset = mGpsOnOffset = mCameraOnOffset = mFlashlightOnOffset =
732                     mWifiRunningOffset = mCpuRunningOffset = mChargingOffset =
733                     mPhoneSignalOffset = 0;
734             mLevelOffset = fullBarOffset + mThinLineWidth*4;
735             if (mHavePhoneSignal) {
736                 mPhoneSignalChart.init(0);
737             }
738         }
739
740         mBatLevelPath.reset();
741         mBatGoodPath.reset();
742         mBatWarnPath.reset();
743         mTimeRemainPath.reset();
744         mBatCriticalPath.reset();
745         mScreenOnPath.reset();
746         mGpsOnPath.reset();
747         mFlashlightOnPath.reset();
748         mCameraOnPath.reset();
749         mWifiRunningPath.reset();
750         mCpuRunningPath.reset();
751         mChargingPath.reset();
752
753         mTimeLabels.clear();
754         mDateLabels.clear();
755
756         final long walltimeStart = mStartWallTime;
757         final long walltimeChange = mEndWallTime > walltimeStart
758                 ? (mEndWallTime-walltimeStart) : 1;
759         long curWalltime = mStartWallTime;
760         long lastRealtime = 0;
761
762         final int batLow = mBatLow;
763         final int batChange = mBatHigh-mBatLow;
764
765         final int levelh = h - mLevelOffset - mLevelTop;
766         mLevelBottom = mLevelTop + levelh;
767
768         int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
769         int i = 0;
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);
784                     if (x < 0) {
785                         x = 0;
786                     }
787                     if (false) {
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);
793                         sb.append(" x=");
794                         sb.append(x);
795                         Log.d("foo", sb.toString());
796                     }
797                     y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
798
799                     if (lastX != x) {
800                         // We have moved by at least a pixel.
801                         if (lastY != y) {
802                             // Don't plot changes within a pixel.
803                             Path path;
804                             byte value = rec.batteryLevel;
805                             if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
806                             else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
807                             else path = null; //mBatGoodPath;
808
809                             if (path != lastLinePath) {
810                                 if (lastLinePath != null) {
811                                     lastLinePath.lineTo(x, y);
812                                 }
813                                 if (path != null) {
814                                     path.moveTo(x, y);
815                                 }
816                                 lastLinePath = path;
817                             } else if (path != null) {
818                                 path.lineTo(x, y);
819                             }
820
821                             if (curLevelPath == null) {
822                                 curLevelPath = mBatLevelPath;
823                                 curLevelPath.moveTo(x, y);
824                                 startX = x;
825                             } else {
826                                 curLevelPath.lineTo(x, y);
827                             }
828                             lastX = x;
829                             lastY = y;
830                         }
831                     }
832
833                     if (mLargeMode) {
834                         final boolean charging =
835                             (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
836                         if (charging != lastCharging) {
837                             if (charging) {
838                                 mChargingPath.moveTo(x, h-mChargingOffset);
839                             } else {
840                                 mChargingPath.lineTo(x, h-mChargingOffset);
841                             }
842                             lastCharging = charging;
843                         }
844
845                         final boolean screenOn =
846                             (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
847                         if (screenOn != lastScreenOn) {
848                             if (screenOn) {
849                                 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
850                             } else {
851                                 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
852                             }
853                             lastScreenOn = screenOn;
854                         }
855
856                         final boolean gpsOn =
857                             (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
858                         if (gpsOn != lastGpsOn) {
859                             if (gpsOn) {
860                                 mGpsOnPath.moveTo(x, h-mGpsOnOffset);
861                             } else {
862                                 mGpsOnPath.lineTo(x, h-mGpsOnOffset);
863                             }
864                             lastGpsOn = gpsOn;
865                         }
866
867                         final boolean flashlightOn =
868                             (rec.states2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
869                         if (flashlightOn != lastFlashlightOn) {
870                             if (flashlightOn) {
871                                 mFlashlightOnPath.moveTo(x, h-mFlashlightOnOffset);
872                             } else {
873                                 mFlashlightOnPath.lineTo(x, h-mFlashlightOnOffset);
874                             }
875                             lastFlashlightOn = flashlightOn;
876                         }
877
878                         final boolean cameraOn =
879                             (rec.states2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
880                         if (cameraOn != lastCameraOn) {
881                             if (cameraOn) {
882                                 mCameraOnPath.moveTo(x, h-mCameraOnOffset);
883                             } else {
884                                 mCameraOnPath.lineTo(x, h-mCameraOnOffset);
885                             }
886                             lastCameraOn = cameraOn;
887                         }
888
889                         final int wifiSupplState =
890                             ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
891                                     >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
892                         boolean wifiRunning;
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;
903                                     break;
904                                 default:
905                                     wifiRunning = lastWifiSupplRunning = true;
906                                     break;
907                             }
908                         } else {
909                             wifiRunning = lastWifiSupplRunning;
910                         }
911                         if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
912                                 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
913                                 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
914                             wifiRunning = true;
915                         }
916                         if (wifiRunning != lastWifiRunning) {
917                             if (wifiRunning) {
918                                 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
919                             } else {
920                                 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
921                             }
922                             lastWifiRunning = wifiRunning;
923                         }
924
925                         final boolean cpuRunning =
926                             (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
927                         if (cpuRunning != lastCpuRunning) {
928                             if (cpuRunning) {
929                                 mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
930                             } else {
931                                 mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
932                             }
933                             lastCpuRunning = cpuRunning;
934                         }
935
936                         if (mLargeMode && mHavePhoneSignal) {
937                             int bin;
938                             if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
939                                     >> HistoryItem.STATE_PHONE_STATE_SHIFT)
940                                     == ServiceState.STATE_POWER_OFF) {
941                                 bin = 0;
942                             } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
943                                 bin = 1;
944                             } else {
945                                 bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
946                                         >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
947                                 bin += 2;
948                             }
949                             mPhoneSignalChart.addTick(x, bin);
950                         }
951                     }
952
953                 } else {
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;
959                         } else {
960                             curWalltime = mStartWallTime + (rec.time-mHistStart);
961                         }
962                         lastRealtime = rec.time;
963                     }
964
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);
972                             lastX = lastY = -1;
973                             curLevelPath = null;
974                             lastLinePath = null;
975                             lastCharging = lastScreenOn = lastGpsOn = lastFlashlightOn =
976                                     lastCameraOn = lastCpuRunning = false;
977                         }
978                     }
979                 }
980
981                 i++;
982             }
983             mStats.finishIteratingHistoryLocked();
984         }
985
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;
990             Path path;
991             byte value = (byte)mInfo.batteryLevel;
992             if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
993             else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
994             else path = null; //mBatGoodPath;
995             if (path != null) {
996                 path.moveTo(x, y);
997                 lastLinePath = path;
998             }
999             mBatLevelPath.moveTo(x, y);
1000             curLevelPath = mBatLevelPath;
1001             x = w;
1002         } else {
1003             // Figure out where the actual data ends on the screen.
1004             x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
1005             if (x < 0) {
1006                 x = 0;
1007             }
1008         }
1009
1010         finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
1011                 lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn, lastCameraOn,
1012                 lastWifiRunning, lastCpuRunning, lastLinePath);
1013
1014         if (x < w) {
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);
1022             } else {
1023                 mTimeRemainPath.lineTo(mLevelRight, fullY);
1024                 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1025             }
1026             mTimeRemainPath.lineTo(x, emptyY);
1027             mTimeRemainPath.close();
1028         }
1029
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();
1042             }
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);
1059                 }
1060                 addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
1061             }
1062
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();
1072                 }
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();
1078
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);
1088                     }
1089                 }
1090                 addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
1091             }
1092         }
1093
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);
1100         } else {
1101             mDurationString = null;
1102             mDurationStringWidth = 0;
1103         }
1104     }
1105
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))
1111                         / walltimeChange),
1112                 cal, is24hr));
1113     }
1114
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))
1120                         / walltimeChange),
1121                 cal, isDayFirst));
1122     }
1123
1124     @Override
1125     protected void onDraw(Canvas canvas) {
1126         super.onDraw(canvas);
1127
1128         final int width = getWidth();
1129         final int height = getHeight();
1130
1131         //buildBitmap(width, height);
1132
1133         if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
1134         //canvas.drawBitmap(mBitmap, 0, 0, null);
1135         drawChart(canvas, width, height);
1136     }
1137
1138     void buildBitmap(int width, int height) {
1139         if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
1140             return;
1141         }
1142
1143         if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
1144
1145         mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
1146                 Bitmap.Config.ARGB_8888);
1147         mCanvas = new Canvas(mBitmap);
1148         drawChart(mCanvas, width, height);
1149     }
1150
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;
1157
1158         if (DEBUG) {
1159             canvas.drawRect(1, 1, width, height, mDebugRectPaint);
1160         }
1161
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);
1167         }
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);
1172             int lastX = 0;
1173             for (int i=0; i<mTimeLabels.size(); i++) {
1174                 TimeLabel label = mTimeLabels.get(i);
1175                 if (i == 0) {
1176                     int x = label.x - label.width/2;
1177                     if (x < 0) {
1178                         x = 0;
1179                     }
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)) {
1187                         continue;
1188                     }
1189                     TimeLabel nextLabel = mTimeLabels.get(i+1);
1190                     if (x > (width-nextLabel.width-mTextAscent)) {
1191                         continue;
1192                     }
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;
1197                 } else {
1198                     int x = label.x - label.width/2;
1199                     if ((x+label.width) >= width) {
1200                         x = width-1-label.width;
1201                     }
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);
1205                 }
1206             }
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,
1212                     y, mTextPaint);
1213         }
1214
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,
1219                 mHeaderTextPaint);
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,
1226                 mHeaderTextPaint);
1227         mHeaderTextPaint.setTextAlign(textAlignRight);
1228         if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString);
1229         canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint);
1230
1231         if (!mBatGoodPath.isEmpty()) {
1232             if (DEBUG) Log.d(TAG, "Drawing good battery path");
1233             canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
1234         }
1235         if (!mBatWarnPath.isEmpty()) {
1236             if (DEBUG) Log.d(TAG, "Drawing warn battery path");
1237             canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
1238         }
1239         if (!mBatCriticalPath.isEmpty()) {
1240             if (DEBUG) Log.d(TAG, "Drawing critical battery path");
1241             canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
1242         }
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);
1247         }
1248         if (!mScreenOnPath.isEmpty()) {
1249             if (DEBUG) Log.d(TAG, "Drawing screen on path");
1250             canvas.drawPath(mScreenOnPath, mScreenOnPaint);
1251         }
1252         if (!mChargingPath.isEmpty()) {
1253             if (DEBUG) Log.d(TAG, "Drawing charging path");
1254             canvas.drawPath(mChargingPath, mChargingPaint);
1255         }
1256         if (mHaveGps) {
1257             if (!mGpsOnPath.isEmpty()) {
1258                 if (DEBUG) Log.d(TAG, "Drawing gps path");
1259                 canvas.drawPath(mGpsOnPath, mGpsOnPaint);
1260             }
1261         }
1262         if (mHaveFlashlight) {
1263             if (!mFlashlightOnPath.isEmpty()) {
1264                 if (DEBUG) Log.d(TAG, "Drawing flashlight path");
1265                 canvas.drawPath(mFlashlightOnPath, mFlashlightOnPaint);
1266             }
1267         }
1268         if (mHaveCamera) {
1269             if (!mCameraOnPath.isEmpty()) {
1270                 if (DEBUG) Log.d(TAG, "Drawing camera path");
1271                 canvas.drawPath(mCameraOnPath, mCameraOnPaint);
1272             }
1273         }
1274         if (mHaveWifi) {
1275             if (!mWifiRunningPath.isEmpty()) {
1276                 if (DEBUG) Log.d(TAG, "Drawing wifi path");
1277                 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
1278             }
1279         }
1280         if (!mCpuRunningPath.isEmpty()) {
1281             if (DEBUG) Log.d(TAG, "Drawing running path");
1282             canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
1283         }
1284
1285         if (mLargeMode) {
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);
1292             }
1293             if (mHaveGps) {
1294                 canvas.drawText(mGpsOnLabel, textStartX,
1295                         height - mGpsOnOffset - mTextDescent, mTextPaint);
1296             }
1297             if (mHaveFlashlight) {
1298                 canvas.drawText(mFlashlightOnLabel, textStartX,
1299                         height - mFlashlightOnOffset - mTextDescent, mTextPaint);
1300             }
1301             if (mHaveCamera) {
1302                 canvas.drawText(mCameraOnLabel, textStartX,
1303                         height - mCameraOnOffset - mTextDescent, mTextPaint);
1304             }
1305             if (mHaveWifi) {
1306                 canvas.drawText(mWifiRunningLabel, textStartX,
1307                         height - mWifiRunningOffset - mTextDescent, mTextPaint);
1308             }
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);
1316         }
1317
1318         canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
1319                 mLevelBottom+(mThinLineWidth/2), mTextPaint);
1320         if (mLargeMode) {
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);
1325             }
1326         }
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);
1335
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) {
1349                         // okay we give up.
1350                         continue;
1351                     }
1352                 }
1353                 if (left < mLevelLeft) {
1354                     // Won't fit on left, give up.
1355                     continue;
1356                 }
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);
1362             }
1363         }
1364     }
1365 }