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