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 android.content.Context;
     20 import android.content.Intent;
     21 import android.content.res.ColorStateList;
     22 import android.content.res.TypedArray;
     23 import android.graphics.Bitmap;
     24 import android.graphics.Canvas;
     25 import android.graphics.DashPathEffect;
     26 import android.graphics.Paint;
     27 import android.graphics.Path;
     28 import android.graphics.Typeface;
     29 import android.os.BatteryStats;
     30 import android.os.BatteryStats.HistoryItem;
     31 import android.os.SystemClock;
     32 import android.telephony.ServiceState;
     33 import android.text.TextPaint;
     34 import android.text.format.DateFormat;
     35 import android.text.format.Formatter;
     36 import android.util.AttributeSet;
     37 import android.util.Log;
     38 import android.util.TimeUtils;
     39 import android.util.TypedValue;
     40 import android.view.View;
     41 
     42 import com.android.settings.R;
     43 import com.android.settings.Utils;
     44 
     45 import libcore.icu.LocaleData;
     46 
     47 import java.util.ArrayList;
     48 import java.util.Calendar;
     49 import java.util.Locale;
     50 
     51 public class BatteryHistoryChart extends View {
     52     static final boolean DEBUG = false;
     53     static final String TAG = "BatteryHistoryChart";
     54 
     55     static final int CHART_DATA_X_MASK = 0x0000ffff;
     56     static final int CHART_DATA_BIN_MASK = 0xffff0000;
     57     static final int CHART_DATA_BIN_SHIFT = 16;
     58 
     59     static class ChartData {
     60         int[] mColors;
     61         Paint[] mPaints;
     62 
     63         int mNumTicks;
     64         int[] mTicks;
     65         int mLastBin;
     66 
     67         void setColors(int[] colors) {
     68             mColors = colors;
     69             mPaints = new Paint[colors.length];
     70             for (int i=0; i<colors.length; i++) {
     71                 mPaints[i] = new Paint();
     72                 mPaints[i].setColor(colors[i]);
     73                 mPaints[i].setStyle(Paint.Style.FILL);
     74             }
     75         }
     76 
     77         void init(int width) {
     78             if (width > 0) {
     79                 mTicks = new int[width*2];
     80             } else {
     81                 mTicks = null;
     82             }
     83             mNumTicks = 0;
     84             mLastBin = 0;
     85         }
     86 
     87         void addTick(int x, int bin) {
     88             if (bin != mLastBin && mNumTicks < mTicks.length) {
     89                 mTicks[mNumTicks] = (x&CHART_DATA_X_MASK) | (bin<<CHART_DATA_BIN_SHIFT);
     90                 mNumTicks++;
     91                 mLastBin = bin;
     92             }
     93         }
     94 
     95         void finish(int width) {
     96             if (mLastBin != 0) {
     97                 addTick(width, 0);
     98             }
     99         }
    100 
    101         void draw(Canvas canvas, int top, int height) {
    102             int lastBin=0, lastX=0;
    103             int bottom = top + height;
    104             for (int i=0; i<mNumTicks; i++) {
    105                 int tick = mTicks[i];
    106                 int x = tick&CHART_DATA_X_MASK;
    107                 int bin = (tick&CHART_DATA_BIN_MASK) >> CHART_DATA_BIN_SHIFT;
    108                 if (lastBin != 0) {
    109                     canvas.drawRect(lastX, top, x, bottom, mPaints[lastBin]);
    110                 }
    111                 lastBin = bin;
    112                 lastX = x;
    113             }
    114 
    115         }
    116     }
    117 
    118     static final int SANS = 1;
    119     static final int SERIF = 2;
    120     static final int MONOSPACE = 3;
    121 
    122     // First value if for phone off; first value is "scanning"; following values
    123     // are battery stats signal strength buckets.
    124     static final int NUM_PHONE_SIGNALS = 7;
    125 
    126     final Paint mBatteryBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    127     final Paint mBatteryGoodPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    128     final Paint mBatteryWarnPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    129     final Paint mBatteryCriticalPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    130     final Paint mTimeRemainPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    131     final Paint mChargingPaint = new Paint();
    132     final Paint mScreenOnPaint = new Paint();
    133     final Paint mGpsOnPaint = new Paint();
    134     final Paint mFlashlightOnPaint = new Paint();
    135     final Paint mCameraOnPaint = new Paint();
    136     final Paint mWifiRunningPaint = new Paint();
    137     final Paint mCpuRunningPaint = new Paint();
    138     final Paint mDateLinePaint = new Paint();
    139     final ChartData mPhoneSignalChart = new ChartData();
    140     final TextPaint mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
    141     final TextPaint mHeaderTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
    142     final Paint mDebugRectPaint = new Paint();
    143 
    144     final Path mBatLevelPath = new Path();
    145     final Path mBatGoodPath = new Path();
    146     final Path mBatWarnPath = new Path();
    147     final Path mBatCriticalPath = new Path();
    148     final Path mTimeRemainPath = new Path();
    149     final Path mChargingPath = new Path();
    150     final Path mScreenOnPath = new Path();
    151     final Path mGpsOnPath = new Path();
    152     final Path mFlashlightOnPath = new Path();
    153     final Path mCameraOnPath = new Path();
    154     final Path mWifiRunningPath = new Path();
    155     final Path mCpuRunningPath = new Path();
    156     final Path mDateLinePath = new Path();
    157 
    158     BatteryStats mStats;
    159     Intent mBatteryBroadcast;
    160     long mStatsPeriod;
    161     String mMaxPercentLabelString;
    162     String mMinPercentLabelString;
    163     String mDurationString;
    164     String mChargeDurationString;
    165     String mDrainString;
    166     String mChargingLabel;
    167     String mScreenOnLabel;
    168     String mGpsOnLabel;
    169     String mCameraOnLabel;
    170     String mFlashlightOnLabel;
    171     String mWifiRunningLabel;
    172     String mCpuRunningLabel;
    173     String mPhoneSignalLabel;
    174 
    175     BatteryInfo mInfo;
    176 
    177     int mChartMinHeight;
    178     int mHeaderHeight;
    179 
    180     int mBatteryWarnLevel;
    181     int mBatteryCriticalLevel;
    182 
    183     int mTextAscent;
    184     int mTextDescent;
    185     int mHeaderTextAscent;
    186     int mHeaderTextDescent;
    187     int mMaxPercentLabelStringWidth;
    188     int mMinPercentLabelStringWidth;
    189     int mDurationStringWidth;
    190     int mChargeLabelStringWidth;
    191     int mChargeDurationStringWidth;
    192     int mDrainStringWidth;
    193 
    194     boolean mLargeMode;
    195 
    196     int mLastWidth = -1;
    197     int mLastHeight = -1;
    198 
    199     int mLineWidth;
    200     int mThinLineWidth;
    201     int mChargingOffset;
    202     int mScreenOnOffset;
    203     int mGpsOnOffset;
    204     int mFlashlightOnOffset;
    205     int mCameraOnOffset;
    206     int mWifiRunningOffset;
    207     int mCpuRunningOffset;
    208     int mPhoneSignalOffset;
    209     int mLevelOffset;
    210     int mLevelTop;
    211     int mLevelBottom;
    212     int mLevelLeft;
    213     int mLevelRight;
    214 
    215     int mNumHist;
    216     long mHistStart;
    217     long mHistDataEnd;
    218     long mHistEnd;
    219     long mStartWallTime;
    220     long mEndDataWallTime;
    221     long mEndWallTime;
    222     int mBatLow;
    223     int mBatHigh;
    224     boolean mHaveWifi;
    225     boolean mHaveGps;
    226     boolean mHavePhoneSignal;
    227     boolean mHaveCamera;
    228     boolean mHaveFlashlight;
    229 
    230     final ArrayList<TimeLabel> mTimeLabels = new ArrayList<TimeLabel>();
    231     final ArrayList<DateLabel> mDateLabels = new ArrayList<DateLabel>();
    232 
    233     Bitmap mBitmap;
    234     Canvas mCanvas;
    235 
    236     static class TextAttrs {
    237         ColorStateList textColor = null;
    238         int textSize = 15;
    239         int typefaceIndex = -1;
    240         int styleIndex = -1;
    241 
    242         void retrieve(Context context, TypedArray from, int index) {
    243             TypedArray appearance = null;
    244             int ap = from.getResourceId(index, -1);
    245             if (ap != -1) {
    246                 appearance = context.obtainStyledAttributes(ap,
    247                                     com.android.internal.R.styleable.TextAppearance);
    248             }
    249             if (appearance != null) {
    250                 int n = appearance.getIndexCount();
    251                 for (int i = 0; i < n; i++) {
    252                     int attr = appearance.getIndex(i);
    253 
    254                     switch (attr) {
    255                     case com.android.internal.R.styleable.TextAppearance_textColor:
    256                         textColor = appearance.getColorStateList(attr);
    257                         break;
    258 
    259                     case com.android.internal.R.styleable.TextAppearance_textSize:
    260                         textSize = appearance.getDimensionPixelSize(attr, textSize);
    261                         break;
    262 
    263                     case com.android.internal.R.styleable.TextAppearance_typeface:
    264                         typefaceIndex = appearance.getInt(attr, -1);
    265                         break;
    266 
    267                     case com.android.internal.R.styleable.TextAppearance_textStyle:
    268                         styleIndex = appearance.getInt(attr, -1);
    269                         break;
    270                     }
    271                 }
    272 
    273                 appearance.recycle();
    274             }
    275         }
    276 
    277         void apply(Context context, TextPaint paint) {
    278             paint.density = context.getResources().getDisplayMetrics().density;
    279             paint.setCompatibilityScaling(
    280                     context.getResources().getCompatibilityInfo().applicationScale);
    281 
    282             paint.setColor(textColor.getDefaultColor());
    283             paint.setTextSize(textSize);
    284 
    285             Typeface tf = null;
    286             switch (typefaceIndex) {
    287                 case SANS:
    288                     tf = Typeface.SANS_SERIF;
    289                     break;
    290 
    291                 case SERIF:
    292                     tf = Typeface.SERIF;
    293                     break;
    294 
    295                 case MONOSPACE:
    296                     tf = Typeface.MONOSPACE;
    297                     break;
    298             }
    299 
    300             setTypeface(paint, tf, styleIndex);
    301         }
    302 
    303         public void setTypeface(TextPaint paint, Typeface tf, int style) {
    304             if (style > 0) {
    305                 if (tf == null) {
    306                     tf = Typeface.defaultFromStyle(style);
    307                 } else {
    308                     tf = Typeface.create(tf, style);
    309                 }
    310 
    311                 paint.setTypeface(tf);
    312                 // now compute what (if any) algorithmic styling is needed
    313                 int typefaceStyle = tf != null ? tf.getStyle() : 0;
    314                 int need = style & ~typefaceStyle;
    315                 paint.setFakeBoldText((need & Typeface.BOLD) != 0);
    316                 paint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
    317             } else {
    318                 paint.setFakeBoldText(false);
    319                 paint.setTextSkewX(0);
    320                 paint.setTypeface(tf);
    321             }
    322         }
    323     }
    324 
    325     static class TimeLabel {
    326         final int x;
    327         final String label;
    328         final int width;
    329 
    330         TimeLabel(TextPaint paint, int x, Calendar cal, boolean use24hr) {
    331             this.x = x;
    332             final String bestFormat = DateFormat.getBestDateTimePattern(
    333                     Locale.getDefault(), use24hr ? "km" : "ha");
    334             label = DateFormat.format(bestFormat, cal).toString();
    335             width = (int)paint.measureText(label);
    336         }
    337     }
    338 
    339     static class DateLabel {
    340         final int x;
    341         final String label;
    342         final int width;
    343 
    344         DateLabel(TextPaint paint, int x, Calendar cal, boolean dayFirst) {
    345             this.x = x;
    346             final String bestFormat = DateFormat.getBestDateTimePattern(
    347                     Locale.getDefault(), dayFirst ? "dM" : "Md");
    348             label = DateFormat.format(bestFormat, cal).toString();
    349             width = (int)paint.measureText(label);
    350         }
    351     }
    352 
    353     public BatteryHistoryChart(Context context, AttributeSet attrs) {
    354         super(context, attrs);
    355 
    356         if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!");
    357 
    358         mBatteryWarnLevel = mContext.getResources().getInteger(
    359                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
    360         mBatteryCriticalLevel = mContext.getResources().getInteger(
    361                 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
    362 
    363         mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    364                 2, getResources().getDisplayMetrics());
    365 
    366         int accentColor = Utils.getColorAccent(mContext);
    367         mBatteryBackgroundPaint.setColor(accentColor);
    368         mBatteryBackgroundPaint.setStyle(Paint.Style.FILL);
    369         mBatteryGoodPaint.setARGB(128, 0, 128, 0);
    370         mBatteryGoodPaint.setStyle(Paint.Style.STROKE);
    371         mBatteryWarnPaint.setARGB(128, 128, 128, 0);
    372         mBatteryWarnPaint.setStyle(Paint.Style.STROKE);
    373         mBatteryCriticalPaint.setARGB(192, 128, 0, 0);
    374         mBatteryCriticalPaint.setStyle(Paint.Style.STROKE);
    375         mTimeRemainPaint.setColor(0xFFCED7BB);
    376         mTimeRemainPaint.setStyle(Paint.Style.FILL);
    377         mChargingPaint.setStyle(Paint.Style.STROKE);
    378         mScreenOnPaint.setStyle(Paint.Style.STROKE);
    379         mGpsOnPaint.setStyle(Paint.Style.STROKE);
    380         mCameraOnPaint.setStyle(Paint.Style.STROKE);
    381         mFlashlightOnPaint.setStyle(Paint.Style.STROKE);
    382         mWifiRunningPaint.setStyle(Paint.Style.STROKE);
    383         mCpuRunningPaint.setStyle(Paint.Style.STROKE);
    384         mPhoneSignalChart.setColors(com.android.settings.Utils.BADNESS_COLORS);
    385         mDebugRectPaint.setARGB(255, 255, 0, 0);
    386         mDebugRectPaint.setStyle(Paint.Style.STROKE);
    387         mScreenOnPaint.setColor(accentColor);
    388         mGpsOnPaint.setColor(accentColor);
    389         mCameraOnPaint.setColor(accentColor);
    390         mFlashlightOnPaint.setColor(accentColor);
    391         mWifiRunningPaint.setColor(accentColor);
    392         mCpuRunningPaint.setColor(accentColor);
    393         mChargingPaint.setColor(accentColor);
    394 
    395         TypedArray a =
    396             context.obtainStyledAttributes(
    397                 attrs, R.styleable.BatteryHistoryChart, 0, 0);
    398 
    399         final TextAttrs mainTextAttrs = new TextAttrs();
    400         final TextAttrs headTextAttrs = new TextAttrs();
    401         mainTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_android_textAppearance);
    402         headTextAttrs.retrieve(context, a, R.styleable.BatteryHistoryChart_headerAppearance);
    403 
    404         int shadowcolor = 0;
    405         float dx=0, dy=0, r=0;
    406 
    407         int n = a.getIndexCount();
    408         for (int i = 0; i < n; i++) {
    409             int attr = a.getIndex(i);
    410 
    411             switch (attr) {
    412                 case R.styleable.BatteryHistoryChart_android_shadowColor:
    413                     shadowcolor = a.getInt(attr, 0);
    414                     break;
    415 
    416                 case R.styleable.BatteryHistoryChart_android_shadowDx:
    417                     dx = a.getFloat(attr, 0);
    418                     break;
    419 
    420                 case R.styleable.BatteryHistoryChart_android_shadowDy:
    421                     dy = a.getFloat(attr, 0);
    422                     break;
    423 
    424                 case R.styleable.BatteryHistoryChart_android_shadowRadius:
    425                     r = a.getFloat(attr, 0);
    426                     break;
    427 
    428                 case R.styleable.BatteryHistoryChart_android_textColor:
    429                     mainTextAttrs.textColor = a.getColorStateList(attr);
    430                     headTextAttrs.textColor = a.getColorStateList(attr);
    431                     break;
    432 
    433                 case R.styleable.BatteryHistoryChart_android_textSize:
    434                     mainTextAttrs.textSize = a.getDimensionPixelSize(attr, mainTextAttrs.textSize);
    435                     headTextAttrs.textSize = a.getDimensionPixelSize(attr, headTextAttrs.textSize);
    436                     break;
    437 
    438                 case R.styleable.BatteryHistoryChart_android_typeface:
    439                     mainTextAttrs.typefaceIndex = a.getInt(attr, mainTextAttrs.typefaceIndex);
    440                     headTextAttrs.typefaceIndex = a.getInt(attr, headTextAttrs.typefaceIndex);
    441                     break;
    442 
    443                 case R.styleable.BatteryHistoryChart_android_textStyle:
    444                     mainTextAttrs.styleIndex = a.getInt(attr, mainTextAttrs.styleIndex);
    445                     headTextAttrs.styleIndex = a.getInt(attr, headTextAttrs.styleIndex);
    446                     break;
    447 
    448                 case R.styleable.BatteryHistoryChart_barPrimaryColor:
    449                     mBatteryBackgroundPaint.setColor(a.getInt(attr, 0));
    450                     mScreenOnPaint.setColor(a.getInt(attr, 0));
    451                     mGpsOnPaint.setColor(a.getInt(attr, 0));
    452                     mCameraOnPaint.setColor(a.getInt(attr, 0));
    453                     mFlashlightOnPaint.setColor(a.getInt(attr, 0));
    454                     mWifiRunningPaint.setColor(a.getInt(attr, 0));
    455                     mCpuRunningPaint.setColor(a.getInt(attr, 0));
    456                     mChargingPaint.setColor(a.getInt(attr, 0));
    457                     break;
    458 
    459                 case R.styleable.BatteryHistoryChart_barPredictionColor:
    460                     mTimeRemainPaint.setColor(a.getInt(attr, 0));
    461                     break;
    462 
    463                 case R.styleable.BatteryHistoryChart_chartMinHeight:
    464                     mChartMinHeight = a.getDimensionPixelSize(attr, 0);
    465                     break;
    466             }
    467         }
    468 
    469         a.recycle();
    470 
    471         mainTextAttrs.apply(context, mTextPaint);
    472         headTextAttrs.apply(context, mHeaderTextPaint);
    473 
    474         mDateLinePaint.set(mTextPaint);
    475         mDateLinePaint.setStyle(Paint.Style.STROKE);
    476         int hairlineWidth = mThinLineWidth/2;
    477         if (hairlineWidth < 1) {
    478             hairlineWidth = 1;
    479         }
    480         mDateLinePaint.setStrokeWidth(hairlineWidth);
    481         mDateLinePaint.setPathEffect(new DashPathEffect(new float[] {
    482                 mThinLineWidth * 2, mThinLineWidth * 2 }, 0));
    483 
    484         if (shadowcolor != 0) {
    485             mTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
    486             mHeaderTextPaint.setShadowLayer(r, dx, dy, shadowcolor);
    487         }
    488     }
    489 
    490     void setStats(BatteryStats stats, Intent broadcast) {
    491         mStats = stats;
    492         mBatteryBroadcast = broadcast;
    493 
    494         if (DEBUG) Log.d(TAG, "Setting stats...");
    495 
    496         final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
    497 
    498         long uSecTime = mStats.computeBatteryRealtime(elapsedRealtimeUs,
    499                 BatteryStats.STATS_SINCE_CHARGED);
    500         mStatsPeriod = uSecTime;
    501         mChargingLabel = getContext().getString(R.string.battery_stats_charging_label);
    502         mScreenOnLabel = getContext().getString(R.string.battery_stats_screen_on_label);
    503         mGpsOnLabel = getContext().getString(R.string.battery_stats_gps_on_label);
    504         mCameraOnLabel = getContext().getString(R.string.battery_stats_camera_on_label);
    505         mFlashlightOnLabel = getContext().getString(R.string.battery_stats_flashlight_on_label);
    506         mWifiRunningLabel = getContext().getString(R.string.battery_stats_wifi_running_label);
    507         mCpuRunningLabel = getContext().getString(R.string.battery_stats_wake_lock_label);
    508         mPhoneSignalLabel = getContext().getString(R.string.battery_stats_phone_signal_label);
    509 
    510         mMaxPercentLabelString = Utils.formatPercentage(100);
    511         mMinPercentLabelString = Utils.formatPercentage(0);
    512         BatteryInfo.getBatteryInfo(getContext(), info -> {
    513             mInfo = info;
    514             mDrainString = "";
    515             mChargeDurationString = "";
    516             setContentDescription(mInfo.chargeLabel);
    517 
    518             int pos = 0;
    519             int lastInteresting = 0;
    520             byte lastLevel = -1;
    521             mBatLow = 0;
    522             mBatHigh = 100;
    523             mStartWallTime = 0;
    524             mEndDataWallTime = 0;
    525             mEndWallTime = 0;
    526             mHistStart = 0;
    527             mHistEnd = 0;
    528             long lastWallTime = 0;
    529             long lastRealtime = 0;
    530             int aggrStates = 0;
    531             int aggrStates2 = 0;
    532             boolean first = true;
    533             if (stats.startIteratingHistoryLocked()) {
    534                 final HistoryItem rec = new HistoryItem();
    535                 while (stats.getNextHistoryLocked(rec)) {
    536                     pos++;
    537                     if (first) {
    538                         first = false;
    539                         mHistStart = rec.time;
    540                     }
    541                     if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
    542                             || rec.cmd == HistoryItem.CMD_RESET) {
    543                         // If there is a ridiculously large jump in time, then we won't be
    544                         // able to create a good chart with that data, so just ignore the
    545                         // times we got before and pretend like our data extends back from
    546                         // the time we have now.
    547                         // Also, if we are getting a time change and we are less than 5 minutes
    548                         // since the start of the history real time, then also use this new
    549                         // time to compute the base time, since whatever time we had before is
    550                         // pretty much just noise.
    551                         if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L))
    552                                 || rec.time < (mHistStart+(5*60*1000L))) {
    553                             mStartWallTime = 0;
    554                         }
    555                         lastWallTime = rec.currentTime;
    556                         lastRealtime = rec.time;
    557                         if (mStartWallTime == 0) {
    558                             mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
    559                         }
    560                     }
    561                     if (rec.isDeltaData()) {
    562                         if (rec.batteryLevel != lastLevel || pos == 1) {
    563                             lastLevel = rec.batteryLevel;
    564                         }
    565                         lastInteresting = pos;
    566                         mHistDataEnd = rec.time;
    567                         aggrStates |= rec.states;
    568                         aggrStates2 |= rec.states2;
    569                     }
    570                 }
    571             }
    572             mHistEnd = mHistDataEnd + (mInfo.remainingTimeUs/1000);
    573             mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
    574             mEndWallTime = mEndDataWallTime + (mInfo.remainingTimeUs/1000);
    575             mNumHist = lastInteresting;
    576             mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
    577             mHaveFlashlight = (aggrStates2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
    578             mHaveCamera = (aggrStates2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
    579             mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
    580                     || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
    581                     |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
    582                     |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
    583             if (!com.android.settingslib.Utils.isWifiOnly(getContext())) {
    584                 mHavePhoneSignal = true;
    585             }
    586             if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
    587         }, mStats, false /* shortString */);
    588     }
    589 
    590     @Override
    591     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    592         mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString);
    593         mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString);
    594         mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString);
    595         mChargeLabelStringWidth = (int) mHeaderTextPaint.measureText(
    596                 mInfo.chargeLabel.toString());
    597         mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString);
    598         mTextAscent = (int)mTextPaint.ascent();
    599         mTextDescent = (int)mTextPaint.descent();
    600         mHeaderTextAscent = (int)mHeaderTextPaint.ascent();
    601         mHeaderTextDescent = (int)mHeaderTextPaint.descent();
    602         int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent;
    603         mHeaderHeight = headerTextHeight*2 - mTextAscent;
    604         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    605                 getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec));
    606     }
    607 
    608     void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
    609             int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
    610             boolean lastFlashlightOn, boolean lastCameraOn, boolean lastWifiRunning,
    611             boolean lastCpuRunning, Path lastPath) {
    612         if (curLevelPath != null) {
    613             if (lastX >= 0 && lastX < w) {
    614                 if (lastPath != null) {
    615                     lastPath.lineTo(w, y);
    616                 }
    617                 curLevelPath.lineTo(w, y);
    618             }
    619             curLevelPath.lineTo(w, mLevelTop+levelh);
    620             curLevelPath.lineTo(startX, mLevelTop+levelh);
    621             curLevelPath.close();
    622         }
    623 
    624         if (lastCharging) {
    625             mChargingPath.lineTo(w, h-mChargingOffset);
    626         }
    627         if (lastScreenOn) {
    628             mScreenOnPath.lineTo(w, h-mScreenOnOffset);
    629         }
    630         if (lastGpsOn) {
    631             mGpsOnPath.lineTo(w, h-mGpsOnOffset);
    632         }
    633         if (lastFlashlightOn) {
    634             mFlashlightOnPath.lineTo(w, h-mFlashlightOnOffset);
    635         }
    636         if (lastCameraOn) {
    637             mCameraOnPath.lineTo(w, h-mCameraOnOffset);
    638         }
    639         if (lastWifiRunning) {
    640             mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
    641         }
    642         if (lastCpuRunning) {
    643             mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
    644         }
    645         if (mHavePhoneSignal) {
    646             mPhoneSignalChart.finish(w);
    647         }
    648     }
    649 
    650     private boolean is24Hour() {
    651         return DateFormat.is24HourFormat(getContext());
    652     }
    653 
    654     private boolean isDayFirst() {
    655         final String value = LocaleData.get(getResources().getConfiguration().locale)
    656                 .getDateFormat(java.text.DateFormat.SHORT);
    657         return value.indexOf('M') > value.indexOf('d');
    658     }
    659 
    660     @Override
    661     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    662         super.onSizeChanged(w, h, oldw, oldh);
    663 
    664         if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
    665 
    666         if (mLastWidth == w && mLastHeight == h) {
    667             return;
    668         }
    669 
    670         if (mLastWidth == 0 || mLastHeight == 0) {
    671             return;
    672         }
    673 
    674         if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
    675 
    676         mLastWidth = w;
    677         mLastHeight = h;
    678         mBitmap = null;
    679         mCanvas = null;
    680 
    681         int textHeight = mTextDescent - mTextAscent;
    682         if (h > ((textHeight*10)+mChartMinHeight)) {
    683             mLargeMode = true;
    684             if (h > (textHeight*15)) {
    685                 // Plenty of room for the chart.
    686                 mLineWidth = textHeight/2;
    687             } else {
    688                 // Compress lines to make more room for chart.
    689                 mLineWidth = textHeight/3;
    690             }
    691         } else {
    692             mLargeMode = false;
    693             mLineWidth = mThinLineWidth;
    694         }
    695         if (mLineWidth <= 0) mLineWidth = 1;
    696 
    697         mLevelTop = mHeaderHeight;
    698         mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
    699         mLevelRight = w;
    700         int levelWidth = mLevelRight-mLevelLeft;
    701 
    702         mTextPaint.setStrokeWidth(mThinLineWidth);
    703         mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
    704         mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
    705         mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth);
    706         mChargingPaint.setStrokeWidth(mLineWidth);
    707         mScreenOnPaint.setStrokeWidth(mLineWidth);
    708         mGpsOnPaint.setStrokeWidth(mLineWidth);
    709         mCameraOnPaint.setStrokeWidth(mLineWidth);
    710         mFlashlightOnPaint.setStrokeWidth(mLineWidth);
    711         mWifiRunningPaint.setStrokeWidth(mLineWidth);
    712         mCpuRunningPaint.setStrokeWidth(mLineWidth);
    713         mDebugRectPaint.setStrokeWidth(1);
    714 
    715         int fullBarOffset = textHeight + mLineWidth;
    716 
    717         if (mLargeMode) {
    718             mChargingOffset = mLineWidth;
    719             mScreenOnOffset = mChargingOffset + fullBarOffset;
    720             mCpuRunningOffset = mScreenOnOffset + fullBarOffset;
    721             mWifiRunningOffset = mCpuRunningOffset + fullBarOffset;
    722             mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0);
    723             mFlashlightOnOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0);
    724             mCameraOnOffset = mFlashlightOnOffset + (mHaveFlashlight ? fullBarOffset : 0);
    725             mPhoneSignalOffset = mCameraOnOffset + (mHaveCamera ? fullBarOffset : 0);
    726             mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0)
    727                     + mLineWidth*2 + mLineWidth/2;
    728             if (mHavePhoneSignal) {
    729                 mPhoneSignalChart.init(w);
    730             }
    731         } else {
    732             mScreenOnOffset = mGpsOnOffset = mCameraOnOffset = mFlashlightOnOffset =
    733                     mWifiRunningOffset = mCpuRunningOffset = mChargingOffset =
    734                     mPhoneSignalOffset = 0;
    735             mLevelOffset = fullBarOffset + mThinLineWidth*4;
    736             if (mHavePhoneSignal) {
    737                 mPhoneSignalChart.init(0);
    738             }
    739         }
    740 
    741         mBatLevelPath.reset();
    742         mBatGoodPath.reset();
    743         mBatWarnPath.reset();
    744         mTimeRemainPath.reset();
    745         mBatCriticalPath.reset();
    746         mScreenOnPath.reset();
    747         mGpsOnPath.reset();
    748         mFlashlightOnPath.reset();
    749         mCameraOnPath.reset();
    750         mWifiRunningPath.reset();
    751         mCpuRunningPath.reset();
    752         mChargingPath.reset();
    753 
    754         mTimeLabels.clear();
    755         mDateLabels.clear();
    756 
    757         final long walltimeStart = mStartWallTime;
    758         final long walltimeChange = mEndWallTime > walltimeStart
    759                 ? (mEndWallTime-walltimeStart) : 1;
    760         long curWalltime = mStartWallTime;
    761         long lastRealtime = 0;
    762 
    763         final int batLow = mBatLow;
    764         final int batChange = mBatHigh-mBatLow;
    765 
    766         final int levelh = h - mLevelOffset - mLevelTop;
    767         mLevelBottom = mLevelTop + levelh;
    768 
    769         int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
    770         int i = 0;
    771         Path curLevelPath = null;
    772         Path lastLinePath = null;
    773         boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
    774         boolean lastFlashlightOn = false, lastCameraOn = false;
    775         boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false;
    776         int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
    777         final int N = mNumHist;
    778         if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) {
    779             final HistoryItem rec = new HistoryItem();
    780             while (mStats.getNextHistoryLocked(rec) && i < N) {
    781                 if (rec.isDeltaData()) {
    782                     curWalltime += rec.time-lastRealtime;
    783                     lastRealtime = rec.time;
    784                     x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange);
    785                     if (x < 0) {
    786                         x = 0;
    787                     }
    788                     if (false) {
    789                         StringBuilder sb = new StringBuilder(128);
    790                         sb.append("walloff=");
    791                         TimeUtils.formatDuration(curWalltime - walltimeStart, sb);
    792                         sb.append(" wallchange=");
    793                         TimeUtils.formatDuration(walltimeChange, sb);
    794                         sb.append(" x=");
    795                         sb.append(x);
    796                         Log.d("foo", sb.toString());
    797                     }
    798                     y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
    799 
    800                     if (lastX != x) {
    801                         // We have moved by at least a pixel.
    802                         if (lastY != y) {
    803                             // Don't plot changes within a pixel.
    804                             Path path;
    805                             byte value = rec.batteryLevel;
    806                             if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
    807                             else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
    808                             else path = null; //mBatGoodPath;
    809 
    810                             if (path != lastLinePath) {
    811                                 if (lastLinePath != null) {
    812                                     lastLinePath.lineTo(x, y);
    813                                 }
    814                                 if (path != null) {
    815                                     path.moveTo(x, y);
    816                                 }
    817                                 lastLinePath = path;
    818                             } else if (path != null) {
    819                                 path.lineTo(x, y);
    820                             }
    821 
    822                             if (curLevelPath == null) {
    823                                 curLevelPath = mBatLevelPath;
    824                                 curLevelPath.moveTo(x, y);
    825                                 startX = x;
    826                             } else {
    827                                 curLevelPath.lineTo(x, y);
    828                             }
    829                             lastX = x;
    830                             lastY = y;
    831                         }
    832                     }
    833 
    834                     if (mLargeMode) {
    835                         final boolean charging =
    836                             (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
    837                         if (charging != lastCharging) {
    838                             if (charging) {
    839                                 mChargingPath.moveTo(x, h-mChargingOffset);
    840                             } else {
    841                                 mChargingPath.lineTo(x, h-mChargingOffset);
    842                             }
    843                             lastCharging = charging;
    844                         }
    845 
    846                         final boolean screenOn =
    847                             (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
    848                         if (screenOn != lastScreenOn) {
    849                             if (screenOn) {
    850                                 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
    851                             } else {
    852                                 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
    853                             }
    854                             lastScreenOn = screenOn;
    855                         }
    856 
    857                         final boolean gpsOn =
    858                             (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
    859                         if (gpsOn != lastGpsOn) {
    860                             if (gpsOn) {
    861                                 mGpsOnPath.moveTo(x, h-mGpsOnOffset);
    862                             } else {
    863                                 mGpsOnPath.lineTo(x, h-mGpsOnOffset);
    864                             }
    865                             lastGpsOn = gpsOn;
    866                         }
    867 
    868                         final boolean flashlightOn =
    869                             (rec.states2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
    870                         if (flashlightOn != lastFlashlightOn) {
    871                             if (flashlightOn) {
    872                                 mFlashlightOnPath.moveTo(x, h-mFlashlightOnOffset);
    873                             } else {
    874                                 mFlashlightOnPath.lineTo(x, h-mFlashlightOnOffset);
    875                             }
    876                             lastFlashlightOn = flashlightOn;
    877                         }
    878 
    879                         final boolean cameraOn =
    880                             (rec.states2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
    881                         if (cameraOn != lastCameraOn) {
    882                             if (cameraOn) {
    883                                 mCameraOnPath.moveTo(x, h-mCameraOnOffset);
    884                             } else {
    885                                 mCameraOnPath.lineTo(x, h-mCameraOnOffset);
    886                             }
    887                             lastCameraOn = cameraOn;
    888                         }
    889 
    890                         final int wifiSupplState =
    891                             ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
    892                                     >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
    893                         boolean wifiRunning;
    894                         if (lastWifiSupplState != wifiSupplState) {
    895                             lastWifiSupplState = wifiSupplState;
    896                             switch (wifiSupplState) {
    897                                 case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED:
    898                                 case BatteryStats.WIFI_SUPPL_STATE_DORMANT:
    899                                 case BatteryStats.WIFI_SUPPL_STATE_INACTIVE:
    900                                 case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
    901                                 case BatteryStats.WIFI_SUPPL_STATE_INVALID:
    902                                 case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED:
    903                                     wifiRunning = lastWifiSupplRunning = false;
    904                                     break;
    905                                 default:
    906                                     wifiRunning = lastWifiSupplRunning = true;
    907                                     break;
    908                             }
    909                         } else {
    910                             wifiRunning = lastWifiSupplRunning;
    911                         }
    912                         if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
    913                                 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
    914                                 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
    915                             wifiRunning = true;
    916                         }
    917                         if (wifiRunning != lastWifiRunning) {
    918                             if (wifiRunning) {
    919                                 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
    920                             } else {
    921                                 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
    922                             }
    923                             lastWifiRunning = wifiRunning;
    924                         }
    925 
    926                         final boolean cpuRunning =
    927                             (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
    928                         if (cpuRunning != lastCpuRunning) {
    929                             if (cpuRunning) {
    930                                 mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
    931                             } else {
    932                                 mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
    933                             }
    934                             lastCpuRunning = cpuRunning;
    935                         }
    936 
    937                         if (mLargeMode && mHavePhoneSignal) {
    938                             int bin;
    939                             if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
    940                                     >> HistoryItem.STATE_PHONE_STATE_SHIFT)
    941                                     == ServiceState.STATE_POWER_OFF) {
    942                                 bin = 0;
    943                             } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
    944                                 bin = 1;
    945                             } else {
    946                                 bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
    947                                         >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
    948                                 bin += 2;
    949                             }
    950                             mPhoneSignalChart.addTick(x, bin);
    951                         }
    952                     }
    953 
    954                 } else {
    955                     long lastWalltime = curWalltime;
    956                     if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
    957                             || rec.cmd == HistoryItem.CMD_RESET) {
    958                         if (rec.currentTime >= mStartWallTime) {
    959                             curWalltime = rec.currentTime;
    960                         } else {
    961                             curWalltime = mStartWallTime + (rec.time-mHistStart);
    962                         }
    963                         lastRealtime = rec.time;
    964                     }
    965 
    966                     if (rec.cmd != HistoryItem.CMD_OVERFLOW
    967                             && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
    968                                     || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) {
    969                         if (curLevelPath != null) {
    970                             finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
    971                                     lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn,
    972                                     lastCameraOn, lastWifiRunning, lastCpuRunning, lastLinePath);
    973                             lastX = lastY = -1;
    974                             curLevelPath = null;
    975                             lastLinePath = null;
    976                             lastCharging = lastScreenOn = lastGpsOn = lastFlashlightOn =
    977                                     lastCameraOn = lastCpuRunning = false;
    978                         }
    979                     }
    980                 }
    981 
    982                 i++;
    983             }
    984             mStats.finishIteratingHistoryLocked();
    985         }
    986 
    987         if (lastY < 0 || lastX < 0) {
    988             // Didn't get any data...
    989             x = lastX = mLevelLeft;
    990             y = lastY = mLevelTop + levelh - ((mInfo.batteryLevel -batLow)*(levelh-1))/batChange;
    991             Path path;
    992             byte value = (byte)mInfo.batteryLevel;
    993             if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
    994             else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
    995             else path = null; //mBatGoodPath;
    996             if (path != null) {
    997                 path.moveTo(x, y);
    998                 lastLinePath = path;
    999             }
   1000             mBatLevelPath.moveTo(x, y);
   1001             curLevelPath = mBatLevelPath;
   1002             x = w;
   1003         } else {
   1004             // Figure out where the actual data ends on the screen.
   1005             x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
   1006             if (x < 0) {
   1007                 x = 0;
   1008             }
   1009         }
   1010 
   1011         finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
   1012                 lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn, lastCameraOn,
   1013                 lastWifiRunning, lastCpuRunning, lastLinePath);
   1014 
   1015         if (x < w) {
   1016             // If we reserved room for the remaining time, create a final path to draw
   1017             // that part of the UI.
   1018             mTimeRemainPath.moveTo(x, lastY);
   1019             int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange;
   1020             int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange;
   1021             if (mInfo.discharging) {
   1022                 mTimeRemainPath.lineTo(mLevelRight, emptyY);
   1023             } else {
   1024                 mTimeRemainPath.lineTo(mLevelRight, fullY);
   1025                 mTimeRemainPath.lineTo(mLevelRight, emptyY);
   1026             }
   1027             mTimeRemainPath.lineTo(x, emptyY);
   1028             mTimeRemainPath.close();
   1029         }
   1030 
   1031         if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) {
   1032             // Create the time labels at the bottom.
   1033             boolean is24hr = is24Hour();
   1034             Calendar calStart = Calendar.getInstance();
   1035             calStart.setTimeInMillis(mStartWallTime);
   1036             calStart.set(Calendar.MILLISECOND, 0);
   1037             calStart.set(Calendar.SECOND, 0);
   1038             calStart.set(Calendar.MINUTE, 0);
   1039             long startRoundTime = calStart.getTimeInMillis();
   1040             if (startRoundTime < mStartWallTime) {
   1041                 calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1);
   1042                 startRoundTime = calStart.getTimeInMillis();
   1043             }
   1044             Calendar calEnd = Calendar.getInstance();
   1045             calEnd.setTimeInMillis(mEndWallTime);
   1046             calEnd.set(Calendar.MILLISECOND, 0);
   1047             calEnd.set(Calendar.SECOND, 0);
   1048             calEnd.set(Calendar.MINUTE, 0);
   1049             long endRoundTime = calEnd.getTimeInMillis();
   1050             if (startRoundTime < endRoundTime) {
   1051                 addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr);
   1052                 Calendar calMid = Calendar.getInstance();
   1053                 calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2));
   1054                 calMid.set(Calendar.MILLISECOND, 0);
   1055                 calMid.set(Calendar.SECOND, 0);
   1056                 calMid.set(Calendar.MINUTE, 0);
   1057                 long calMidMillis = calMid.getTimeInMillis();
   1058                 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
   1059                     addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr);
   1060                 }
   1061                 addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
   1062             }
   1063 
   1064             // Create the date labels if the chart includes multiple days
   1065             if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) ||
   1066                     calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) {
   1067                 boolean isDayFirst = isDayFirst();
   1068                 calStart.set(Calendar.HOUR_OF_DAY, 0);
   1069                 startRoundTime = calStart.getTimeInMillis();
   1070                 if (startRoundTime < mStartWallTime) {
   1071                     calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1);
   1072                     startRoundTime = calStart.getTimeInMillis();
   1073                 }
   1074                 calEnd.set(Calendar.HOUR_OF_DAY, 0);
   1075                 endRoundTime = calEnd.getTimeInMillis();
   1076                 if (startRoundTime < endRoundTime) {
   1077                     addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst);
   1078                     Calendar calMid = Calendar.getInstance();
   1079 
   1080                     // The middle between two beginnings of days can be anywhere between -1 to 13
   1081                     // after the beginning of the "median" day.
   1082                     calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2)
   1083                                            + 2 * 60 * 60 * 1000);
   1084                     calMid.set(Calendar.HOUR_OF_DAY, 0);
   1085                     calMid.set(Calendar.MINUTE, 0);
   1086                     long calMidMillis = calMid.getTimeInMillis();
   1087                     if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
   1088                         addDateLabel(calMid, mLevelLeft, mLevelRight, isDayFirst);
   1089                     }
   1090                 }
   1091                 addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
   1092             }
   1093         }
   1094 
   1095         if (mTimeLabels.size() < 2) {
   1096             // If there are fewer than 2 time labels, then they are useless.  Just
   1097             // show an axis label giving the entire duration.
   1098             mDurationString = Formatter.formatShortElapsedTime(getContext(),
   1099                     mEndWallTime - mStartWallTime);
   1100             mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
   1101         } else {
   1102             mDurationString = null;
   1103             mDurationStringWidth = 0;
   1104         }
   1105     }
   1106 
   1107     void addTimeLabel(Calendar cal, int levelLeft, int levelRight, boolean is24hr) {
   1108         final long walltimeStart = mStartWallTime;
   1109         final long walltimeChange = mEndWallTime-walltimeStart;
   1110         mTimeLabels.add(new TimeLabel(mTextPaint,
   1111                 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
   1112                         / walltimeChange),
   1113                 cal, is24hr));
   1114     }
   1115 
   1116     void addDateLabel(Calendar cal, int levelLeft, int levelRight, boolean isDayFirst) {
   1117         final long walltimeStart = mStartWallTime;
   1118         final long walltimeChange = mEndWallTime-walltimeStart;
   1119         mDateLabels.add(new DateLabel(mTextPaint,
   1120                 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
   1121                         / walltimeChange),
   1122                 cal, isDayFirst));
   1123     }
   1124 
   1125     @Override
   1126     protected void onDraw(Canvas canvas) {
   1127         super.onDraw(canvas);
   1128 
   1129         final int width = getWidth();
   1130         final int height = getHeight();
   1131 
   1132         //buildBitmap(width, height);
   1133 
   1134         if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
   1135         //canvas.drawBitmap(mBitmap, 0, 0, null);
   1136         drawChart(canvas, width, height);
   1137     }
   1138 
   1139     void buildBitmap(int width, int height) {
   1140         if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
   1141             return;
   1142         }
   1143 
   1144         if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
   1145 
   1146         mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
   1147                 Bitmap.Config.ARGB_8888);
   1148         mCanvas = new Canvas(mBitmap);
   1149         drawChart(mCanvas, width, height);
   1150     }
   1151 
   1152     void drawChart(Canvas canvas, int width, int height) {
   1153         final boolean layoutRtl = isLayoutRtl();
   1154         final int textStartX = layoutRtl ? width : 0;
   1155         final int textEndX = layoutRtl ? 0 : width;
   1156         final Paint.Align textAlignLeft = layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT;
   1157         final Paint.Align textAlignRight = layoutRtl ? Paint.Align.LEFT : Paint.Align.RIGHT;
   1158 
   1159         if (DEBUG) {
   1160             canvas.drawRect(1, 1, width, height, mDebugRectPaint);
   1161         }
   1162 
   1163         if (DEBUG) Log.d(TAG, "Drawing level path.");
   1164         canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
   1165         if (!mTimeRemainPath.isEmpty()) {
   1166             if (DEBUG) Log.d(TAG, "Drawing time remain path.");
   1167             canvas.drawPath(mTimeRemainPath, mTimeRemainPaint);
   1168         }
   1169         if (mTimeLabels.size() > 1) {
   1170             int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
   1171             int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2);
   1172             mTextPaint.setTextAlign(Paint.Align.LEFT);
   1173             int lastX = 0;
   1174             for (int i=0; i<mTimeLabels.size(); i++) {
   1175                 TimeLabel label = mTimeLabels.get(i);
   1176                 if (i == 0) {
   1177                     int x = label.x - label.width/2;
   1178                     if (x < 0) {
   1179                         x = 0;
   1180                     }
   1181                     if (DEBUG) Log.d(TAG, "Drawing left label: " + label.label + " @ " + x);
   1182                     canvas.drawText(label.label, x, y, mTextPaint);
   1183                     canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
   1184                     lastX = x + label.width;
   1185                 } else if (i < (mTimeLabels.size()-1)) {
   1186                     int x = label.x - label.width/2;
   1187                     if (x < (lastX+mTextAscent)) {
   1188                         continue;
   1189                     }
   1190                     TimeLabel nextLabel = mTimeLabels.get(i+1);
   1191                     if (x > (width-nextLabel.width-mTextAscent)) {
   1192                         continue;
   1193                     }
   1194                     if (DEBUG) Log.d(TAG, "Drawing middle label: " + label.label + " @ " + x);
   1195                     canvas.drawText(label.label, x, y, mTextPaint);
   1196                     canvas.drawLine(label.x, ytick, label.x, ytick + mThinLineWidth, mTextPaint);
   1197                     lastX = x + label.width;
   1198                 } else {
   1199                     int x = label.x - label.width/2;
   1200                     if ((x+label.width) >= width) {
   1201                         x = width-1-label.width;
   1202                     }
   1203                     if (DEBUG) Log.d(TAG, "Drawing right label: " + label.label + " @ " + x);
   1204                     canvas.drawText(label.label, x, y, mTextPaint);
   1205                     canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
   1206                 }
   1207             }
   1208         } else if (mDurationString != null) {
   1209             int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
   1210             mTextPaint.setTextAlign(Paint.Align.LEFT);
   1211             canvas.drawText(mDurationString,
   1212                     mLevelLeft + (mLevelRight-mLevelLeft)/2 - mDurationStringWidth/2,
   1213                     y, mTextPaint);
   1214         }
   1215 
   1216         int headerTop = -mHeaderTextAscent + (mHeaderTextDescent-mHeaderTextAscent)/3;
   1217         mHeaderTextPaint.setTextAlign(textAlignLeft);
   1218         if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mInfo.chargeLabel);
   1219         canvas.drawText(mInfo.chargeLabel.toString(), textStartX, headerTop,
   1220                 mHeaderTextPaint);
   1221         int stringHalfWidth = mChargeDurationStringWidth / 2;
   1222         if (layoutRtl) stringHalfWidth = -stringHalfWidth;
   1223         int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2)
   1224                 + (layoutRtl ? mDrainStringWidth : mChargeLabelStringWidth);
   1225         if (DEBUG) Log.d(TAG, "Drawing charge duration string: " + mChargeDurationString);
   1226         canvas.drawText(mChargeDurationString, headerCenter - stringHalfWidth, headerTop,
   1227                 mHeaderTextPaint);
   1228         mHeaderTextPaint.setTextAlign(textAlignRight);
   1229         if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString);
   1230         canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint);
   1231 
   1232         if (!mBatGoodPath.isEmpty()) {
   1233             if (DEBUG) Log.d(TAG, "Drawing good battery path");
   1234             canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
   1235         }
   1236         if (!mBatWarnPath.isEmpty()) {
   1237             if (DEBUG) Log.d(TAG, "Drawing warn battery path");
   1238             canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
   1239         }
   1240         if (!mBatCriticalPath.isEmpty()) {
   1241             if (DEBUG) Log.d(TAG, "Drawing critical battery path");
   1242             canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
   1243         }
   1244         if (mHavePhoneSignal) {
   1245             if (DEBUG) Log.d(TAG, "Drawing phone signal path");
   1246             int top = height-mPhoneSignalOffset - (mLineWidth/2);
   1247             mPhoneSignalChart.draw(canvas, top, mLineWidth);
   1248         }
   1249         if (!mScreenOnPath.isEmpty()) {
   1250             if (DEBUG) Log.d(TAG, "Drawing screen on path");
   1251             canvas.drawPath(mScreenOnPath, mScreenOnPaint);
   1252         }
   1253         if (!mChargingPath.isEmpty()) {
   1254             if (DEBUG) Log.d(TAG, "Drawing charging path");
   1255             canvas.drawPath(mChargingPath, mChargingPaint);
   1256         }
   1257         if (mHaveGps) {
   1258             if (!mGpsOnPath.isEmpty()) {
   1259                 if (DEBUG) Log.d(TAG, "Drawing gps path");
   1260                 canvas.drawPath(mGpsOnPath, mGpsOnPaint);
   1261             }
   1262         }
   1263         if (mHaveFlashlight) {
   1264             if (!mFlashlightOnPath.isEmpty()) {
   1265                 if (DEBUG) Log.d(TAG, "Drawing flashlight path");
   1266                 canvas.drawPath(mFlashlightOnPath, mFlashlightOnPaint);
   1267             }
   1268         }
   1269         if (mHaveCamera) {
   1270             if (!mCameraOnPath.isEmpty()) {
   1271                 if (DEBUG) Log.d(TAG, "Drawing camera path");
   1272                 canvas.drawPath(mCameraOnPath, mCameraOnPaint);
   1273             }
   1274         }
   1275         if (mHaveWifi) {
   1276             if (!mWifiRunningPath.isEmpty()) {
   1277                 if (DEBUG) Log.d(TAG, "Drawing wifi path");
   1278                 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
   1279             }
   1280         }
   1281         if (!mCpuRunningPath.isEmpty()) {
   1282             if (DEBUG) Log.d(TAG, "Drawing running path");
   1283             canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
   1284         }
   1285 
   1286         if (mLargeMode) {
   1287             if (DEBUG) Log.d(TAG, "Drawing large mode labels");
   1288             Paint.Align align = mTextPaint.getTextAlign();
   1289             mTextPaint.setTextAlign(textAlignLeft);  // large-mode labels always aligned to start
   1290             if (mHavePhoneSignal) {
   1291                 canvas.drawText(mPhoneSignalLabel, textStartX,
   1292                         height - mPhoneSignalOffset - mTextDescent, mTextPaint);
   1293             }
   1294             if (mHaveGps) {
   1295                 canvas.drawText(mGpsOnLabel, textStartX,
   1296                         height - mGpsOnOffset - mTextDescent, mTextPaint);
   1297             }
   1298             if (mHaveFlashlight) {
   1299                 canvas.drawText(mFlashlightOnLabel, textStartX,
   1300                         height - mFlashlightOnOffset - mTextDescent, mTextPaint);
   1301             }
   1302             if (mHaveCamera) {
   1303                 canvas.drawText(mCameraOnLabel, textStartX,
   1304                         height - mCameraOnOffset - mTextDescent, mTextPaint);
   1305             }
   1306             if (mHaveWifi) {
   1307                 canvas.drawText(mWifiRunningLabel, textStartX,
   1308                         height - mWifiRunningOffset - mTextDescent, mTextPaint);
   1309             }
   1310             canvas.drawText(mCpuRunningLabel, textStartX,
   1311                     height - mCpuRunningOffset - mTextDescent, mTextPaint);
   1312             canvas.drawText(mChargingLabel, textStartX,
   1313                     height - mChargingOffset - mTextDescent, mTextPaint);
   1314             canvas.drawText(mScreenOnLabel, textStartX,
   1315                     height - mScreenOnOffset - mTextDescent, mTextPaint);
   1316             mTextPaint.setTextAlign(align);
   1317         }
   1318 
   1319         canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
   1320                 mLevelBottom+(mThinLineWidth/2), mTextPaint);
   1321         if (mLargeMode) {
   1322             for (int i=0; i<10; i++) {
   1323                 int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10;
   1324                 canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y,
   1325                         mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint);
   1326             }
   1327         }
   1328         if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth
   1329                 + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString));
   1330         canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint);
   1331         canvas.drawText(mMinPercentLabelString,
   1332                 mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth,
   1333                 mLevelBottom - mThinLineWidth, mTextPaint);
   1334         canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width,
   1335                 mLevelBottom+mThinLineWidth, mTextPaint);
   1336 
   1337         if (mDateLabels.size() > 0) {
   1338             int ytop = mLevelTop + mTextAscent;
   1339             int ybottom = mLevelBottom;
   1340             int lastLeft = mLevelRight;
   1341             mTextPaint.setTextAlign(Paint.Align.LEFT);
   1342             for (int i=mDateLabels.size()-1; i>=0; i--) {
   1343                 DateLabel label = mDateLabels.get(i);
   1344                 int left = label.x - mThinLineWidth;
   1345                 int x = label.x + mThinLineWidth*2;
   1346                 if ((x+label.width) >= lastLeft) {
   1347                     x = label.x - mThinLineWidth*2 - label.width;
   1348                     left = x - mThinLineWidth;
   1349                     if (left >= lastLeft) {
   1350                         // okay we give up.
   1351                         continue;
   1352                     }
   1353                 }
   1354                 if (left < mLevelLeft) {
   1355                     // Won't fit on left, give up.
   1356                     continue;
   1357                 }
   1358                 mDateLinePath.reset();
   1359                 mDateLinePath.moveTo(label.x, ytop);
   1360                 mDateLinePath.lineTo(label.x, ybottom);
   1361                 canvas.drawPath(mDateLinePath, mDateLinePaint);
   1362                 canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint);
   1363             }
   1364         }
   1365     }
   1366 }
   1367