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 
    177     public BatteryHistoryChart(Context context, AttributeSet attrs) {
    178         super(context, attrs);
    179 
    180         mBatteryBackgroundPaint.setARGB(255, 128, 128, 128);
    181         mBatteryBackgroundPaint.setStyle(Paint.Style.FILL);
    182         mBatteryGoodPaint.setARGB(128, 0, 255, 0);
    183         mBatteryGoodPaint.setStyle(Paint.Style.STROKE);
    184         mBatteryWarnPaint.setARGB(128, 255, 255, 0);
    185         mBatteryWarnPaint.setStyle(Paint.Style.STROKE);
    186         mBatteryCriticalPaint.setARGB(192, 255, 0, 0);
    187         mBatteryCriticalPaint.setStyle(Paint.Style.STROKE);
    188         mChargingPaint.setARGB(255, 0, 128, 0);
    189         mChargingPaint.setStyle(Paint.Style.STROKE);
    190         mScreenOnPaint.setStyle(Paint.Style.STROKE);
    191         mGpsOnPaint.setStyle(Paint.Style.STROKE);
    192         mWifiRunningPaint.setStyle(Paint.Style.STROKE);
    193         mWakeLockPaint.setStyle(Paint.Style.STROKE);
    194         mPhoneSignalChart.setColors(new int[] {
    195                 0x00000000, 0xffa00000, 0xffa0a000, 0xff808020,
    196                 0xff808040, 0xff808060, 0xff008000
    197         });
    198 
    199         mTextPaint.density = getResources().getDisplayMetrics().density;
    200         mTextPaint.setCompatibilityScaling(
    201                 getResources().getCompatibilityInfo().applicationScale);
    202 
    203         TypedArray a =
    204             context.obtainStyledAttributes(
    205                 attrs, R.styleable.BatteryHistoryChart, 0, 0);
    206 
    207         ColorStateList textColor = null;
    208         int textSize = 15;
    209         int typefaceIndex = -1;
    210         int styleIndex = -1;
    211 
    212         TypedArray appearance = null;
    213         int ap = a.getResourceId(R.styleable.BatteryHistoryChart_android_textAppearance, -1);
    214         if (ap != -1) {
    215             appearance = context.obtainStyledAttributes(ap,
    216                                 com.android.internal.R.styleable.
    217                                 TextAppearance);
    218         }
    219         if (appearance != null) {
    220             int n = appearance.getIndexCount();
    221             for (int i = 0; i < n; i++) {
    222                 int attr = appearance.getIndex(i);
    223 
    224                 switch (attr) {
    225                 case com.android.internal.R.styleable.TextAppearance_textColor:
    226                     textColor = appearance.getColorStateList(attr);
    227                     break;
    228 
    229                 case com.android.internal.R.styleable.TextAppearance_textSize:
    230                     textSize = appearance.getDimensionPixelSize(attr, textSize);
    231                     break;
    232 
    233                 case com.android.internal.R.styleable.TextAppearance_typeface:
    234                     typefaceIndex = appearance.getInt(attr, -1);
    235                     break;
    236 
    237                 case com.android.internal.R.styleable.TextAppearance_textStyle:
    238                     styleIndex = appearance.getInt(attr, -1);
    239                     break;
    240                 }
    241             }
    242 
    243             appearance.recycle();
    244         }
    245 
    246         int shadowcolor = 0;
    247         float dx=0, dy=0, r=0;
    248 
    249         int n = a.getIndexCount();
    250         for (int i = 0; i < n; i++) {
    251             int attr = a.getIndex(i);
    252 
    253             switch (attr) {
    254                 case R.styleable.BatteryHistoryChart_android_shadowColor:
    255                     shadowcolor = a.getInt(attr, 0);
    256                     break;
    257 
    258                 case R.styleable.BatteryHistoryChart_android_shadowDx:
    259                     dx = a.getFloat(attr, 0);
    260                     break;
    261 
    262                 case R.styleable.BatteryHistoryChart_android_shadowDy:
    263                     dy = a.getFloat(attr, 0);
    264                     break;
    265 
    266                 case R.styleable.BatteryHistoryChart_android_shadowRadius:
    267                     r = a.getFloat(attr, 0);
    268                     break;
    269 
    270                 case R.styleable.BatteryHistoryChart_android_textColor:
    271                     textColor = a.getColorStateList(attr);
    272                     break;
    273 
    274                 case R.styleable.BatteryHistoryChart_android_textSize:
    275                     textSize = a.getDimensionPixelSize(attr, textSize);
    276                     break;
    277 
    278                 case R.styleable.BatteryHistoryChart_android_typeface:
    279                     typefaceIndex = a.getInt(attr, typefaceIndex);
    280                     break;
    281 
    282                 case R.styleable.BatteryHistoryChart_android_textStyle:
    283                     styleIndex = a.getInt(attr, styleIndex);
    284                     break;
    285             }
    286         }
    287 
    288         a.recycle();
    289 
    290         mTextPaint.setColor(textColor.getDefaultColor());
    291         mTextPaint.setTextSize(textSize);
    292 
    293         Typeface tf = null;
    294         switch (typefaceIndex) {
    295             case SANS:
    296                 tf = Typeface.SANS_SERIF;
    297                 break;
    298 
    299             case SERIF:
    300                 tf = Typeface.SERIF;
    301                 break;
    302 
    303             case MONOSPACE:
    304                 tf = Typeface.MONOSPACE;
    305                 break;
    306         }
    307 
    308         setTypeface(tf, styleIndex);
    309 
    310         if (shadowcolor != 0) {
    311             mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
    312         }
    313     }
    314 
    315     public void setTypeface(Typeface tf, int style) {
    316         if (style > 0) {
    317             if (tf == null) {
    318                 tf = Typeface.defaultFromStyle(style);
    319             } else {
    320                 tf = Typeface.create(tf, style);
    321             }
    322 
    323             mTextPaint.setTypeface(tf);
    324             // now compute what (if any) algorithmic styling is needed
    325             int typefaceStyle = tf != null ? tf.getStyle() : 0;
    326             int need = style & ~typefaceStyle;
    327             mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
    328             mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
    329         } else {
    330             mTextPaint.setFakeBoldText(false);
    331             mTextPaint.setTextSkewX(0);
    332             mTextPaint.setTypeface(tf);
    333         }
    334     }
    335 
    336     void setStats(BatteryStats stats) {
    337         mStats = stats;
    338 
    339         long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000,
    340                 BatteryStats.STATS_SINCE_CHARGED);
    341         mStatsPeriod = uSecTime;
    342         String durationString = Utils.formatElapsedTime(getContext(), mStatsPeriod / 1000);
    343         mDurationString = getContext().getString(R.string.battery_stats_on_battery,
    344                 durationString);
    345         mChargingLabel = getContext().getString(R.string.battery_stats_charging_label);
    346         mScreenOnLabel = getContext().getString(R.string.battery_stats_screen_on_label);
    347         mGpsOnLabel = getContext().getString(R.string.battery_stats_gps_on_label);
    348         mWifiRunningLabel = getContext().getString(R.string.battery_stats_wifi_running_label);
    349         mWakeLockLabel = getContext().getString(R.string.battery_stats_wake_lock_label);
    350         mPhoneSignalLabel = getContext().getString(R.string.battery_stats_phone_signal_label);
    351 
    352         int pos = 0;
    353         int lastInteresting = 0;
    354         byte lastLevel = -1;
    355         mBatLow = 0;
    356         mBatHigh = 100;
    357         int aggrStates = 0;
    358         boolean first = true;
    359         if (stats.startIteratingHistoryLocked()) {
    360             final HistoryItem rec = new HistoryItem();
    361             while (stats.getNextHistoryLocked(rec)) {
    362                 pos++;
    363                 if (rec.cmd == HistoryItem.CMD_UPDATE) {
    364                     if (first) {
    365                         first = false;
    366                         mHistStart = rec.time;
    367                     }
    368                     if (rec.batteryLevel != lastLevel || pos == 1) {
    369                         lastLevel = rec.batteryLevel;
    370                         lastInteresting = pos;
    371                         mHistEnd = rec.time;
    372                     }
    373                     aggrStates |= rec.states;
    374                 }
    375             }
    376         }
    377         mNumHist = lastInteresting;
    378         mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
    379         mHaveWifi = (aggrStates&HistoryItem.STATE_WIFI_RUNNING_FLAG) != 0;
    380 
    381         if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
    382         mTotalDurationString = Utils.formatElapsedTime(getContext(), mHistEnd - mHistStart);
    383     }
    384 
    385     @Override
    386     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    387         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    388         mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
    389         mTotalDurationStringWidth = (int)mTextPaint.measureText(mTotalDurationString);
    390         mTextAscent = (int)mTextPaint.ascent();
    391         mTextDescent = (int)mTextPaint.descent();
    392     }
    393 
    394     void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
    395             int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
    396             boolean lastWifiRunning, boolean lastWakeLock, Path lastPath) {
    397         if (curLevelPath != null) {
    398             if (lastX >= 0 && lastX < w) {
    399                 if (lastPath != null) {
    400                     lastPath.lineTo(w, y);
    401                 }
    402                 curLevelPath.lineTo(w, y);
    403             }
    404             curLevelPath.lineTo(w, mLevelTop+levelh);
    405             curLevelPath.lineTo(startX, mLevelTop+levelh);
    406             curLevelPath.close();
    407         }
    408 
    409         if (lastCharging) {
    410             mChargingPath.lineTo(w, h-mChargingOffset);
    411         }
    412         if (lastScreenOn) {
    413             mScreenOnPath.lineTo(w, h-mScreenOnOffset);
    414         }
    415         if (lastGpsOn) {
    416             mGpsOnPath.lineTo(w, h-mGpsOnOffset);
    417         }
    418         if (lastWifiRunning) {
    419             mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
    420         }
    421         if (lastWakeLock) {
    422             mWakeLockPath.lineTo(w, h-mWakeLockOffset);
    423         }
    424         mPhoneSignalChart.finish(w);
    425     }
    426 
    427     @Override
    428     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    429         super.onSizeChanged(w, h, oldw, oldh);
    430 
    431         int textHeight = mTextDescent - mTextAscent;
    432         mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    433                 2, getResources().getDisplayMetrics());
    434         if (h > (textHeight*6)) {
    435             mLargeMode = true;
    436             mLineWidth = textHeight/2;
    437             mLevelTop = textHeight + mLineWidth;
    438             mScreenOnPaint.setARGB(255, 32, 64, 255);
    439             mGpsOnPaint.setARGB(255, 32, 64, 255);
    440             mWifiRunningPaint.setARGB(255, 32, 64, 255);
    441             mWakeLockPaint.setARGB(255, 32, 64, 255);
    442         } else {
    443             mLargeMode = false;
    444             mLineWidth = mThinLineWidth;
    445             mLevelTop = 0;
    446             mScreenOnPaint.setARGB(255, 0, 0, 255);
    447             mGpsOnPaint.setARGB(255, 0, 0, 255);
    448             mWifiRunningPaint.setARGB(255, 0, 0, 255);
    449             mWakeLockPaint.setARGB(255, 0, 0, 255);
    450         }
    451         if (mLineWidth <= 0) mLineWidth = 1;
    452         mTextPaint.setStrokeWidth(mThinLineWidth);
    453         mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
    454         mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
    455         mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth);
    456         mChargingPaint.setStrokeWidth(mLineWidth);
    457         mScreenOnPaint.setStrokeWidth(mLineWidth);
    458         mGpsOnPaint.setStrokeWidth(mLineWidth);
    459         mWifiRunningPaint.setStrokeWidth(mLineWidth);
    460         mWakeLockPaint.setStrokeWidth(mLineWidth);
    461 
    462         if (mLargeMode) {
    463             int barOffset = textHeight + mLineWidth;
    464             mChargingOffset = mLineWidth;
    465             mScreenOnOffset = mChargingOffset + barOffset;
    466             mWakeLockOffset = mScreenOnOffset + barOffset;
    467             mWifiRunningOffset = mWakeLockOffset + barOffset;
    468             mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? barOffset : 0);
    469             mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? barOffset : 0);
    470             mLevelOffset = mPhoneSignalOffset + barOffset + mLineWidth;
    471             mPhoneSignalChart.init(w);
    472         } else {
    473             mScreenOnOffset = mGpsOnOffset = mWifiRunningOffset
    474                     = mWakeLockOffset = mLineWidth;
    475             mChargingOffset = mLineWidth*2;
    476             mPhoneSignalOffset = 0;
    477             mLevelOffset = mLineWidth*3;
    478             mPhoneSignalChart.init(0);
    479         }
    480 
    481         mBatLevelPath.reset();
    482         mBatGoodPath.reset();
    483         mBatWarnPath.reset();
    484         mBatCriticalPath.reset();
    485         mScreenOnPath.reset();
    486         mGpsOnPath.reset();
    487         mWifiRunningPath.reset();
    488         mWakeLockPath.reset();
    489         mChargingPath.reset();
    490 
    491         final long timeStart = mHistStart;
    492         final long timeChange = mHistEnd-mHistStart;
    493 
    494         final int batLow = mBatLow;
    495         final int batChange = mBatHigh-mBatLow;
    496 
    497         final int levelh = h - mLevelOffset - mLevelTop;
    498         mLevelBottom = mLevelTop + levelh;
    499 
    500         int x = 0, y = 0, startX = 0, lastX = -1, lastY = -1;
    501         int i = 0;
    502         Path curLevelPath = null;
    503         Path lastLinePath = null;
    504         boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
    505         boolean lastWifiRunning = false, lastWakeLock = false;
    506         final int N = mNumHist;
    507         if (mStats.startIteratingHistoryLocked()) {
    508             final HistoryItem rec = new HistoryItem();
    509             while (mStats.getNextHistoryLocked(rec) && i < N) {
    510                 if (rec.cmd == BatteryStats.HistoryItem.CMD_UPDATE) {
    511                     x = (int)(((rec.time-timeStart)*w)/timeChange);
    512                     y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
    513 
    514                     if (lastX != x) {
    515                         // We have moved by at least a pixel.
    516                         if (lastY != y) {
    517                             // Don't plot changes within a pixel.
    518                             Path path;
    519                             byte value = rec.batteryLevel;
    520                             if (value <= BATTERY_CRITICAL) path = mBatCriticalPath;
    521                             else if (value <= BATTERY_WARN) path = mBatWarnPath;
    522                             else path = mBatGoodPath;
    523 
    524                             if (path != lastLinePath) {
    525                                 if (lastLinePath != null) {
    526                                     lastLinePath.lineTo(x, y);
    527                                 }
    528                                 path.moveTo(x, y);
    529                                 lastLinePath = path;
    530                             } else {
    531                                 path.lineTo(x, y);
    532                             }
    533 
    534                             if (curLevelPath == null) {
    535                                 curLevelPath = mBatLevelPath;
    536                                 curLevelPath.moveTo(x, y);
    537                                 startX = x;
    538                             } else {
    539                                 curLevelPath.lineTo(x, y);
    540                             }
    541                             lastX = x;
    542                             lastY = y;
    543                         }
    544 
    545                         final boolean charging =
    546                             (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
    547                         if (charging != lastCharging) {
    548                             if (charging) {
    549                                 mChargingPath.moveTo(x, h-mChargingOffset);
    550                             } else {
    551                                 mChargingPath.lineTo(x, h-mChargingOffset);
    552                             }
    553                             lastCharging = charging;
    554                         }
    555 
    556                         final boolean screenOn =
    557                             (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
    558                         if (screenOn != lastScreenOn) {
    559                             if (screenOn) {
    560                                 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
    561                             } else {
    562                                 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
    563                             }
    564                             lastScreenOn = screenOn;
    565                         }
    566 
    567                         final boolean gpsOn =
    568                             (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
    569                         if (gpsOn != lastGpsOn) {
    570                             if (gpsOn) {
    571                                 mGpsOnPath.moveTo(x, h-mGpsOnOffset);
    572                             } else {
    573                                 mGpsOnPath.lineTo(x, h-mGpsOnOffset);
    574                             }
    575                             lastGpsOn = gpsOn;
    576                         }
    577 
    578                         final boolean wifiRunning =
    579                             (rec.states&HistoryItem.STATE_WIFI_RUNNING_FLAG) != 0;
    580                         if (wifiRunning != lastWifiRunning) {
    581                             if (wifiRunning) {
    582                                 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
    583                             } else {
    584                                 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
    585                             }
    586                             lastWifiRunning = wifiRunning;
    587                         }
    588 
    589                         final boolean wakeLock =
    590                             (rec.states&HistoryItem.STATE_WAKE_LOCK_FLAG) != 0;
    591                         if (wakeLock != lastWakeLock) {
    592                             if (wakeLock) {
    593                                 mWakeLockPath.moveTo(x, h-mWakeLockOffset);
    594                             } else {
    595                                 mWakeLockPath.lineTo(x, h-mWakeLockOffset);
    596                             }
    597                             lastWakeLock = wakeLock;
    598                         }
    599 
    600                         if (mLargeMode) {
    601                             int bin;
    602                             if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
    603                                     >> HistoryItem.STATE_PHONE_STATE_SHIFT)
    604                                     == ServiceState.STATE_POWER_OFF) {
    605                                 bin = 0;
    606                             } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
    607                                 bin = 1;
    608                             } else {
    609                                 bin = (rec.states&HistoryItem.STATE_SIGNAL_STRENGTH_MASK)
    610                                         >> HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT;
    611                                 bin += 2;
    612                             }
    613                             mPhoneSignalChart.addTick(x, bin);
    614                         }
    615                     }
    616 
    617                 } else if (rec.cmd != BatteryStats.HistoryItem.CMD_OVERFLOW) {
    618                     if (curLevelPath != null) {
    619                         finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
    620                                 lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
    621                                 lastWakeLock, lastLinePath);
    622                         lastX = lastY = -1;
    623                         curLevelPath = null;
    624                         lastLinePath = null;
    625                         lastCharging = lastScreenOn = lastGpsOn = lastWakeLock = false;
    626                     }
    627                 }
    628 
    629                 i++;
    630             }
    631         }
    632 
    633         finishPaths(w, h, levelh, startX, lastY, curLevelPath, lastX,
    634                 lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning,
    635                 lastWakeLock, lastLinePath);
    636     }
    637 
    638     @Override
    639     protected void onDraw(Canvas canvas) {
    640         super.onDraw(canvas);
    641 
    642         final int width = getWidth();
    643         final int height = getHeight();
    644 
    645         canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
    646         if (mLargeMode) {
    647             canvas.drawText(mDurationString, 0, -mTextAscent + (mLineWidth/2),
    648                     mTextPaint);
    649             canvas.drawText(mTotalDurationString, (width/2) - (mTotalDurationStringWidth/2),
    650                     mLevelBottom - mTextAscent + mThinLineWidth, mTextPaint);
    651         } else {
    652             canvas.drawText(mDurationString, (width/2) - (mDurationStringWidth/2),
    653                     (height/2) - ((mTextDescent-mTextAscent)/2) - mTextAscent, mTextPaint);
    654         }
    655         if (!mBatGoodPath.isEmpty()) {
    656             canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
    657         }
    658         if (!mBatWarnPath.isEmpty()) {
    659             canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
    660         }
    661         if (!mBatCriticalPath.isEmpty()) {
    662             canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
    663         }
    664         int top = height-mPhoneSignalOffset - (mLineWidth/2);
    665         mPhoneSignalChart.draw(canvas, top, mLineWidth);
    666         if (!mScreenOnPath.isEmpty()) {
    667             canvas.drawPath(mScreenOnPath, mScreenOnPaint);
    668         }
    669         if (!mChargingPath.isEmpty()) {
    670             canvas.drawPath(mChargingPath, mChargingPaint);
    671         }
    672         if (mHaveGps) {
    673             if (!mGpsOnPath.isEmpty()) {
    674                 canvas.drawPath(mGpsOnPath, mGpsOnPaint);
    675             }
    676         }
    677         if (mHaveWifi) {
    678             if (!mWifiRunningPath.isEmpty()) {
    679                 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
    680             }
    681         }
    682         if (!mWakeLockPath.isEmpty()) {
    683             canvas.drawPath(mWakeLockPath, mWakeLockPaint);
    684         }
    685 
    686         if (mLargeMode) {
    687             canvas.drawText(mPhoneSignalLabel, 0,
    688                     height - mPhoneSignalOffset - mTextDescent, mTextPaint);
    689             if (mHaveGps) {
    690                 canvas.drawText(mGpsOnLabel, 0,
    691                         height - mGpsOnOffset - mTextDescent, mTextPaint);
    692             }
    693             if (mHaveWifi) {
    694                 canvas.drawText(mWifiRunningLabel, 0,
    695                         height - mWifiRunningOffset - mTextDescent, mTextPaint);
    696             }
    697             canvas.drawText(mWakeLockLabel, 0,
    698                     height - mWakeLockOffset - mTextDescent, mTextPaint);
    699             canvas.drawText(mChargingLabel, 0,
    700                     height - mChargingOffset - mTextDescent, mTextPaint);
    701             canvas.drawText(mScreenOnLabel, 0,
    702                     height - mScreenOnOffset - mTextDescent, mTextPaint);
    703             canvas.drawLine(0, mLevelBottom+(mThinLineWidth/2), width,
    704                     mLevelBottom+(mThinLineWidth/2), mTextPaint);
    705             canvas.drawLine(0, mLevelTop, 0,
    706                     mLevelBottom+(mThinLineWidth/2), mTextPaint);
    707             for (int i=0; i<10; i++) {
    708                 int y = mLevelTop + ((mLevelBottom-mLevelTop)*i)/10;
    709                 canvas.drawLine(0, y, mThinLineWidth*2, y, mTextPaint);
    710             }
    711         }
    712     }
    713 }
    714