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 com.android.settings.R;
20
21import android.content.Context;
22import android.content.res.ColorStateList;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Paint;
26import android.graphics.Path;
27import android.graphics.Typeface;
28import android.os.BatteryStats;
29import android.os.SystemClock;
30import android.os.BatteryStats.HistoryItem;
31import android.telephony.ServiceState;
32import android.text.TextPaint;
33import android.util.AttributeSet;
34import android.util.TypedValue;
35import android.view.View;
36
37public class BatteryHistoryChart extends View {
38    static final int CHART_DATA_X_MASK = 0x0000ffff;
39    static final int CHART_DATA_BIN_MASK = 0xffff0000;
40    static final int CHART_DATA_BIN_SHIFT = 16;
41
42    static class ChartData {
43        int[] mColors;
44        Paint[] mPaints;
45
46        int mNumTicks;
47        int[] mTicks;
48        int mLastBin;
49
50        void setColors(int[] colors) {
51            mColors = colors;
52            mPaints = new Paint[colors.length];
53            for (int i=0; i<colors.length; i++) {
54                mPaints[i] = new Paint();
55                mPaints[i].setColor(colors[i]);
56                mPaints[i].setStyle(Paint.Style.FILL);
57            }
58        }
59
60        void init(int width) {
61            if (width > 0) {
62                mTicks = new int[width*2];
63            } else {
64                mTicks = null;
65            }
66            mNumTicks = 0;
67            mLastBin = 0;
68        }
69
70        void addTick(int x, int bin) {
71            if (bin != mLastBin && mNumTicks < mTicks.length) {
72                mTicks[mNumTicks] = x | bin << CHART_DATA_BIN_SHIFT;
73                mNumTicks++;
74                mLastBin = bin;
75            }
76        }
77
78        void finish(int width) {
79            if (mLastBin != 0) {
80                addTick(width, 0);
81            }
82        }
83
84        void draw(Canvas canvas, int top, int height) {
85            int lastBin=0, lastX=0;
86            int bottom = top + height;
87            for (int i=0; i<mNumTicks; i++) {
88                int tick = mTicks[i];
89                int x = tick&CHART_DATA_X_MASK;
90                int bin = (tick&CHART_DATA_BIN_MASK) >> CHART_DATA_BIN_SHIFT;
91                if (lastBin != 0) {
92                    canvas.drawRect(lastX, top, x, bottom, mPaints[lastBin]);
93                }
94                lastBin = bin;
95                lastX = x;
96            }
97
98        }
99    }
100
101    static final int SANS = 1;
102    static final int SERIF = 2;
103    static final int MONOSPACE = 3;
104
105    static final int BATTERY_WARN = 29;
106    static final int BATTERY_CRITICAL = 14;
107
108    // First value if for phone off; first value is "scanning"; following values
109    // are battery stats signal strength buckets.
110    static final int NUM_PHONE_SIGNALS = 7;
111
112    final Paint mBatteryBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
113    final Paint mBatteryGoodPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
114    final Paint mBatteryWarnPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
115    final Paint mBatteryCriticalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
116    final Paint mChargingPaint = new Paint();
117    final Paint mScreenOnPaint = new Paint();
118    final Paint mGpsOnPaint = new Paint();
119    final Paint mWifiRunningPaint = new Paint();
120    final Paint mWakeLockPaint = new Paint();
121    final ChartData mPhoneSignalChart = new ChartData();
122    final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
123
124    final Path mBatLevelPath = new Path();
125    final Path mBatGoodPath = new Path();
126    final Path mBatWarnPath = new Path();
127    final Path mBatCriticalPath = new Path();
128    final Path mChargingPath = new Path();
129    final Path mScreenOnPath = new Path();
130    final Path mGpsOnPath = new Path();
131    final Path mWifiRunningPath = new Path();
132    final Path mWakeLockPath = new Path();
133
134    int mFontSize;
135
136    BatteryStats mStats;
137    long mStatsPeriod;
138    String mDurationString;
139    String mTotalDurationString;
140    String mChargingLabel;
141    String mScreenOnLabel;
142    String mGpsOnLabel;
143    String mWifiRunningLabel;
144    String mWakeLockLabel;
145    String mPhoneSignalLabel;
146
147    int mTextAscent;
148    int mTextDescent;
149    int mDurationStringWidth;
150    int mTotalDurationStringWidth;
151
152    boolean mLargeMode;
153
154    int mLineWidth;
155    int mThinLineWidth;
156    int mChargingOffset;
157    int mScreenOnOffset;
158    int mGpsOnOffset;
159    int mWifiRunningOffset;
160    int mWakeLockOffset;
161    int mPhoneSignalOffset;
162    int mLevelOffset;
163    int mLevelTop;
164    int mLevelBottom;
165    static final int PHONE_SIGNAL_X_MASK = CHART_DATA_X_MASK;
166    static final int PHONE_SIGNAL_BIN_MASK = CHART_DATA_BIN_MASK;
167    static final int PHONE_SIGNAL_BIN_SHIFT = CHART_DATA_BIN_SHIFT;
168
169    int mNumHist;
170    long mHistStart;
171    long mHistEnd;
172    int mBatLow;
173    int mBatHigh;
174    boolean mHaveWifi;
175    boolean mHaveGps;
176    boolean mHavePhoneSignal;
177
178    public BatteryHistoryChart(Context context, AttributeSet attrs) {
179        super(context, attrs);
180
181        mBatteryBackgroundPaint.setARGB(255, 128, 128, 128);
182        mBatteryBackgroundPaint.setStyle(Paint.Style.FILL);
183        mBatteryGoodPaint.setARGB(128, 0, 255, 0);
184        mBatteryGoodPaint.setStyle(Paint.Style.STROKE);
185        mBatteryWarnPaint.setARGB(128, 255, 255, 0);
186        mBatteryWarnPaint.setStyle(Paint.Style.STROKE);
187        mBatteryCriticalPaint.setARGB(192, 255, 0, 0);
188        mBatteryCriticalPaint.setStyle(Paint.Style.STROKE);
189        mChargingPaint.setARGB(255, 0, 128, 0);
190        mChargingPaint.setStyle(Paint.Style.STROKE);
191        mScreenOnPaint.setStyle(Paint.Style.STROKE);
192        mGpsOnPaint.setStyle(Paint.Style.STROKE);
193        mWifiRunningPaint.setStyle(Paint.Style.STROKE);
194        mWakeLockPaint.setStyle(Paint.Style.STROKE);
195        mPhoneSignalChart.setColors(new int[] {
196                0x00000000, 0xffa00000, 0xffa0a000, 0xff808020,
197                0xff808040, 0xff808060, 0xff008000
198        });
199
200        mTextPaint.density = getResources().getDisplayMetrics().density;
201        mTextPaint.setCompatibilityScaling(
202                getResources().getCompatibilityInfo().applicationScale);
203
204        TypedArray a =
205            context.obtainStyledAttributes(
206                attrs, R.styleable.BatteryHistoryChart, 0, 0);
207
208        ColorStateList textColor = null;
209        int textSize = 15;
210        int typefaceIndex = -1;
211        int styleIndex = -1;
212
213        TypedArray appearance = null;
214        int ap = a.getResourceId(R.styleable.BatteryHistoryChart_android_textAppearance, -1);
215        if (ap != -1) {
216            appearance = context.obtainStyledAttributes(ap,
217                                com.android.internal.R.styleable.
218                                TextAppearance);
219        }
220        if (appearance != null) {
221            int n = appearance.getIndexCount();
222            for (int i = 0; i < n; i++) {
223                int attr = appearance.getIndex(i);
224
225                switch (attr) {
226                case com.android.internal.R.styleable.TextAppearance_textColor:
227                    textColor = appearance.getColorStateList(attr);
228                    break;
229
230                case com.android.internal.R.styleable.TextAppearance_textSize:
231                    textSize = appearance.getDimensionPixelSize(attr, textSize);
232                    break;
233
234                case com.android.internal.R.styleable.TextAppearance_typeface:
235                    typefaceIndex = appearance.getInt(attr, -1);
236                    break;
237
238                case com.android.internal.R.styleable.TextAppearance_textStyle:
239                    styleIndex = appearance.getInt(attr, -1);
240                    break;
241                }
242            }
243
244            appearance.recycle();
245        }
246
247        int shadowcolor = 0;
248        float dx=0, dy=0, r=0;
249
250        int n = a.getIndexCount();
251        for (int i = 0; i < n; i++) {
252            int attr = a.getIndex(i);
253
254            switch (attr) {
255                case R.styleable.BatteryHistoryChart_android_shadowColor:
256                    shadowcolor = a.getInt(attr, 0);
257                    break;
258
259                case R.styleable.BatteryHistoryChart_android_shadowDx:
260                    dx = a.getFloat(attr, 0);
261                    break;
262
263                case R.styleable.BatteryHistoryChart_android_shadowDy:
264                    dy = a.getFloat(attr, 0);
265                    break;
266
267                case R.styleable.BatteryHistoryChart_android_shadowRadius:
268                    r = a.getFloat(attr, 0);
269                    break;
270
271                case R.styleable.BatteryHistoryChart_android_textColor:
272                    textColor = a.getColorStateList(attr);
273                    break;
274
275                case R.styleable.BatteryHistoryChart_android_textSize:
276                    textSize = a.getDimensionPixelSize(attr, textSize);
277                    break;
278
279                case R.styleable.BatteryHistoryChart_android_typeface:
280                    typefaceIndex = a.getInt(attr, typefaceIndex);
281                    break;
282
283                case R.styleable.BatteryHistoryChart_android_textStyle:
284                    styleIndex = a.getInt(attr, styleIndex);
285                    break;
286            }
287        }
288
289        a.recycle();
290
291        mTextPaint.setColor(textColor.getDefaultColor());
292        mTextPaint.setTextSize(textSize);
293
294        Typeface tf = null;
295        switch (typefaceIndex) {
296            case SANS:
297                tf = Typeface.SANS_SERIF;
298                break;
299
300            case SERIF:
301                tf = Typeface.SERIF;
302                break;
303
304            case MONOSPACE:
305                tf = Typeface.MONOSPACE;
306                break;
307        }
308
309        setTypeface(tf, styleIndex);
310
311        if (shadowcolor != 0) {
312            mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
313        }
314    }
315
316    public void setTypeface(Typeface tf, int style) {
317        if (style > 0) {
318            if (tf == null) {
319                tf = Typeface.defaultFromStyle(style);
320            } else {
321                tf = Typeface.create(tf, style);
322            }
323
324            mTextPaint.setTypeface(tf);
325            // now compute what (if any) algorithmic styling is needed
326            int typefaceStyle = tf != null ? tf.getStyle() : 0;
327            int need = style & ~typefaceStyle;
328            mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
329            mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
330        } else {
331            mTextPaint.setFakeBoldText(false);
332            mTextPaint.setTextSkewX(0);
333            mTextPaint.setTypeface(tf);
334        }
335    }
336
337    void setStats(BatteryStats stats) {
338        mStats = stats;
339
340        long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000,
341                BatteryStats.STATS_SINCE_CHARGED);
342        mStatsPeriod = uSecTime;
343        String durationString = Utils.formatElapsedTime(getContext(), mStatsPeriod / 1000);
344        mDurationString = getContext().getString(R.string.battery_stats_on_battery,
345                durationString);
346        mChargingLabel = getContext().getString(R.string.battery_stats_charging_label);
347        mScreenOnLabel = getContext().getString(R.string.battery_stats_screen_on_label);
348        mGpsOnLabel = getContext().getString(R.string.battery_stats_gps_on_label);
349        mWifiRunningLabel = getContext().getString(R.string.battery_stats_wifi_running_label);
350        mWakeLockLabel = getContext().getString(R.string.battery_stats_wake_lock_label);
351        mPhoneSignalLabel = getContext().getString(R.string.battery_stats_phone_signal_label);
352
353        int pos = 0;
354        int lastInteresting = 0;
355        byte lastLevel = -1;
356        mBatLow = 0;
357        mBatHigh = 100;
358        int aggrStates = 0;
359        boolean first = true;
360        if (stats.startIteratingHistoryLocked()) {
361            final HistoryItem rec = new HistoryItem();
362            while (stats.getNextHistoryLocked(rec)) {
363                pos++;
364                if (rec.cmd == HistoryItem.CMD_UPDATE) {
365                    if (first) {
366                        first = false;
367                        mHistStart = rec.time;
368                    }
369                    if (rec.batteryLevel != lastLevel || pos == 1) {
370                        lastLevel = rec.batteryLevel;
371                    }
372                    lastInteresting = pos;
373                    mHistEnd = rec.time;
374                    aggrStates |= rec.states;
375                }
376            }
377        }
378        mNumHist = lastInteresting;
379        mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
380        mHaveWifi = (aggrStates&HistoryItem.STATE_WIFI_RUNNING_FLAG) != 0;
381        if (!com.android.settings.Utils.isWifiOnly(getContext())) {
382            mHavePhoneSignal = true;
383        }
384        if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
385        mTotalDurationString = Utils.formatElapsedTime(getContext(), mHistEnd - mHistStart);
386    }
387
388    @Override
389    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
390        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
391        mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
392        mTotalDurationStringWidth = (int)mTextPaint.measureText(mTotalDurationString);
393        mTextAscent = (int)mTextPaint.ascent();
394        mTextDescent = (int)mTextPaint.descent();
395    }
396
397    void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
398            int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
399            boolean lastWifiRunning, boolean lastWakeLock, Path lastPath) {
400        if (curLevelPath != null) {
401            if (lastX >= 0 && lastX < w) {
402                if (lastPath != null) {
403                    lastPath.lineTo(w, y);
404                }
405                curLevelPath.lineTo(w, y);
406            }
407            curLevelPath.lineTo(w, mLevelTop+levelh);
408            curLevelPath.lineTo(startX, mLevelTop+levelh);
409            curLevelPath.close();
410        }
411
412        if (lastCharging) {
413            mChargingPath.lineTo(w, h-mChargingOffset);
414        }
415        if (lastScreenOn) {
416            mScreenOnPath.lineTo(w, h-mScreenOnOffset);
417        }
418        if (lastGpsOn) {
419            mGpsOnPath.lineTo(w, h-mGpsOnOffset);
420        }
421        if (lastWifiRunning) {
422            mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
423        }
424        if (lastWakeLock) {
425            mWakeLockPath.lineTo(w, h-mWakeLockOffset);
426        }
427        if (mHavePhoneSignal) {
428            mPhoneSignalChart.finish(w);
429        }
430    }
431
432    @Override
433    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
434        super.onSizeChanged(w, h, oldw, oldh);
435
436        int textHeight = mTextDescent - mTextAscent;
437        mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
438                2, getResources().getDisplayMetrics());
439        if (h > (textHeight*6)) {
440            mLargeMode = true;
441            if (h > (textHeight*15)) {
442                // Plenty of room for the chart.
443                mLineWidth = textHeight/2;
444            } else {
445                // Compress lines to make more room for chart.
446                mLineWidth = textHeight/3;
447            }
448            mLevelTop = textHeight + mLineWidth;
449            mScreenOnPaint.setARGB(255, 32, 64, 255);
450            mGpsOnPaint.setARGB(255, 32, 64, 255);
451            mWifiRunningPaint.setARGB(255, 32, 64, 255);
452            mWakeLockPaint.setARGB(255, 32, 64, 255);
453        } else {
454            mLargeMode = false;
455            mLineWidth = mThinLineWidth;
456            mLevelTop = 0;
457            mScreenOnPaint.setARGB(255, 0, 0, 255);
458            mGpsOnPaint.setARGB(255, 0, 0, 255);
459            mWifiRunningPaint.setARGB(255, 0, 0, 255);
460            mWakeLockPaint.setARGB(255, 0, 0, 255);
461        }
462        if (mLineWidth <= 0) mLineWidth = 1;
463        mTextPaint.setStrokeWidth(mThinLineWidth);
464        mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
465        mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
466        mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth);
467        mChargingPaint.setStrokeWidth(mLineWidth);
468        mScreenOnPaint.setStrokeWidth(mLineWidth);
469        mGpsOnPaint.setStrokeWidth(mLineWidth);
470        mWifiRunningPaint.setStrokeWidth(mLineWidth);
471        mWakeLockPaint.setStrokeWidth(mLineWidth);
472
473        if (mLargeMode) {
474            int barOffset = textHeight + mLineWidth;
475            mChargingOffset = mLineWidth;
476            mScreenOnOffset = mChargingOffset + barOffset;
477            mWakeLockOffset = mScreenOnOffset + barOffset;
478            mWifiRunningOffset = mWakeLockOffset + barOffset;
479            mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? barOffset : 0);
480            mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? barOffset : 0);
481            mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? barOffset : 0)
482                    + ((mLineWidth*3)/2);
483            if (mHavePhoneSignal) {
484                mPhoneSignalChart.init(w);
485            }
486        } else {
487            mScreenOnOffset = mGpsOnOffset = mWifiRunningOffset
488                    = mWakeLockOffset = mLineWidth;
489            mChargingOffset = mLineWidth*2;
490            mPhoneSignalOffset = 0;
491            mLevelOffset = mLineWidth*3;
492            if (mHavePhoneSignal) {
493                mPhoneSignalChart.init(0);
494            }
495        }
496
497        mBatLevelPath.reset();
498        mBatGoodPath.reset();
499        mBatWarnPath.reset();
500        mBatCriticalPath.reset();
501        mScreenOnPath.reset();
502        mGpsOnPath.reset();
503        mWifiRunningPath.reset();
504        mWakeLockPath.reset();
505        mChargingPath.reset();
506
507        final long timeStart = mHistStart;
508        final long timeChange = mHistEnd-mHistStart;
509
510        final int batLow = mBatLow;
511        final int batChange = mBatHigh-mBatLow;
512
513        final int levelh = h - mLevelOffset - mLevelTop;
514        mLevelBottom = mLevelTop + levelh;
515
516        int x = 0, y = 0, startX = 0, lastX = -1, lastY = -1;
517        int i = 0;
518        Path curLevelPath = null;
519        Path lastLinePath = null;
520        boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
521        boolean lastWifiRunning = false, lastWakeLock = false;
522        final int N = mNumHist;
523        if (mStats.startIteratingHistoryLocked()) {
524            final HistoryItem rec = new HistoryItem();
525            while (mStats.getNextHistoryLocked(rec) && i < N) {
526                if (rec.cmd == BatteryStats.HistoryItem.CMD_UPDATE) {
527                    x = (int)(((rec.time-timeStart)*w)/timeChange);
528                    y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
529
530                    if (lastX != x) {
531                        // We have moved by at least a pixel.
532                        if (lastY != y) {
533                            // Don't plot changes within a pixel.
534                            Path path;
535                            byte value = rec.batteryLevel;
536                            if (value <= BATTERY_CRITICAL) path = mBatCriticalPath;
537                            else if (value <= BATTERY_WARN) path = mBatWarnPath;
538                            else path = mBatGoodPath;
539
540                            if (path != lastLinePath) {
541                                if (lastLinePath != null) {
542                                    lastLinePath.lineTo(x, y);
543                                }
544                                path.moveTo(x, y);
545                                lastLinePath = path;
546                            } else {
547                                path.lineTo(x, y);
548                            }
549
550                            if (curLevelPath == null) {
551                                curLevelPath = mBatLevelPath;
552                                curLevelPath.moveTo(x, y);
553                                startX = x;
554                            } else {
555                                curLevelPath.lineTo(x, y);
556                            }
557                            lastX = x;
558                            lastY = y;
559                        }
560
561                        final boolean charging =
562                            (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
563                        if (charging != lastCharging) {
564                            if (charging) {
565                                mChargingPath.moveTo(x, h-mChargingOffset);
566                            } else {
567                                mChargingPath.lineTo(x, h-mChargingOffset);
568                            }
569                            lastCharging = charging;
570                        }
571
572                        final boolean screenOn =
573                            (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
574                        if (screenOn != lastScreenOn) {
575                            if (screenOn) {
576                                mScreenOnPath.moveTo(x, h-mScreenOnOffset);
577                            } else {
578                                mScreenOnPath.lineTo(x, h-mScreenOnOffset);
579                            }
580                            lastScreenOn = screenOn;
581                        }
582
583                        final boolean gpsOn =
584                            (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
585                        if (gpsOn != lastGpsOn) {
586                            if (gpsOn) {
587                                mGpsOnPath.moveTo(x, h-mGpsOnOffset);
588                            } else {
589                                mGpsOnPath.lineTo(x, h-mGpsOnOffset);
590                            }
591                            lastGpsOn = gpsOn;
592                        }
593
594                        final boolean wifiRunning =
595                            (rec.states&HistoryItem.STATE_WIFI_RUNNING_FLAG) != 0;
596                        if (wifiRunning != lastWifiRunning) {
597                            if (wifiRunning) {
598                                mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
599                            } else {
600                                mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
601                            }
602                            lastWifiRunning = wifiRunning;
603                        }
604
605                        final boolean wakeLock =
606                            (rec.states&HistoryItem.STATE_WAKE_LOCK_FLAG) != 0;
607                        if (wakeLock != lastWakeLock) {
608                            if (wakeLock) {
609                                mWakeLockPath.moveTo(x, h-mWakeLockOffset);
610                            } else {
611                                mWakeLockPath.lineTo(x, h-mWakeLockOffset);
612                            }
613                            lastWakeLock = wakeLock;
614                        }
615
616                        if (mLargeMode && mHavePhoneSignal) {
617                            int bin;
618                            if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
619                                    >> HistoryItem.STATE_PHONE_STATE_SHIFT)
620                                    == ServiceState.STATE_POWER_OFF) {
621                                bin = 0;
622                            } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
623                                bin = 1;
624                            } else {
625                                bin = (rec.states&HistoryItem.STATE_SIGNAL_STRENGTH_MASK)
626                                        >> HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT;
627                                bin += 2;
628                            }
629                            mPhoneSignalChart.addTick(x, bin);
630                        }
631                    }
632
633                } else if (rec.cmd != BatteryStats.HistoryItem.CMD_OVERFLOW) {
634                    if (curLevelPath != null) {
635                        finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
636                                lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
637                                lastWakeLock, lastLinePath);
638                        lastX = lastY = -1;
639                        curLevelPath = null;
640                        lastLinePath = null;
641                        lastCharging = lastScreenOn = lastGpsOn = lastWakeLock = false;
642                    }
643                }
644
645                i++;
646            }
647        }
648
649        finishPaths(w, h, levelh, startX, lastY, curLevelPath, lastX,
650                lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
651                lastWakeLock, lastLinePath);
652    }
653
654    @Override
655    protected void onDraw(Canvas canvas) {
656        super.onDraw(canvas);
657
658        final int width = getWidth();
659        final int height = getHeight();
660
661        canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
662        if (mLargeMode) {
663            canvas.drawText(mDurationString, 0, -mTextAscent + (mLineWidth/2),
664                    mTextPaint);
665            canvas.drawText(mTotalDurationString, (width/2) - (mTotalDurationStringWidth/2),
666                    mLevelBottom - mTextAscent + mThinLineWidth, mTextPaint);
667        } else {
668            canvas.drawText(mDurationString, (width/2) - (mDurationStringWidth/2),
669                    (height/2) - ((mTextDescent-mTextAscent)/2) - mTextAscent, mTextPaint);
670        }
671        if (!mBatGoodPath.isEmpty()) {
672            canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
673        }
674        if (!mBatWarnPath.isEmpty()) {
675            canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
676        }
677        if (!mBatCriticalPath.isEmpty()) {
678            canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
679        }
680        if (mHavePhoneSignal) {
681            int top = height-mPhoneSignalOffset - (mLineWidth/2);
682            mPhoneSignalChart.draw(canvas, top, mLineWidth);
683        }
684        if (!mScreenOnPath.isEmpty()) {
685            canvas.drawPath(mScreenOnPath, mScreenOnPaint);
686        }
687        if (!mChargingPath.isEmpty()) {
688            canvas.drawPath(mChargingPath, mChargingPaint);
689        }
690        if (mHaveGps) {
691            if (!mGpsOnPath.isEmpty()) {
692                canvas.drawPath(mGpsOnPath, mGpsOnPaint);
693            }
694        }
695        if (mHaveWifi) {
696            if (!mWifiRunningPath.isEmpty()) {
697                canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
698            }
699        }
700        if (!mWakeLockPath.isEmpty()) {
701            canvas.drawPath(mWakeLockPath, mWakeLockPaint);
702        }
703
704        if (mLargeMode) {
705            if (mHavePhoneSignal) {
706                canvas.drawText(mPhoneSignalLabel, 0,
707                        height - mPhoneSignalOffset - mTextDescent, mTextPaint);
708            }
709            if (mHaveGps) {
710                canvas.drawText(mGpsOnLabel, 0,
711                        height - mGpsOnOffset - mTextDescent, mTextPaint);
712            }
713            if (mHaveWifi) {
714                canvas.drawText(mWifiRunningLabel, 0,
715                        height - mWifiRunningOffset - mTextDescent, mTextPaint);
716            }
717            canvas.drawText(mWakeLockLabel, 0,
718                    height - mWakeLockOffset - mTextDescent, mTextPaint);
719            canvas.drawText(mChargingLabel, 0,
720                    height - mChargingOffset - mTextDescent, mTextPaint);
721            canvas.drawText(mScreenOnLabel, 0,
722                    height - mScreenOnOffset - mTextDescent, mTextPaint);
723            canvas.drawLine(0, mLevelBottom+(mThinLineWidth/2), width,
724                    mLevelBottom+(mThinLineWidth/2), mTextPaint);
725            canvas.drawLine(0, mLevelTop, 0,
726                    mLevelBottom+(mThinLineWidth/2), mTextPaint);
727            for (int i=0; i<10; i++) {
728                int y = mLevelTop + ((mLevelBottom-mLevelTop)*i)/10;
729                canvas.drawLine(0, y, mThinLineWidth*2, y, mTextPaint);
730            }
731        }
732    }
733}
734