Home | History | Annotate | Download | only in fuelgauge
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.fuelgauge;
     18 
     19 import com.android.settings.R;
     20 
     21 import android.content.Context;
     22 import android.content.res.ColorStateList;
     23 import android.content.res.TypedArray;
     24 import android.graphics.Canvas;
     25 import android.graphics.Paint;
     26 import android.graphics.Path;
     27 import android.graphics.Typeface;
     28 import android.os.BatteryStats;
     29 import android.os.SystemClock;
     30 import android.os.BatteryStats.HistoryItem;
     31 import android.telephony.ServiceState;
     32 import android.text.TextPaint;
     33 import android.util.AttributeSet;
     34 import android.util.TypedValue;
     35 import android.view.View;
     36 
     37 public 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