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
17package com.android.settings.fuelgauge;
18
19import android.content.Intent;
20import android.graphics.Bitmap;
21import android.graphics.DashPathEffect;
22import android.os.BatteryManager;
23import android.provider.Settings;
24import android.text.format.DateFormat;
25import android.text.format.Formatter;
26import android.util.Log;
27import android.util.TimeUtils;
28import com.android.settings.R;
29import com.android.settings.Utils;
30
31import android.content.Context;
32import android.content.res.ColorStateList;
33import android.content.res.TypedArray;
34import android.graphics.Canvas;
35import android.graphics.Paint;
36import android.graphics.Path;
37import android.graphics.Typeface;
38import android.os.BatteryStats;
39import android.os.SystemClock;
40import android.os.BatteryStats.HistoryItem;
41import android.telephony.ServiceState;
42import android.text.TextPaint;
43import android.util.AttributeSet;
44import android.util.TypedValue;
45import android.view.View;
46import libcore.icu.LocaleData;
47
48import java.util.ArrayList;
49import java.util.Calendar;
50import java.util.Locale;
51
52public 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        long remainingTimeUs = 0;
498        mDischarging = true;
499        if (mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) == 0) {
500            final long drainTime = mStats.computeBatteryTimeRemaining(elapsedRealtimeUs);
501            if (drainTime > 0) {
502                remainingTimeUs = drainTime;
503                String timeString = Formatter.formatShortElapsedTime(getContext(),
504                        drainTime / 1000);
505                mChargeLabelString = getContext().getResources().getString(
506                        R.string.power_discharging_duration, mBatteryLevel, timeString);
507            } else {
508                mChargeLabelString = Utils.formatPercentage(mBatteryLevel);
509            }
510        } else {
511            final long chargeTime = mStats.computeChargeTimeRemaining(elapsedRealtimeUs);
512            final String statusLabel = com.android.settings.Utils.getBatteryStatus(getResources(),
513                    mBatteryBroadcast);
514            final int status = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
515                    BatteryManager.BATTERY_STATUS_UNKNOWN);
516            if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
517                mDischarging = false;
518                remainingTimeUs = chargeTime;
519                String timeString = Formatter.formatShortElapsedTime(getContext(),
520                        chargeTime / 1000);
521                int plugType = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
522                int resId;
523                if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
524                    resId = R.string.power_charging_duration_ac;
525                } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
526                    resId = R.string.power_charging_duration_usb;
527                } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
528                    resId = R.string.power_charging_duration_wireless;
529                } else {
530                    resId = R.string.power_charging_duration;
531                }
532                mChargeLabelString = getContext().getResources().getString(
533                        resId, mBatteryLevel, timeString);
534            } else {
535                mChargeLabelString = getContext().getResources().getString(
536                        R.string.power_charging, mBatteryLevel, statusLabel);
537            }
538        }
539        mDrainString = "";
540        mChargeDurationString = "";
541        setContentDescription(mChargeLabelString);
542
543        int pos = 0;
544        int lastInteresting = 0;
545        byte lastLevel = -1;
546        mBatLow = 0;
547        mBatHigh = 100;
548        mStartWallTime = 0;
549        mEndDataWallTime = 0;
550        mEndWallTime = 0;
551        mHistStart = 0;
552        mHistEnd = 0;
553        long lastWallTime = 0;
554        long lastRealtime = 0;
555        int aggrStates = 0;
556        int aggrStates2 = 0;
557        boolean first = true;
558        if (stats.startIteratingHistoryLocked()) {
559            final HistoryItem rec = new HistoryItem();
560            while (stats.getNextHistoryLocked(rec)) {
561                pos++;
562                if (first) {
563                    first = false;
564                    mHistStart = rec.time;
565                }
566                if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
567                        || rec.cmd == HistoryItem.CMD_RESET) {
568                    // If there is a ridiculously large jump in time, then we won't be
569                    // able to create a good chart with that data, so just ignore the
570                    // times we got before and pretend like our data extends back from
571                    // the time we have now.
572                    // Also, if we are getting a time change and we are less than 5 minutes
573                    // since the start of the history real time, then also use this new
574                    // time to compute the base time, since whatever time we had before is
575                    // pretty much just noise.
576                    if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L))
577                            || rec.time < (mHistStart+(5*60*1000L))) {
578                        mStartWallTime = 0;
579                    }
580                    lastWallTime = rec.currentTime;
581                    lastRealtime = rec.time;
582                    if (mStartWallTime == 0) {
583                        mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
584                    }
585                }
586                if (rec.isDeltaData()) {
587                    if (rec.batteryLevel != lastLevel || pos == 1) {
588                        lastLevel = rec.batteryLevel;
589                    }
590                    lastInteresting = pos;
591                    mHistDataEnd = rec.time;
592                    aggrStates |= rec.states;
593                    aggrStates2 |= rec.states2;
594                }
595            }
596        }
597        mHistEnd = mHistDataEnd + (remainingTimeUs/1000);
598        mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
599        mEndWallTime = mEndDataWallTime + (remainingTimeUs/1000);
600        mNumHist = lastInteresting;
601        mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
602        mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
603                || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
604                        |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
605                        |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
606        if (!com.android.settings.Utils.isWifiOnly(getContext())) {
607            mHavePhoneSignal = true;
608        }
609        if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
610    }
611
612    @Override
613    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
614        mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString);
615        mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString);
616        mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString);
617        mChargeLabelStringWidth = (int)mHeaderTextPaint.measureText(mChargeLabelString);
618        mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString);
619        mTextAscent = (int)mTextPaint.ascent();
620        mTextDescent = (int)mTextPaint.descent();
621        mHeaderTextAscent = (int)mHeaderTextPaint.ascent();
622        mHeaderTextDescent = (int)mHeaderTextPaint.descent();
623        int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent;
624        mHeaderHeight = headerTextHeight*2 - mTextAscent;
625        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
626                getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec));
627    }
628
629    void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
630            int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
631            boolean lastWifiRunning, boolean lastCpuRunning, Path lastPath) {
632        if (curLevelPath != null) {
633            if (lastX >= 0 && lastX < w) {
634                if (lastPath != null) {
635                    lastPath.lineTo(w, y);
636                }
637                curLevelPath.lineTo(w, y);
638            }
639            curLevelPath.lineTo(w, mLevelTop+levelh);
640            curLevelPath.lineTo(startX, mLevelTop+levelh);
641            curLevelPath.close();
642        }
643
644        if (lastCharging) {
645            mChargingPath.lineTo(w, h-mChargingOffset);
646        }
647        if (lastScreenOn) {
648            mScreenOnPath.lineTo(w, h-mScreenOnOffset);
649        }
650        if (lastGpsOn) {
651            mGpsOnPath.lineTo(w, h-mGpsOnOffset);
652        }
653        if (lastWifiRunning) {
654            mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
655        }
656        if (lastCpuRunning) {
657            mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
658        }
659        if (mHavePhoneSignal) {
660            mPhoneSignalChart.finish(w);
661        }
662    }
663
664    private boolean is24Hour() {
665        return DateFormat.is24HourFormat(getContext());
666    }
667
668    private boolean isDayFirst() {
669        String value = Settings.System.getString(mContext.getContentResolver(),
670                Settings.System.DATE_FORMAT);
671        if (value == null) {
672            LocaleData d = LocaleData.get(mContext.getResources().getConfiguration().locale);
673            value = d.shortDateFormat4;
674        }
675        return value.indexOf('M') > value.indexOf('d');
676    }
677
678    /*
679    private void buildTime() {
680        java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(context);
681        final Calendar now = Calendar.getInstance();
682        mDummyDate.setTimeZone(now.getTimeZone());
683        // We use December 31st because it's unambiguous when demonstrating the date format.
684        // We use 13:00 so we can demonstrate the 12/24 hour options.
685        mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 13, 0, 0);
686        Date dummyDate = mDummyDate.getTime();
687        mTimePref.setSummary(DateFormat.getTimeFormat(getActivity()).format(now.getTime()));
688        mTimeZone.setSummary(getTimeZoneText(now.getTimeZone(), true));
689        mDatePref.setSummary(shortDateFormat.format(now.getTime()));
690        mDateFormat.setSummary(shortDateFormat.format(dummyDate));
691        mTime24Pref.setSummary(DateFormat.getTimeFormat(getActivity()).format(dummyDate));
692
693    }
694    */
695
696    @Override
697    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
698        super.onSizeChanged(w, h, oldw, oldh);
699
700        if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
701
702        if (mLastWidth == w && mLastHeight == h) {
703            return;
704        }
705
706        if (mLastWidth == 0 || mLastHeight == 0) {
707            return;
708        }
709
710        if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
711
712        mLastWidth = w;
713        mLastHeight = h;
714        mBitmap = null;
715        mCanvas = null;
716
717        int textHeight = mTextDescent - mTextAscent;
718        if (h > ((textHeight*10)+mChartMinHeight)) {
719            mLargeMode = true;
720            if (h > (textHeight*15)) {
721                // Plenty of room for the chart.
722                mLineWidth = textHeight/2;
723            } else {
724                // Compress lines to make more room for chart.
725                mLineWidth = textHeight/3;
726            }
727        } else {
728            mLargeMode = false;
729            mLineWidth = mThinLineWidth;
730        }
731        if (mLineWidth <= 0) mLineWidth = 1;
732
733        mLevelTop = mHeaderHeight;
734        mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
735        mLevelRight = w;
736        int levelWidth = mLevelRight-mLevelLeft;
737
738        mTextPaint.setStrokeWidth(mThinLineWidth);
739        mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
740        mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
741        mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth);
742        mChargingPaint.setStrokeWidth(mLineWidth);
743        mScreenOnPaint.setStrokeWidth(mLineWidth);
744        mGpsOnPaint.setStrokeWidth(mLineWidth);
745        mWifiRunningPaint.setStrokeWidth(mLineWidth);
746        mCpuRunningPaint.setStrokeWidth(mLineWidth);
747        mDebugRectPaint.setStrokeWidth(1);
748
749        int fullBarOffset = textHeight + mLineWidth;
750
751        if (mLargeMode) {
752            mChargingOffset = mLineWidth;
753            mScreenOnOffset = mChargingOffset + fullBarOffset;
754            mCpuRunningOffset = mScreenOnOffset + fullBarOffset;
755            mWifiRunningOffset = mCpuRunningOffset + fullBarOffset;
756            mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0);
757            mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0);
758            mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0)
759                    + mLineWidth*2 + mLineWidth/2;
760            if (mHavePhoneSignal) {
761                mPhoneSignalChart.init(w);
762            }
763        } else {
764            mScreenOnOffset = mGpsOnOffset = mWifiRunningOffset
765                    = mCpuRunningOffset = mChargingOffset = mPhoneSignalOffset = 0;
766            mLevelOffset = fullBarOffset + mThinLineWidth*4;
767            if (mHavePhoneSignal) {
768                mPhoneSignalChart.init(0);
769            }
770        }
771
772        mBatLevelPath.reset();
773        mBatGoodPath.reset();
774        mBatWarnPath.reset();
775        mTimeRemainPath.reset();
776        mBatCriticalPath.reset();
777        mScreenOnPath.reset();
778        mGpsOnPath.reset();
779        mWifiRunningPath.reset();
780        mCpuRunningPath.reset();
781        mChargingPath.reset();
782
783        mTimeLabels.clear();
784        mDateLabels.clear();
785
786        final long walltimeStart = mStartWallTime;
787        final long walltimeChange = mEndWallTime > walltimeStart
788                ? (mEndWallTime-walltimeStart) : 1;
789        long curWalltime = mStartWallTime;
790        long lastRealtime = 0;
791
792        final int batLow = mBatLow;
793        final int batChange = mBatHigh-mBatLow;
794
795        final int levelh = h - mLevelOffset - mLevelTop;
796        mLevelBottom = mLevelTop + levelh;
797
798        int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
799        int i = 0;
800        Path curLevelPath = null;
801        Path lastLinePath = null;
802        boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
803        boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false;
804        int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
805        final int N = mNumHist;
806        if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) {
807            final HistoryItem rec = new HistoryItem();
808            while (mStats.getNextHistoryLocked(rec) && i < N) {
809                if (rec.isDeltaData()) {
810                    curWalltime += rec.time-lastRealtime;
811                    lastRealtime = rec.time;
812                    x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange);
813                    if (x < 0) {
814                        x = 0;
815                    }
816                    if (false) {
817                        StringBuilder sb = new StringBuilder(128);
818                        sb.append("walloff=");
819                        TimeUtils.formatDuration(curWalltime - walltimeStart, sb);
820                        sb.append(" wallchange=");
821                        TimeUtils.formatDuration(walltimeChange, sb);
822                        sb.append(" x=");
823                        sb.append(x);
824                        Log.d("foo", sb.toString());
825                    }
826                    y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
827
828                    if (lastX != x) {
829                        // We have moved by at least a pixel.
830                        if (lastY != y) {
831                            // Don't plot changes within a pixel.
832                            Path path;
833                            byte value = rec.batteryLevel;
834                            if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
835                            else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
836                            else path = null; //mBatGoodPath;
837
838                            if (path != lastLinePath) {
839                                if (lastLinePath != null) {
840                                    lastLinePath.lineTo(x, y);
841                                }
842                                if (path != null) {
843                                    path.moveTo(x, y);
844                                }
845                                lastLinePath = path;
846                            } else if (path != null) {
847                                path.lineTo(x, y);
848                            }
849
850                            if (curLevelPath == null) {
851                                curLevelPath = mBatLevelPath;
852                                curLevelPath.moveTo(x, y);
853                                startX = x;
854                            } else {
855                                curLevelPath.lineTo(x, y);
856                            }
857                            lastX = x;
858                            lastY = y;
859                        }
860                    }
861
862                    if (mLargeMode) {
863                        final boolean charging =
864                            (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
865                        if (charging != lastCharging) {
866                            if (charging) {
867                                mChargingPath.moveTo(x, h-mChargingOffset);
868                            } else {
869                                mChargingPath.lineTo(x, h-mChargingOffset);
870                            }
871                            lastCharging = charging;
872                        }
873
874                        final boolean screenOn =
875                            (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
876                        if (screenOn != lastScreenOn) {
877                            if (screenOn) {
878                                mScreenOnPath.moveTo(x, h-mScreenOnOffset);
879                            } else {
880                                mScreenOnPath.lineTo(x, h-mScreenOnOffset);
881                            }
882                            lastScreenOn = screenOn;
883                        }
884
885                        final boolean gpsOn =
886                            (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
887                        if (gpsOn != lastGpsOn) {
888                            if (gpsOn) {
889                                mGpsOnPath.moveTo(x, h-mGpsOnOffset);
890                            } else {
891                                mGpsOnPath.lineTo(x, h-mGpsOnOffset);
892                            }
893                            lastGpsOn = gpsOn;
894                        }
895
896                        final int wifiSupplState =
897                            ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
898                                    >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
899                        boolean wifiRunning;
900                        if (lastWifiSupplState != wifiSupplState) {
901                            lastWifiSupplState = wifiSupplState;
902                            switch (wifiSupplState) {
903                                case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED:
904                                case BatteryStats.WIFI_SUPPL_STATE_DORMANT:
905                                case BatteryStats.WIFI_SUPPL_STATE_INACTIVE:
906                                case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
907                                case BatteryStats.WIFI_SUPPL_STATE_INVALID:
908                                case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED:
909                                    wifiRunning = lastWifiSupplRunning = false;
910                                    break;
911                                default:
912                                    wifiRunning = lastWifiSupplRunning = true;
913                                    break;
914                            }
915                        } else {
916                            wifiRunning = lastWifiSupplRunning;
917                        }
918                        if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
919                                |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
920                                |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
921                            wifiRunning = true;
922                        }
923                        if (wifiRunning != lastWifiRunning) {
924                            if (wifiRunning) {
925                                mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
926                            } else {
927                                mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
928                            }
929                            lastWifiRunning = wifiRunning;
930                        }
931
932                        final boolean cpuRunning =
933                            (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
934                        if (cpuRunning != lastCpuRunning) {
935                            if (cpuRunning) {
936                                mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
937                            } else {
938                                mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
939                            }
940                            lastCpuRunning = cpuRunning;
941                        }
942
943                        if (mLargeMode && mHavePhoneSignal) {
944                            int bin;
945                            if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
946                                    >> HistoryItem.STATE_PHONE_STATE_SHIFT)
947                                    == ServiceState.STATE_POWER_OFF) {
948                                bin = 0;
949                            } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
950                                bin = 1;
951                            } else {
952                                bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
953                                        >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
954                                bin += 2;
955                            }
956                            mPhoneSignalChart.addTick(x, bin);
957                        }
958                    }
959
960                } else {
961                    long lastWalltime = curWalltime;
962                    if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
963                            || rec.cmd == HistoryItem.CMD_RESET) {
964                        if (rec.currentTime >= mStartWallTime) {
965                            curWalltime = rec.currentTime;
966                        } else {
967                            curWalltime = mStartWallTime + (rec.time-mHistStart);
968                        }
969                        lastRealtime = rec.time;
970                    }
971
972                    if (rec.cmd != HistoryItem.CMD_OVERFLOW
973                            && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
974                                    || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) {
975                        if (curLevelPath != null) {
976                            finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
977                                    lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
978                                    lastCpuRunning, lastLinePath);
979                            lastX = lastY = -1;
980                            curLevelPath = null;
981                            lastLinePath = null;
982                            lastCharging = lastScreenOn = lastGpsOn = lastCpuRunning = false;
983                        }
984                    }
985                }
986
987                i++;
988            }
989            mStats.finishIteratingHistoryLocked();
990        }
991
992        if (lastY < 0 || lastX < 0) {
993            // Didn't get any data...
994            x = lastX = mLevelLeft;
995            y = lastY = mLevelTop + levelh - ((mBatteryLevel-batLow)*(levelh-1))/batChange;
996            Path path;
997            byte value = (byte)mBatteryLevel;
998            if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
999            else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
1000            else path = null; //mBatGoodPath;
1001            if (path != null) {
1002                path.moveTo(x, y);
1003                lastLinePath = path;
1004            }
1005            mBatLevelPath.moveTo(x, y);
1006            curLevelPath = mBatLevelPath;
1007            x = w;
1008        } else {
1009            // Figure out where the actual data ends on the screen.
1010            x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
1011            if (x < 0) {
1012                x = 0;
1013            }
1014        }
1015
1016        finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
1017                lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
1018                lastCpuRunning, lastLinePath);
1019
1020        if (x < w) {
1021            // If we reserved room for the remaining time, create a final path to draw
1022            // that part of the UI.
1023            mTimeRemainPath.moveTo(x, lastY);
1024            int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange;
1025            int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange;
1026            if (mDischarging) {
1027                mTimeRemainPath.lineTo(mLevelRight, emptyY);
1028            } else {
1029                mTimeRemainPath.lineTo(mLevelRight, fullY);
1030                mTimeRemainPath.lineTo(mLevelRight, emptyY);
1031            }
1032            mTimeRemainPath.lineTo(x, emptyY);
1033            mTimeRemainPath.close();
1034        }
1035
1036        if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) {
1037            // Create the time labels at the bottom.
1038            boolean is24hr = is24Hour();
1039            Calendar calStart = Calendar.getInstance();
1040            calStart.setTimeInMillis(mStartWallTime);
1041            calStart.set(Calendar.MILLISECOND, 0);
1042            calStart.set(Calendar.SECOND, 0);
1043            calStart.set(Calendar.MINUTE, 0);
1044            long startRoundTime = calStart.getTimeInMillis();
1045            if (startRoundTime < mStartWallTime) {
1046                calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1);
1047                startRoundTime = calStart.getTimeInMillis();
1048            }
1049            Calendar calEnd = Calendar.getInstance();
1050            calEnd.setTimeInMillis(mEndWallTime);
1051            calEnd.set(Calendar.MILLISECOND, 0);
1052            calEnd.set(Calendar.SECOND, 0);
1053            calEnd.set(Calendar.MINUTE, 0);
1054            long endRoundTime = calEnd.getTimeInMillis();
1055            if (startRoundTime < endRoundTime) {
1056                addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr);
1057                Calendar calMid = Calendar.getInstance();
1058                calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2));
1059                calMid.set(Calendar.MILLISECOND, 0);
1060                calMid.set(Calendar.SECOND, 0);
1061                calMid.set(Calendar.MINUTE, 0);
1062                long calMidMillis = calMid.getTimeInMillis();
1063                if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1064                    addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr);
1065                }
1066                addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
1067            }
1068
1069            // Create the date labels if the chart includes multiple days
1070            if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) ||
1071                    calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) {
1072                boolean isDayFirst = isDayFirst();
1073                calStart.set(Calendar.HOUR_OF_DAY, 0);
1074                startRoundTime = calStart.getTimeInMillis();
1075                if (startRoundTime < mStartWallTime) {
1076                    calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1);
1077                    startRoundTime = calStart.getTimeInMillis();
1078                }
1079                calEnd.set(Calendar.HOUR_OF_DAY, 0);
1080                endRoundTime = calEnd.getTimeInMillis();
1081                if (startRoundTime < endRoundTime) {
1082                    addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst);
1083                    Calendar calMid = Calendar.getInstance();
1084                    calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2));
1085                    calMid.set(Calendar.HOUR_OF_DAY, 0);
1086                    long calMidMillis = calMid.getTimeInMillis();
1087                    if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
1088                        addDateLabel(calMid, mLevelLeft, mLevelRight, isDayFirst);
1089                    }
1090                }
1091                addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
1092            }
1093        }
1094
1095        if (mTimeLabels.size() < 2) {
1096            // If there are fewer than 2 time labels, then they are useless.  Just
1097            // show an axis label giving the entire duration.
1098            mDurationString = Formatter.formatShortElapsedTime(getContext(),
1099                    mEndWallTime - mStartWallTime);
1100            mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
1101        } else {
1102            mDurationString = null;
1103            mDurationStringWidth = 0;
1104        }
1105    }
1106
1107    void addTimeLabel(Calendar cal, int levelLeft, int levelRight, boolean is24hr) {
1108        final long walltimeStart = mStartWallTime;
1109        final long walltimeChange = mEndWallTime-walltimeStart;
1110        mTimeLabels.add(new TimeLabel(mTextPaint,
1111                levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1112                        / walltimeChange),
1113                cal, is24hr));
1114    }
1115
1116    void addDateLabel(Calendar cal, int levelLeft, int levelRight, boolean isDayFirst) {
1117        final long walltimeStart = mStartWallTime;
1118        final long walltimeChange = mEndWallTime-walltimeStart;
1119        mDateLabels.add(new DateLabel(mTextPaint,
1120                levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
1121                        / walltimeChange),
1122                cal, isDayFirst));
1123    }
1124
1125    @Override
1126    protected void onDraw(Canvas canvas) {
1127        super.onDraw(canvas);
1128
1129        final int width = getWidth();
1130        final int height = getHeight();
1131
1132        //buildBitmap(width, height);
1133
1134        if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
1135        //canvas.drawBitmap(mBitmap, 0, 0, null);
1136        drawChart(canvas, width, height);
1137    }
1138
1139    void buildBitmap(int width, int height) {
1140        if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
1141            return;
1142        }
1143
1144        if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
1145
1146        mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
1147                Bitmap.Config.ARGB_8888);
1148        mCanvas = new Canvas(mBitmap);
1149        drawChart(mCanvas, width, height);
1150    }
1151
1152    void drawChart(Canvas canvas, int width, int height) {
1153        final boolean layoutRtl = isLayoutRtl();
1154        final int textStartX = layoutRtl ? width : 0;
1155        final int textEndX = layoutRtl ? 0 : width;
1156        final Paint.Align textAlignLeft = layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT;
1157        final Paint.Align textAlignRight = layoutRtl ? Paint.Align.LEFT : Paint.Align.RIGHT;
1158
1159        if (DEBUG) {
1160            canvas.drawRect(1, 1, width, height, mDebugRectPaint);
1161        }
1162
1163        if (DEBUG) Log.d(TAG, "Drawing level path.");
1164        canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
1165        if (!mTimeRemainPath.isEmpty()) {
1166            if (DEBUG) Log.d(TAG, "Drawing time remain path.");
1167            canvas.drawPath(mTimeRemainPath, mTimeRemainPaint);
1168        }
1169        if (mTimeLabels.size() > 1) {
1170            int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1171            int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2);
1172            mTextPaint.setTextAlign(Paint.Align.LEFT);
1173            int lastX = 0;
1174            for (int i=0; i<mTimeLabels.size(); i++) {
1175                TimeLabel label = mTimeLabels.get(i);
1176                if (i == 0) {
1177                    int x = label.x - label.width/2;
1178                    if (x < 0) {
1179                        x = 0;
1180                    }
1181                    if (DEBUG) Log.d(TAG, "Drawing left label: " + label.label + " @ " + x);
1182                    canvas.drawText(label.label, x, y, mTextPaint);
1183                    canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1184                    lastX = x + label.width;
1185                } else if (i < (mTimeLabels.size()-1)) {
1186                    int x = label.x - label.width/2;
1187                    if (x < (lastX+mTextAscent)) {
1188                        continue;
1189                    }
1190                    TimeLabel nextLabel = mTimeLabels.get(i+1);
1191                    if (x > (width-nextLabel.width-mTextAscent)) {
1192                        continue;
1193                    }
1194                    if (DEBUG) Log.d(TAG, "Drawing middle label: " + label.label + " @ " + x);
1195                    canvas.drawText(label.label, x, y, mTextPaint);
1196                    canvas.drawLine(label.x, ytick, label.x, ytick + mThinLineWidth, mTextPaint);
1197                    lastX = x + label.width;
1198                } else {
1199                    int x = label.x - label.width/2;
1200                    if ((x+label.width) >= width) {
1201                        x = width-1-label.width;
1202                    }
1203                    if (DEBUG) Log.d(TAG, "Drawing right label: " + label.label + " @ " + x);
1204                    canvas.drawText(label.label, x, y, mTextPaint);
1205                    canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
1206                }
1207            }
1208        } else if (mDurationString != null) {
1209            int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
1210            mTextPaint.setTextAlign(Paint.Align.LEFT);
1211            canvas.drawText(mDurationString,
1212                    mLevelLeft + (mLevelRight-mLevelLeft)/2 - mDurationStringWidth/2,
1213                    y, mTextPaint);
1214        }
1215
1216        int headerTop = -mHeaderTextAscent + (mHeaderTextDescent-mHeaderTextAscent)/3;
1217        mHeaderTextPaint.setTextAlign(textAlignLeft);
1218        if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mChargeLabelString);
1219        canvas.drawText(mChargeLabelString, textStartX, headerTop, 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 (mHaveWifi) {
1263            if (!mWifiRunningPath.isEmpty()) {
1264                if (DEBUG) Log.d(TAG, "Drawing wifi path");
1265                canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
1266            }
1267        }
1268        if (!mCpuRunningPath.isEmpty()) {
1269            if (DEBUG) Log.d(TAG, "Drawing running path");
1270            canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
1271        }
1272
1273        if (mLargeMode) {
1274            if (DEBUG) Log.d(TAG, "Drawing large mode labels");
1275            Paint.Align align = mTextPaint.getTextAlign();
1276            mTextPaint.setTextAlign(textAlignLeft);  // large-mode labels always aligned to start
1277            if (mHavePhoneSignal) {
1278                canvas.drawText(mPhoneSignalLabel, textStartX,
1279                        height - mPhoneSignalOffset - mTextDescent, mTextPaint);
1280            }
1281            if (mHaveGps) {
1282                canvas.drawText(mGpsOnLabel, textStartX,
1283                        height - mGpsOnOffset - mTextDescent, mTextPaint);
1284            }
1285            if (mHaveWifi) {
1286                canvas.drawText(mWifiRunningLabel, textStartX,
1287                        height - mWifiRunningOffset - mTextDescent, mTextPaint);
1288            }
1289            canvas.drawText(mCpuRunningLabel, textStartX,
1290                    height - mCpuRunningOffset - mTextDescent, mTextPaint);
1291            canvas.drawText(mChargingLabel, textStartX,
1292                    height - mChargingOffset - mTextDescent, mTextPaint);
1293            canvas.drawText(mScreenOnLabel, textStartX,
1294                    height - mScreenOnOffset - mTextDescent, mTextPaint);
1295            mTextPaint.setTextAlign(align);
1296        }
1297
1298        canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
1299                mLevelBottom+(mThinLineWidth/2), mTextPaint);
1300        if (mLargeMode) {
1301            for (int i=0; i<10; i++) {
1302                int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10;
1303                canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y,
1304                        mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint);
1305            }
1306        }
1307        if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth
1308                + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString));
1309        canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint);
1310        canvas.drawText(mMinPercentLabelString,
1311                mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth,
1312                mLevelBottom - mThinLineWidth, mTextPaint);
1313        canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width,
1314                mLevelBottom+mThinLineWidth, mTextPaint);
1315
1316        if (mDateLabels.size() > 0) {
1317            int ytop = mLevelTop + mTextAscent;
1318            int ybottom = mLevelBottom;
1319            int lastLeft = mLevelRight;
1320            mTextPaint.setTextAlign(Paint.Align.LEFT);
1321            for (int i=mDateLabels.size()-1; i>=0; i--) {
1322                DateLabel label = mDateLabels.get(i);
1323                int left = label.x - mThinLineWidth;
1324                int x = label.x + mThinLineWidth*2;
1325                if ((x+label.width) >= lastLeft) {
1326                    x = label.x - mThinLineWidth*2 - label.width;
1327                    left = x - mThinLineWidth;
1328                    if (left >= lastLeft) {
1329                        // okay we give up.
1330                        continue;
1331                    }
1332                }
1333                if (left < mLevelLeft) {
1334                    // Won't fit on left, give up.
1335                    continue;
1336                }
1337                mDateLinePath.reset();
1338                mDateLinePath.moveTo(label.x, ytop);
1339                mDateLinePath.lineTo(label.x, ybottom);
1340                canvas.drawPath(mDateLinePath, mDateLinePaint);
1341                canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint);
1342            }
1343        }
1344    }
1345}
1346