OSDN Git Service

am 4fedc0fd: (-s ours) Import translations. DO NOT MERGE
[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.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;
30
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;
47
48 import java.util.ArrayList;
49 import java.util.Calendar;
50 import java.util.Locale;
51
52 public class BatteryHistoryChart extends View {
53     static final boolean DEBUG = false;
54     static final String TAG = "BatteryHistoryChart";
55
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;
59
60     static class ChartData {
61         int[] mColors;
62         Paint[] mPaints;
63
64         int mNumTicks;
65         int[] mTicks;
66         int mLastBin;
67
68         void setColors(int[] colors) {
69             mColors = 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);
75             }
76         }
77
78         void init(int width) {
79             if (width > 0) {
80                 mTicks = new int[width*2];
81             } else {
82                 mTicks = null;
83             }
84             mNumTicks = 0;
85             mLastBin = 0;
86         }
87
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);
91                 mNumTicks++;
92                 mLastBin = bin;
93             }
94         }
95
96         void finish(int width) {
97             if (mLastBin != 0) {
98                 addTick(width, 0);
99             }
100         }
101
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;
109                 if (lastBin != 0) {
110                     canvas.drawRect(lastX, top, x, bottom, mPaints[lastBin]);
111                 }
112                 lastBin = bin;
113                 lastX = x;
114             }
115
116         }
117     }
118
119     static final int SANS = 1;
120     static final int SERIF = 2;
121     static final int MONOSPACE = 3;
122
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;
126
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();
142
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();
154     
155     BatteryStats mStats;
156     Intent mBatteryBroadcast;
157     long mStatsPeriod;
158     int mBatteryLevel;
159     String mMaxPercentLabelString;
160     String mMinPercentLabelString;
161     String mDurationString;
162     String mChargeLabelString;
163     String mChargeDurationString;
164     String mDrainString;
165     String mChargingLabel;
166     String mScreenOnLabel;
167     String mGpsOnLabel;
168     String mWifiRunningLabel;
169     String mCpuRunningLabel;
170     String mPhoneSignalLabel;
171
172     int mChartMinHeight;
173     int mHeaderHeight;
174
175     int mBatteryWarnLevel;
176     int mBatteryCriticalLevel;
177
178     int mTextAscent;
179     int mTextDescent;
180     int mHeaderTextAscent;
181     int mHeaderTextDescent;
182     int mMaxPercentLabelStringWidth;
183     int mMinPercentLabelStringWidth;
184     int mDurationStringWidth;
185     int mChargeLabelStringWidth;
186     int mChargeDurationStringWidth;
187     int mDrainStringWidth;
188
189     boolean mLargeMode;
190
191     int mLastWidth = -1;
192     int mLastHeight = -1;
193
194     int mLineWidth;
195     int mThinLineWidth;
196     int mChargingOffset;
197     int mScreenOnOffset;
198     int mGpsOnOffset;
199     int mWifiRunningOffset;
200     int mCpuRunningOffset;
201     int mPhoneSignalOffset;
202     int mLevelOffset;
203     int mLevelTop;
204     int mLevelBottom;
205     int mLevelLeft;
206     int mLevelRight;
207
208     int mNumHist;
209     long mHistStart;
210     long mHistDataEnd;
211     long mHistEnd;
212     long mStartWallTime;
213     long mEndDataWallTime;
214     long mEndWallTime;
215     boolean mDischarging;
216     int mBatLow;
217     int mBatHigh;
218     boolean mHaveWifi;
219     boolean mHaveGps;
220     boolean mHavePhoneSignal;
221
222     final ArrayList<TimeLabel> mTimeLabels = new ArrayList<TimeLabel>();
223     final ArrayList<DateLabel> mDateLabels = new ArrayList<DateLabel>();
224
225     Bitmap mBitmap;
226     Canvas mCanvas;
227
228     static class TextAttrs {
229         ColorStateList textColor = null;
230         int textSize = 15;
231         int typefaceIndex = -1;
232         int styleIndex = -1;
233
234         void retrieve(Context context, TypedArray from, int index) {
235             TypedArray appearance = null;
236             int ap = from.getResourceId(index, -1);
237             if (ap != -1) {
238                 appearance = context.obtainStyledAttributes(ap,
239                                     com.android.internal.R.styleable.TextAppearance);
240             }
241             if (appearance != null) {
242                 int n = appearance.getIndexCount();
243                 for (int i = 0; i < n; i++) {
244                     int attr = appearance.getIndex(i);
245
246                     switch (attr) {
247                     case com.android.internal.R.styleable.TextAppearance_textColor:
248                         textColor = appearance.getColorStateList(attr);
249                         break;
250
251                     case com.android.internal.R.styleable.TextAppearance_textSize:
252                         textSize = appearance.getDimensionPixelSize(attr, textSize);
253                         break;
254
255                     case com.android.internal.R.styleable.TextAppearance_typeface:
256                         typefaceIndex = appearance.getInt(attr, -1);
257                         break;
258
259                     case com.android.internal.R.styleable.TextAppearance_textStyle:
260                         styleIndex = appearance.getInt(attr, -1);
261                         break;
262                     }
263                 }
264
265                 appearance.recycle();
266             }
267         }
268
269         void apply(Context context, TextPaint paint) {
270             paint.density = context.getResources().getDisplayMetrics().density;
271             paint.setCompatibilityScaling(
272                     context.getResources().getCompatibilityInfo().applicationScale);
273
274             paint.setColor(textColor.getDefaultColor());
275             paint.setTextSize(textSize);
276
277             Typeface tf = null;
278             switch (typefaceIndex) {
279                 case SANS:
280                     tf = Typeface.SANS_SERIF;
281                     break;
282
283                 case SERIF:
284                     tf = Typeface.SERIF;
285                     break;
286
287                 case MONOSPACE:
288                     tf = Typeface.MONOSPACE;
289                     break;
290             }
291
292             setTypeface(paint, tf, styleIndex);
293         }
294
295         public void setTypeface(TextPaint paint, Typeface tf, int style) {
296             if (style > 0) {
297                 if (tf == null) {
298                     tf = Typeface.defaultFromStyle(style);
299                 } else {
300                     tf = Typeface.create(tf, style);
301                 }
302
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);
309             } else {
310                 paint.setFakeBoldText(false);
311                 paint.setTextSkewX(0);
312                 paint.setTypeface(tf);
313             }
314         }
315     }
316
317     static class TimeLabel {
318         final int x;
319         final String label;
320         final int width;
321
322         TimeLabel(TextPaint paint, int x, Calendar cal, boolean use24hr) {
323             this.x = x;
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);
328         }
329     }
330
331     static class DateLabel {
332         final int x;
333         final String label;
334         final int width;
335
336         DateLabel(TextPaint paint, int x, Calendar cal, boolean dayFirst) {
337             this.x = x;
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);
342         }
343     }
344
345     public BatteryHistoryChart(Context context, AttributeSet attrs) {
346         super(context, attrs);
347
348         if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!");
349
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);
354
355         mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
356                 2, getResources().getDisplayMetrics());
357
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);
381
382         TypedArray a =
383             context.obtainStyledAttributes(
384                 attrs, R.styleable.BatteryHistoryChart, 0, 0);
385
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);
390
391         int shadowcolor = 0;
392         float dx=0, dy=0, r=0;
393         
394         int n = a.getIndexCount();
395         for (int i = 0; i < n; i++) {
396             int attr = a.getIndex(i);
397
398             switch (attr) {
399                 case R.styleable.BatteryHistoryChart_android_shadowColor:
400                     shadowcolor = a.getInt(attr, 0);
401                     break;
402
403                 case R.styleable.BatteryHistoryChart_android_shadowDx:
404                     dx = a.getFloat(attr, 0);
405                     break;
406
407                 case R.styleable.BatteryHistoryChart_android_shadowDy:
408                     dy = a.getFloat(attr, 0);
409                     break;
410
411                 case R.styleable.BatteryHistoryChart_android_shadowRadius:
412                     r = a.getFloat(attr, 0);
413                     break;
414
415                 case R.styleable.BatteryHistoryChart_android_textColor:
416                     mainTextAttrs.textColor = a.getColorStateList(attr);
417                     headTextAttrs.textColor = a.getColorStateList(attr);
418                     break;
419
420                 case R.styleable.BatteryHistoryChart_android_textSize:
421                     mainTextAttrs.textSize = a.getDimensionPixelSize(attr, mainTextAttrs.textSize);
422                     headTextAttrs.textSize = a.getDimensionPixelSize(attr, headTextAttrs.textSize);
423                     break;
424
425                 case R.styleable.BatteryHistoryChart_android_typeface:
426                     mainTextAttrs.typefaceIndex = a.getInt(attr, mainTextAttrs.typefaceIndex);
427                     headTextAttrs.typefaceIndex = a.getInt(attr, headTextAttrs.typefaceIndex);
428                     break;
429
430                 case R.styleable.BatteryHistoryChart_android_textStyle:
431                     mainTextAttrs.styleIndex = a.getInt(attr, mainTextAttrs.styleIndex);
432                     headTextAttrs.styleIndex = a.getInt(attr, headTextAttrs.styleIndex);
433                     break;
434
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));
442                     break;
443
444                 case R.styleable.BatteryHistoryChart_barPredictionColor:
445                     mTimeRemainPaint.setColor(a.getInt(attr, 0));
446                     break;
447
448                 case R.styleable.BatteryHistoryChart_chartMinHeight:
449                     mChartMinHeight = a.getDimensionPixelSize(attr, 0);
450                     break;
451             }
452         }
453         
454         a.recycle();
455         
456         mainTextAttrs.apply(context, mTextPaint);
457         headTextAttrs.apply(context, mHeaderTextPaint);
458
459         mDateLinePaint.set(mTextPaint);
460         mDateLinePaint.setStyle(Paint.Style.STROKE);
461         int hairlineWidth = mThinLineWidth/2;
462         if (hairlineWidth < 1) {
463             hairlineWidth = 1;
464         }
465         mDateLinePaint.setStrokeWidth(hairlineWidth);
466         mDateLinePaint.setPathEffect(new DashPathEffect(new float[] {
467                 mThinLineWidth * 2, mThinLineWidth * 2 }, 0));
468
469         if (shadowcolor != 0) {
470             mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
471             mHeaderTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
472         }
473     }
474
475     void setStats(BatteryStats stats, Intent broadcast) {
476         mStats = stats;
477         mBatteryBroadcast = broadcast;
478
479         if (DEBUG) Log.d(TAG, "Setting stats...");
480
481         final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
482
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);
492
493         mMaxPercentLabelString = Utils.formatPercentage(100);
494         mMinPercentLabelString = Utils.formatPercentage(0);
495
496         mBatteryLevel = com.android.settings.Utils.getBatteryLevel(mBatteryBroadcast);
497         String batteryPercentString = Utils.formatPercentage(mBatteryLevel);
498         long remainingTimeUs = 0;
499         mDischarging = true;
500         if (mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) == 0) {
501             final long drainTime = mStats.computeBatteryTimeRemaining(elapsedRealtimeUs);
502             if (drainTime > 0) {
503                 remainingTimeUs = drainTime;
504                 String timeString = Formatter.formatShortElapsedTime(getContext(),
505                         drainTime / 1000);
506                 mChargeLabelString = getContext().getResources().getString(
507                         R.string.power_discharging_duration, batteryPercentString, timeString);
508             } else {
509                 mChargeLabelString = batteryPercentString;
510             }
511         } else {
512             final long chargeTime = mStats.computeChargeTimeRemaining(elapsedRealtimeUs);
513             final String statusLabel = com.android.settings.Utils.getBatteryStatus(getResources(),
514                     mBatteryBroadcast);
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(),
521                         chargeTime / 1000);
522                 int plugType = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
523                 int resId;
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;
530                 } else {
531                     resId = R.string.power_charging_duration;
532                 }
533                 mChargeLabelString = getContext().getResources().getString(
534                         resId, batteryPercentString, timeString);
535             } else {
536                 mChargeLabelString = getContext().getResources().getString(
537                         R.string.power_charging, batteryPercentString, statusLabel);
538             }
539         }
540         mDrainString = "";
541         mChargeDurationString = "";
542         setContentDescription(mChargeLabelString);
543
544         int pos = 0;
545         int lastInteresting = 0;
546         byte lastLevel = -1;
547         mBatLow = 0;
548         mBatHigh = 100;
549         mStartWallTime = 0;
550         mEndDataWallTime = 0;
551         mEndWallTime = 0;
552         mHistStart = 0;
553         mHistEnd = 0;
554         long lastWallTime = 0;
555         long lastRealtime = 0;
556         int aggrStates = 0;
557         int aggrStates2 = 0;
558         boolean first = true;
559         if (stats.startIteratingHistoryLocked()) {
560             final HistoryItem rec = new HistoryItem();
561             while (stats.getNextHistoryLocked(rec)) {
562                 pos++;
563                 if (first) {
564                     first = false;
565                     mHistStart = rec.time;
566                 }
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))) {
579                         mStartWallTime = 0;
580                     }
581                     lastWallTime = rec.currentTime;
582                     lastRealtime = rec.time;
583                     if (mStartWallTime == 0) {
584                         mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
585                     }
586                 }
587                 if (rec.isDeltaData()) {
588                     if (rec.batteryLevel != lastLevel || pos == 1) {
589                         lastLevel = rec.batteryLevel;
590                     }
591                     lastInteresting = pos;
592                     mHistDataEnd = rec.time;
593                     aggrStates |= rec.states;
594                     aggrStates2 |= rec.states2;
595                 }
596             }
597         }
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;
609         }
610         if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
611     }
612
613     @Override
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));
628     }
629
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);
637                 }
638                 curLevelPath.lineTo(w, y);
639             }
640             curLevelPath.lineTo(w, mLevelTop+levelh);
641             curLevelPath.lineTo(startX, mLevelTop+levelh);
642             curLevelPath.close();
643         }
644         
645         if (lastCharging) {
646             mChargingPath.lineTo(w, h-mChargingOffset);
647         }
648         if (lastScreenOn) {
649             mScreenOnPath.lineTo(w, h-mScreenOnOffset);
650         }
651         if (lastGpsOn) {
652             mGpsOnPath.lineTo(w, h-mGpsOnOffset);
653         }
654         if (lastWifiRunning) {
655             mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
656         }
657         if (lastCpuRunning) {
658             mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
659         }
660         if (mHavePhoneSignal) {
661             mPhoneSignalChart.finish(w);
662         }
663     }
664
665     private boolean is24Hour() {
666         return DateFormat.is24HourFormat(getContext());
667     }
668
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');
673     }
674
675     @Override
676     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
677         super.onSizeChanged(w, h, oldw, oldh);
678
679         if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
680
681         if (mLastWidth == w && mLastHeight == h) {
682             return;
683         }
684
685         if (mLastWidth == 0 || mLastHeight == 0) {
686             return;
687         }
688
689         if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
690
691         mLastWidth = w;
692         mLastHeight = h;
693         mBitmap = null;
694         mCanvas = null;
695
696         int textHeight = mTextDescent - mTextAscent;
697         if (h > ((textHeight*10)+mChartMinHeight)) {
698             mLargeMode = true;
699             if (h > (textHeight*15)) {
700                 // Plenty of room for the chart.
701                 mLineWidth = textHeight/2;
702             } else {
703                 // Compress lines to make more room for chart.
704                 mLineWidth = textHeight/3;
705             }
706         } else {
707             mLargeMode = false;
708             mLineWidth = mThinLineWidth;
709         }
710         if (mLineWidth <= 0) mLineWidth = 1;
711
712         mLevelTop = mHeaderHeight;
713         mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
714         mLevelRight = w;
715         int levelWidth = mLevelRight-mLevelLeft;
716
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);
727
728         int fullBarOffset = textHeight + mLineWidth;
729
730         if (mLargeMode) {
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);
741             }
742         } else {
743             mScreenOnOffset = mGpsOnOffset = mWifiRunningOffset
744                     = mCpuRunningOffset = mChargingOffset = mPhoneSignalOffset = 0;
745             mLevelOffset = fullBarOffset + mThinLineWidth*4;
746             if (mHavePhoneSignal) {
747                 mPhoneSignalChart.init(0);
748             }
749         }
750
751         mBatLevelPath.reset();
752         mBatGoodPath.reset();
753         mBatWarnPath.reset();
754         mTimeRemainPath.reset();
755         mBatCriticalPath.reset();
756         mScreenOnPath.reset();
757         mGpsOnPath.reset();
758         mWifiRunningPath.reset();
759         mCpuRunningPath.reset();
760         mChargingPath.reset();
761
762         mTimeLabels.clear();
763         mDateLabels.clear();
764
765         final long walltimeStart = mStartWallTime;
766         final long walltimeChange = mEndWallTime > walltimeStart
767                 ? (mEndWallTime-walltimeStart) : 1;
768         long curWalltime = mStartWallTime;
769         long lastRealtime = 0;
770
771         final int batLow = mBatLow;
772         final int batChange = mBatHigh-mBatLow;
773
774         final int levelh = h - mLevelOffset - mLevelTop;
775         mLevelBottom = mLevelTop + levelh;
776
777         int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
778         int i = 0;
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);
792                     if (x < 0) {
793                         x = 0;
794                     }
795                     if (false) {
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);
801                         sb.append(" x=");
802                         sb.append(x);
803                         Log.d("foo", sb.toString());
804                     }
805                     y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
806
807                     if (lastX != x) {
808                         // We have moved by at least a pixel.
809                         if (lastY != y) {
810                             // Don't plot changes within a pixel.
811                             Path path;
812                             byte value = rec.batteryLevel;
813                             if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
814                             else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
815                             else path = null; //mBatGoodPath;
816
817                             if (path != lastLinePath) {
818                                 if (lastLinePath != null) {
819                                     lastLinePath.lineTo(x, y);
820                                 }
821                                 if (path != null) {
822                                     path.moveTo(x, y);
823                                 }
824                                 lastLinePath = path;
825                             } else if (path != null) {
826                                 path.lineTo(x, y);
827                             }
828
829                             if (curLevelPath == null) {
830                                 curLevelPath = mBatLevelPath;
831                                 curLevelPath.moveTo(x, y);
832                                 startX = x;
833                             } else {
834                                 curLevelPath.lineTo(x, y);
835                             }
836                             lastX = x;
837                             lastY = y;
838                         }
839                     }
840
841                     if (mLargeMode) {
842                         final boolean charging =
843                             (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
844                         if (charging != lastCharging) {
845                             if (charging) {
846                                 mChargingPath.moveTo(x, h-mChargingOffset);
847                             } else {
848                                 mChargingPath.lineTo(x, h-mChargingOffset);
849                             }
850                             lastCharging = charging;
851                         }
852
853                         final boolean screenOn =
854                             (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
855                         if (screenOn != lastScreenOn) {
856                             if (screenOn) {
857                                 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
858                             } else {
859                                 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
860                             }
861                             lastScreenOn = screenOn;
862                         }
863
864                         final boolean gpsOn =
865                             (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
866                         if (gpsOn != lastGpsOn) {
867                             if (gpsOn) {
868                                 mGpsOnPath.moveTo(x, h-mGpsOnOffset);
869                             } else {
870                                 mGpsOnPath.lineTo(x, h-mGpsOnOffset);
871                             }
872                             lastGpsOn = gpsOn;
873                         }
874
875                         final int wifiSupplState =
876                             ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
877                                     >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
878                         boolean wifiRunning;
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;
889                                     break;
890                                 default:
891                                     wifiRunning = lastWifiSupplRunning = true;
892                                     break;
893                             }
894                         } else {
895                             wifiRunning = lastWifiSupplRunning;
896                         }
897                         if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
898                                 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
899                                 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
900                             wifiRunning = true;
901                         }
902                         if (wifiRunning != lastWifiRunning) {
903                             if (wifiRunning) {
904                                 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
905                             } else {
906                                 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
907                             }
908                             lastWifiRunning = wifiRunning;
909                         }
910
911                         final boolean cpuRunning =
912                             (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
913                         if (cpuRunning != lastCpuRunning) {
914                             if (cpuRunning) {
915                                 mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
916                             } else {
917                                 mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
918                             }
919                             lastCpuRunning = cpuRunning;
920                         }
921
922                         if (mLargeMode && mHavePhoneSignal) {
923                             int bin;
924                             if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
925                                     >> HistoryItem.STATE_PHONE_STATE_SHIFT)
926                                     == ServiceState.STATE_POWER_OFF) {
927                                 bin = 0;
928                             } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
929                                 bin = 1;
930                             } else {
931                                 bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
932                                         >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
933                                 bin += 2;
934                             }
935                             mPhoneSignalChart.addTick(x, bin);
936                         }
937                     }
938
939                 } else {
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;
945                         } else {
946                             curWalltime = mStartWallTime + (rec.time-mHistStart);
947                         }
948                         lastRealtime = rec.time;
949                     }
950
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);
958                             lastX = lastY = -1;
959                             curLevelPath = null;
960                             lastLinePath = null;
961                             lastCharging = lastScreenOn = lastGpsOn = lastCpuRunning = false;
962                         }
963                     }
964                 }
965                 
966                 i++;
967             }
968             mStats.finishIteratingHistoryLocked();
969         }
970
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;
975             Path path;
976             byte value = (byte)mBatteryLevel;
977             if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
978             else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
979             else path = null; //mBatGoodPath;
980             if (path != null) {
981                 path.moveTo(x, y);
982                 lastLinePath = path;
983             }
984             mBatLevelPath.moveTo(x, y);
985             curLevelPath = mBatLevelPath;
986             x = w;
987         } else {
988             // Figure out where the actual data ends on the screen.
989             x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
990             if (x < 0) {
991                 x = 0;
992             }
993         }
994
995         finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
996                 lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
997                 lastCpuRunning, lastLinePath);
998
999         if (x < w) {
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;
1005             if (mDischarging) {
1006                 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1007             } else {
1008                 mTimeRemainPath.lineTo(mLevelRight, fullY);
1009                 mTimeRemainPath.lineTo(mLevelRight, emptyY);
1010             }
1011             mTimeRemainPath.lineTo(x, emptyY);
1012             mTimeRemainPath.close();
1013         }
1014
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();
1027             }
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);
1044                 }
1045                 addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
1046             }
1047
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();
1057                 }
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);
1068                     }
1069                 }
1070                 addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
1071             }
1072         }
1073
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);
1080         } else {
1081             mDurationString = null;
1082             mDurationStringWidth = 0;
1083         }
1084     }
1085
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))
1091                         / walltimeChange),
1092                 cal, is24hr));
1093     }
1094
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))
1100                         / walltimeChange),
1101                 cal, isDayFirst));
1102     }
1103
1104     @Override
1105     protected void onDraw(Canvas canvas) {
1106         super.onDraw(canvas);
1107
1108         final int width = getWidth();
1109         final int height = getHeight();
1110
1111         //buildBitmap(width, height);
1112
1113         if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
1114         //canvas.drawBitmap(mBitmap, 0, 0, null);
1115         drawChart(canvas, width, height);
1116     }
1117
1118     void buildBitmap(int width, int height) {
1119         if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
1120             return;
1121         }
1122
1123         if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
1124
1125         mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
1126                 Bitmap.Config.ARGB_8888);
1127         mCanvas = new Canvas(mBitmap);
1128         drawChart(mCanvas, width, height);
1129     }
1130
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;
1137
1138         if (DEBUG) {
1139             canvas.drawRect(1, 1, width, height, mDebugRectPaint);
1140         }
1141
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);
1147         }
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);
1152             int lastX = 0;
1153             for (int i=0; i<mTimeLabels.size(); i++) {
1154                 TimeLabel label = mTimeLabels.get(i);
1155                 if (i == 0) {
1156                     int x = label.x - label.width/2;
1157                     if (x < 0) {
1158                         x = 0;
1159                     }
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)) {
1167                         continue;
1168                     }
1169                     TimeLabel nextLabel = mTimeLabels.get(i+1);
1170                     if (x > (width-nextLabel.width-mTextAscent)) {
1171                         continue;
1172                     }
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;
1177                 } else {
1178                     int x = label.x - label.width/2;
1179                     if ((x+label.width) >= width) {
1180                         x = width-1-label.width;
1181                     }
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);
1185                 }
1186             }
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,
1192                     y, mTextPaint);
1193         }
1194
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,
1205                 mHeaderTextPaint);
1206         mHeaderTextPaint.setTextAlign(textAlignRight);
1207         if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString);
1208         canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint);
1209
1210         if (!mBatGoodPath.isEmpty()) {
1211             if (DEBUG) Log.d(TAG, "Drawing good battery path");
1212             canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
1213         }
1214         if (!mBatWarnPath.isEmpty()) {
1215             if (DEBUG) Log.d(TAG, "Drawing warn battery path");
1216             canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
1217         }
1218         if (!mBatCriticalPath.isEmpty()) {
1219             if (DEBUG) Log.d(TAG, "Drawing critical battery path");
1220             canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
1221         }
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);
1226         }
1227         if (!mScreenOnPath.isEmpty()) {
1228             if (DEBUG) Log.d(TAG, "Drawing screen on path");
1229             canvas.drawPath(mScreenOnPath, mScreenOnPaint);
1230         }
1231         if (!mChargingPath.isEmpty()) {
1232             if (DEBUG) Log.d(TAG, "Drawing charging path");
1233             canvas.drawPath(mChargingPath, mChargingPaint);
1234         }
1235         if (mHaveGps) {
1236             if (!mGpsOnPath.isEmpty()) {
1237                 if (DEBUG) Log.d(TAG, "Drawing gps path");
1238                 canvas.drawPath(mGpsOnPath, mGpsOnPaint);
1239             }
1240         }
1241         if (mHaveWifi) {
1242             if (!mWifiRunningPath.isEmpty()) {
1243                 if (DEBUG) Log.d(TAG, "Drawing wifi path");
1244                 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
1245             }
1246         }
1247         if (!mCpuRunningPath.isEmpty()) {
1248             if (DEBUG) Log.d(TAG, "Drawing running path");
1249             canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
1250         }
1251
1252         if (mLargeMode) {
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);
1259             }
1260             if (mHaveGps) {
1261                 canvas.drawText(mGpsOnLabel, textStartX,
1262                         height - mGpsOnOffset - mTextDescent, mTextPaint);
1263             }
1264             if (mHaveWifi) {
1265                 canvas.drawText(mWifiRunningLabel, textStartX,
1266                         height - mWifiRunningOffset - mTextDescent, mTextPaint);
1267             }
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);
1275         }
1276
1277         canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
1278                 mLevelBottom+(mThinLineWidth/2), mTextPaint);
1279         if (mLargeMode) {
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);
1284             }
1285         }
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);
1294
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) {
1308                         // okay we give up.
1309                         continue;
1310                     }
1311                 }
1312                 if (left < mLevelLeft) {
1313                     // Won't fit on left, give up.
1314                     continue;
1315                 }
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);
1321             }
1322         }
1323     }
1324 }