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.text.format.DateFormat;
     24 import android.text.format.Formatter;
     25 import android.util.Log;
     26 import android.util.TimeUtils;
     27 import com.android.settings.R;
     28 import com.android.settings.Utils;
     29 
     30 import android.content.Context;
     31 import android.content.res.ColorStateList;
     32 import android.content.res.TypedArray;
     33 import android.graphics.Canvas;
     34 import android.graphics.Paint;
     35 import android.graphics.Path;
     36 import android.graphics.Typeface;
     37 import android.os.BatteryStats;
     38 import android.os.SystemClock;
     39 import android.os.BatteryStats.HistoryItem;
     40 import android.telephony.ServiceState;
     41 import android.text.TextPaint;
     42 import android.util.AttributeSet;
     43 import android.util.TypedValue;
     44 import android.view.View;
     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     int mBatteryLevel;
    162     String mMaxPercentLabelString;
    163     String mMinPercentLabelString;
    164     String mDurationString;
    165     String mChargeLabelString;
    166     String mChargeDurationString;
    167     String mDrainString;
    168     String mChargingLabel;
    169     String mScreenOnLabel;
    170     String mGpsOnLabel;
    171     String mCameraOnLabel;
    172     String mFlashlightOnLabel;
    173     String mWifiRunningLabel;
    174     String mCpuRunningLabel;
    175     String mPhoneSignalLabel;
    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     boolean mDischarging;
    223     int mBatLow;
    224     int mBatHigh;
    225     boolean mHaveWifi;
    226     boolean mHaveGps;
    227     boolean mHavePhoneSignal;
    228     boolean mHaveCamera;
    229     boolean mHaveFlashlight;
    230 
    231     final ArrayList<TimeLabel> mTimeLabels = new ArrayList<TimeLabel>();
    232     final ArrayList<DateLabel> mDateLabels = new ArrayList<DateLabel>();
    233 
    234     Bitmap mBitmap;
    235     Canvas mCanvas;
    236 
    237     static class TextAttrs {
    238         ColorStateList textColor = null;
    239         int textSize = 15;
    240         int typefaceIndex = -1;
    241         int styleIndex = -1;
    242 
    243         void retrieve(Context context, TypedArray from, int index) {
    244             TypedArray appearance = null;
    245             int ap = from.getResourceId(index, -1);
    246             if (ap != -1) {
    247                 appearance = context.obtainStyledAttributes(ap,
    248                                     com.android.internal.R.styleable.TextAppearance);
    249             }
    250             if (appearance != null) {
    251                 int n = appearance.getIndexCount();
    252                 for (int i = 0; i < n; i++) {
    253                     int attr = appearance.getIndex(i);
    254 
    255                     switch (attr) {
    256                     case com.android.internal.R.styleable.TextAppearance_textColor:
    257                         textColor = appearance.getColorStateList(attr);
    258                         break;
    259 
    260                     case com.android.internal.R.styleable.TextAppearance_textSize:
    261                         textSize = appearance.getDimensionPixelSize(attr, textSize);
    262                         break;
    263 
    264                     case com.android.internal.R.styleable.TextAppearance_typeface:
    265                         typefaceIndex = appearance.getInt(attr, -1);
    266                         break;
    267 
    268                     case com.android.internal.R.styleable.TextAppearance_textStyle:
    269                         styleIndex = appearance.getInt(attr, -1);
    270                         break;
    271                     }
    272                 }
    273 
    274                 appearance.recycle();
    275             }
    276         }
    277 
    278         void apply(Context context, TextPaint paint) {
    279             paint.density = context.getResources().getDisplayMetrics().density;
    280             paint.setCompatibilityScaling(
    281                     context.getResources().getCompatibilityInfo().applicationScale);
    282 
    283             paint.setColor(textColor.getDefaultColor());
    284             paint.setTextSize(textSize);
    285 
    286             Typeface tf = null;
    287             switch (typefaceIndex) {
    288                 case SANS:
    289                     tf = Typeface.SANS_SERIF;
    290                     break;
    291 
    292                 case SERIF:
    293                     tf = Typeface.SERIF;
    294                     break;
    295 
    296                 case MONOSPACE:
    297                     tf = Typeface.MONOSPACE;
    298                     break;
    299             }
    300 
    301             setTypeface(paint, tf, styleIndex);
    302         }
    303 
    304         public void setTypeface(TextPaint paint, Typeface tf, int style) {
    305             if (style > 0) {
    306                 if (tf == null) {
    307                     tf = Typeface.defaultFromStyle(style);
    308                 } else {
    309                     tf = Typeface.create(tf, style);
    310                 }
    311 
    312                 paint.setTypeface(tf);
    313                 // now compute what (if any) algorithmic styling is needed
    314                 int typefaceStyle = tf != null ? tf.getStyle() : 0;
    315                 int need = style & ~typefaceStyle;
    316                 paint.setFakeBoldText((need & Typeface.BOLD) != 0);
    317                 paint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
    318             } else {
    319                 paint.setFakeBoldText(false);
    320                 paint.setTextSkewX(0);
    321                 paint.setTypeface(tf);
    322             }
    323         }
    324     }
    325 
    326     static class TimeLabel {
    327         final int x;
    328         final String label;
    329         final int width;
    330 
    331         TimeLabel(TextPaint paint, int x, Calendar cal, boolean use24hr) {
    332             this.x = x;
    333             final String bestFormat = DateFormat.getBestDateTimePattern(
    334                     Locale.getDefault(), use24hr ? "km" : "ha");
    335             label = DateFormat.format(bestFormat, cal).toString();
    336             width = (int)paint.measureText(label);
    337         }
    338     }
    339 
    340     static class DateLabel {
    341         final int x;
    342         final String label;
    343         final int width;
    344 
    345         DateLabel(TextPaint paint, int x, Calendar cal, boolean dayFirst) {
    346             this.x = x;
    347             final String bestFormat = DateFormat.getBestDateTimePattern(
    348                     Locale.getDefault(), dayFirst ? "dM" : "Md");
    349             label = DateFormat.format(bestFormat, cal).toString();
    350             width = (int)paint.measureText(label);
    351         }
    352     }
    353 
    354     public BatteryHistoryChart(Context context, AttributeSet attrs) {
    355         super(context, attrs);
    356 
    357         if (DEBUG) Log.d(TAG, "New BatteryHistoryChart!");
    358 
    359         mBatteryWarnLevel = mContext.getResources().getInteger(
    360                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
    361         mBatteryCriticalLevel = mContext.getResources().getInteger(
    362                 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
    363 
    364         mThinLineWidth = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
    365                 2, getResources().getDisplayMetrics());
    366 
    367         mBatteryBackgroundPaint.setColor(0xFF009688);
    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(0xFF009688);
    388         mGpsOnPaint.setColor(0xFF009688);
    389         mCameraOnPaint.setColor(0xFF009688);
    390         mFlashlightOnPaint.setColor(0xFF009688);
    391         mWifiRunningPaint.setColor(0xFF009688);
    392         mCpuRunningPaint.setColor(0xFF009688);
    393         mChargingPaint.setColor(0xFF009688);
    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 
    513         mBatteryLevel = com.android.settings.Utils.getBatteryLevel(mBatteryBroadcast);
    514         String batteryPercentString = Utils.formatPercentage(mBatteryLevel);
    515         long remainingTimeUs = 0;
    516         mDischarging = true;
    517         if (mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) == 0) {
    518             final long drainTime = mStats.computeBatteryTimeRemaining(elapsedRealtimeUs);
    519             if (drainTime > 0) {
    520                 remainingTimeUs = drainTime;
    521                 String timeString = Formatter.formatShortElapsedTime(getContext(),
    522                         drainTime / 1000);
    523                 mChargeLabelString = getContext().getResources().getString(
    524                         R.string.power_discharging_duration, batteryPercentString, timeString);
    525             } else {
    526                 mChargeLabelString = batteryPercentString;
    527             }
    528         } else {
    529             final long chargeTime = mStats.computeChargeTimeRemaining(elapsedRealtimeUs);
    530             final String statusLabel = com.android.settings.Utils.getBatteryStatus(getResources(),
    531                     mBatteryBroadcast);
    532             final int status = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
    533                     BatteryManager.BATTERY_STATUS_UNKNOWN);
    534             if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
    535                 mDischarging = false;
    536                 remainingTimeUs = chargeTime;
    537                 String timeString = Formatter.formatShortElapsedTime(getContext(),
    538                         chargeTime / 1000);
    539                 int plugType = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
    540                 int resId;
    541                 if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
    542                     resId = R.string.power_charging_duration_ac;
    543                 } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
    544                     resId = R.string.power_charging_duration_usb;
    545                 } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
    546                     resId = R.string.power_charging_duration_wireless;
    547                 } else {
    548                     resId = R.string.power_charging_duration;
    549                 }
    550                 mChargeLabelString = getContext().getResources().getString(
    551                         resId, batteryPercentString, timeString);
    552             } else {
    553                 mChargeLabelString = getContext().getResources().getString(
    554                         R.string.power_charging, batteryPercentString, statusLabel);
    555             }
    556         }
    557         mDrainString = "";
    558         mChargeDurationString = "";
    559         setContentDescription(mChargeLabelString);
    560 
    561         int pos = 0;
    562         int lastInteresting = 0;
    563         byte lastLevel = -1;
    564         mBatLow = 0;
    565         mBatHigh = 100;
    566         mStartWallTime = 0;
    567         mEndDataWallTime = 0;
    568         mEndWallTime = 0;
    569         mHistStart = 0;
    570         mHistEnd = 0;
    571         long lastWallTime = 0;
    572         long lastRealtime = 0;
    573         int aggrStates = 0;
    574         int aggrStates2 = 0;
    575         boolean first = true;
    576         if (stats.startIteratingHistoryLocked()) {
    577             final HistoryItem rec = new HistoryItem();
    578             while (stats.getNextHistoryLocked(rec)) {
    579                 pos++;
    580                 if (first) {
    581                     first = false;
    582                     mHistStart = rec.time;
    583                 }
    584                 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
    585                         || rec.cmd == HistoryItem.CMD_RESET) {
    586                     // If there is a ridiculously large jump in time, then we won't be
    587                     // able to create a good chart with that data, so just ignore the
    588                     // times we got before and pretend like our data extends back from
    589                     // the time we have now.
    590                     // Also, if we are getting a time change and we are less than 5 minutes
    591                     // since the start of the history real time, then also use this new
    592                     // time to compute the base time, since whatever time we had before is
    593                     // pretty much just noise.
    594                     if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L))
    595                             || rec.time < (mHistStart+(5*60*1000L))) {
    596                         mStartWallTime = 0;
    597                     }
    598                     lastWallTime = rec.currentTime;
    599                     lastRealtime = rec.time;
    600                     if (mStartWallTime == 0) {
    601                         mStartWallTime = lastWallTime - (lastRealtime-mHistStart);
    602                     }
    603                 }
    604                 if (rec.isDeltaData()) {
    605                     if (rec.batteryLevel != lastLevel || pos == 1) {
    606                         lastLevel = rec.batteryLevel;
    607                     }
    608                     lastInteresting = pos;
    609                     mHistDataEnd = rec.time;
    610                     aggrStates |= rec.states;
    611                     aggrStates2 |= rec.states2;
    612                 }
    613             }
    614         }
    615         mHistEnd = mHistDataEnd + (remainingTimeUs/1000);
    616         mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime;
    617         mEndWallTime = mEndDataWallTime + (remainingTimeUs/1000);
    618         mNumHist = lastInteresting;
    619         mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0;
    620         mHaveFlashlight = (aggrStates2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
    621         mHaveCamera = (aggrStates2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
    622         mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0
    623                 || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
    624                         |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
    625                         |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0;
    626         if (!com.android.settings.Utils.isWifiOnly(getContext())) {
    627             mHavePhoneSignal = true;
    628         }
    629         if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1;
    630     }
    631 
    632     @Override
    633     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    634         mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString);
    635         mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString);
    636         mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString);
    637         mChargeLabelStringWidth = (int)mHeaderTextPaint.measureText(mChargeLabelString);
    638         mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString);
    639         mTextAscent = (int)mTextPaint.ascent();
    640         mTextDescent = (int)mTextPaint.descent();
    641         mHeaderTextAscent = (int)mHeaderTextPaint.ascent();
    642         mHeaderTextDescent = (int)mHeaderTextPaint.descent();
    643         int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent;
    644         mHeaderHeight = headerTextHeight*2 - mTextAscent;
    645         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    646                 getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec));
    647     }
    648 
    649     void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
    650             int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn,
    651             boolean lastFlashlightOn, boolean lastCameraOn, boolean lastWifiRunning,
    652             boolean lastCpuRunning, Path lastPath) {
    653         if (curLevelPath != null) {
    654             if (lastX >= 0 && lastX < w) {
    655                 if (lastPath != null) {
    656                     lastPath.lineTo(w, y);
    657                 }
    658                 curLevelPath.lineTo(w, y);
    659             }
    660             curLevelPath.lineTo(w, mLevelTop+levelh);
    661             curLevelPath.lineTo(startX, mLevelTop+levelh);
    662             curLevelPath.close();
    663         }
    664 
    665         if (lastCharging) {
    666             mChargingPath.lineTo(w, h-mChargingOffset);
    667         }
    668         if (lastScreenOn) {
    669             mScreenOnPath.lineTo(w, h-mScreenOnOffset);
    670         }
    671         if (lastGpsOn) {
    672             mGpsOnPath.lineTo(w, h-mGpsOnOffset);
    673         }
    674         if (lastFlashlightOn) {
    675             mFlashlightOnPath.lineTo(w, h-mFlashlightOnOffset);
    676         }
    677         if (lastCameraOn) {
    678             mCameraOnPath.lineTo(w, h-mCameraOnOffset);
    679         }
    680         if (lastWifiRunning) {
    681             mWifiRunningPath.lineTo(w, h-mWifiRunningOffset);
    682         }
    683         if (lastCpuRunning) {
    684             mCpuRunningPath.lineTo(w, h - mCpuRunningOffset);
    685         }
    686         if (mHavePhoneSignal) {
    687             mPhoneSignalChart.finish(w);
    688         }
    689     }
    690 
    691     private boolean is24Hour() {
    692         return DateFormat.is24HourFormat(getContext());
    693     }
    694 
    695     private boolean isDayFirst() {
    696         final String value = LocaleData.get(getResources().getConfiguration().locale)
    697                 .getDateFormat(java.text.DateFormat.SHORT);
    698         return value.indexOf('M') > value.indexOf('d');
    699     }
    700 
    701     @Override
    702     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    703         super.onSizeChanged(w, h, oldw, oldh);
    704 
    705         if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h);
    706 
    707         if (mLastWidth == w && mLastHeight == h) {
    708             return;
    709         }
    710 
    711         if (mLastWidth == 0 || mLastHeight == 0) {
    712             return;
    713         }
    714 
    715         if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h);
    716 
    717         mLastWidth = w;
    718         mLastHeight = h;
    719         mBitmap = null;
    720         mCanvas = null;
    721 
    722         int textHeight = mTextDescent - mTextAscent;
    723         if (h > ((textHeight*10)+mChartMinHeight)) {
    724             mLargeMode = true;
    725             if (h > (textHeight*15)) {
    726                 // Plenty of room for the chart.
    727                 mLineWidth = textHeight/2;
    728             } else {
    729                 // Compress lines to make more room for chart.
    730                 mLineWidth = textHeight/3;
    731             }
    732         } else {
    733             mLargeMode = false;
    734             mLineWidth = mThinLineWidth;
    735         }
    736         if (mLineWidth <= 0) mLineWidth = 1;
    737 
    738         mLevelTop = mHeaderHeight;
    739         mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3;
    740         mLevelRight = w;
    741         int levelWidth = mLevelRight-mLevelLeft;
    742 
    743         mTextPaint.setStrokeWidth(mThinLineWidth);
    744         mBatteryGoodPaint.setStrokeWidth(mThinLineWidth);
    745         mBatteryWarnPaint.setStrokeWidth(mThinLineWidth);
    746         mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth);
    747         mChargingPaint.setStrokeWidth(mLineWidth);
    748         mScreenOnPaint.setStrokeWidth(mLineWidth);
    749         mGpsOnPaint.setStrokeWidth(mLineWidth);
    750         mCameraOnPaint.setStrokeWidth(mLineWidth);
    751         mFlashlightOnPaint.setStrokeWidth(mLineWidth);
    752         mWifiRunningPaint.setStrokeWidth(mLineWidth);
    753         mCpuRunningPaint.setStrokeWidth(mLineWidth);
    754         mDebugRectPaint.setStrokeWidth(1);
    755 
    756         int fullBarOffset = textHeight + mLineWidth;
    757 
    758         if (mLargeMode) {
    759             mChargingOffset = mLineWidth;
    760             mScreenOnOffset = mChargingOffset + fullBarOffset;
    761             mCpuRunningOffset = mScreenOnOffset + fullBarOffset;
    762             mWifiRunningOffset = mCpuRunningOffset + fullBarOffset;
    763             mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0);
    764             mFlashlightOnOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0);
    765             mCameraOnOffset = mFlashlightOnOffset + (mHaveFlashlight ? fullBarOffset : 0);
    766             mPhoneSignalOffset = mCameraOnOffset + (mHaveCamera ? fullBarOffset : 0);
    767             mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0)
    768                     + mLineWidth*2 + mLineWidth/2;
    769             if (mHavePhoneSignal) {
    770                 mPhoneSignalChart.init(w);
    771             }
    772         } else {
    773             mScreenOnOffset = mGpsOnOffset = mCameraOnOffset = mFlashlightOnOffset =
    774                     mWifiRunningOffset = mCpuRunningOffset = mChargingOffset =
    775                     mPhoneSignalOffset = 0;
    776             mLevelOffset = fullBarOffset + mThinLineWidth*4;
    777             if (mHavePhoneSignal) {
    778                 mPhoneSignalChart.init(0);
    779             }
    780         }
    781 
    782         mBatLevelPath.reset();
    783         mBatGoodPath.reset();
    784         mBatWarnPath.reset();
    785         mTimeRemainPath.reset();
    786         mBatCriticalPath.reset();
    787         mScreenOnPath.reset();
    788         mGpsOnPath.reset();
    789         mFlashlightOnPath.reset();
    790         mCameraOnPath.reset();
    791         mWifiRunningPath.reset();
    792         mCpuRunningPath.reset();
    793         mChargingPath.reset();
    794 
    795         mTimeLabels.clear();
    796         mDateLabels.clear();
    797 
    798         final long walltimeStart = mStartWallTime;
    799         final long walltimeChange = mEndWallTime > walltimeStart
    800                 ? (mEndWallTime-walltimeStart) : 1;
    801         long curWalltime = mStartWallTime;
    802         long lastRealtime = 0;
    803 
    804         final int batLow = mBatLow;
    805         final int batChange = mBatHigh-mBatLow;
    806 
    807         final int levelh = h - mLevelOffset - mLevelTop;
    808         mLevelBottom = mLevelTop + levelh;
    809 
    810         int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1;
    811         int i = 0;
    812         Path curLevelPath = null;
    813         Path lastLinePath = null;
    814         boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false;
    815         boolean lastFlashlightOn = false, lastCameraOn = false;
    816         boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false;
    817         int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID;
    818         final int N = mNumHist;
    819         if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) {
    820             final HistoryItem rec = new HistoryItem();
    821             while (mStats.getNextHistoryLocked(rec) && i < N) {
    822                 if (rec.isDeltaData()) {
    823                     curWalltime += rec.time-lastRealtime;
    824                     lastRealtime = rec.time;
    825                     x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange);
    826                     if (x < 0) {
    827                         x = 0;
    828                     }
    829                     if (false) {
    830                         StringBuilder sb = new StringBuilder(128);
    831                         sb.append("walloff=");
    832                         TimeUtils.formatDuration(curWalltime - walltimeStart, sb);
    833                         sb.append(" wallchange=");
    834                         TimeUtils.formatDuration(walltimeChange, sb);
    835                         sb.append(" x=");
    836                         sb.append(x);
    837                         Log.d("foo", sb.toString());
    838                     }
    839                     y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange;
    840 
    841                     if (lastX != x) {
    842                         // We have moved by at least a pixel.
    843                         if (lastY != y) {
    844                             // Don't plot changes within a pixel.
    845                             Path path;
    846                             byte value = rec.batteryLevel;
    847                             if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
    848                             else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
    849                             else path = null; //mBatGoodPath;
    850 
    851                             if (path != lastLinePath) {
    852                                 if (lastLinePath != null) {
    853                                     lastLinePath.lineTo(x, y);
    854                                 }
    855                                 if (path != null) {
    856                                     path.moveTo(x, y);
    857                                 }
    858                                 lastLinePath = path;
    859                             } else if (path != null) {
    860                                 path.lineTo(x, y);
    861                             }
    862 
    863                             if (curLevelPath == null) {
    864                                 curLevelPath = mBatLevelPath;
    865                                 curLevelPath.moveTo(x, y);
    866                                 startX = x;
    867                             } else {
    868                                 curLevelPath.lineTo(x, y);
    869                             }
    870                             lastX = x;
    871                             lastY = y;
    872                         }
    873                     }
    874 
    875                     if (mLargeMode) {
    876                         final boolean charging =
    877                             (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
    878                         if (charging != lastCharging) {
    879                             if (charging) {
    880                                 mChargingPath.moveTo(x, h-mChargingOffset);
    881                             } else {
    882                                 mChargingPath.lineTo(x, h-mChargingOffset);
    883                             }
    884                             lastCharging = charging;
    885                         }
    886 
    887                         final boolean screenOn =
    888                             (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0;
    889                         if (screenOn != lastScreenOn) {
    890                             if (screenOn) {
    891                                 mScreenOnPath.moveTo(x, h-mScreenOnOffset);
    892                             } else {
    893                                 mScreenOnPath.lineTo(x, h-mScreenOnOffset);
    894                             }
    895                             lastScreenOn = screenOn;
    896                         }
    897 
    898                         final boolean gpsOn =
    899                             (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0;
    900                         if (gpsOn != lastGpsOn) {
    901                             if (gpsOn) {
    902                                 mGpsOnPath.moveTo(x, h-mGpsOnOffset);
    903                             } else {
    904                                 mGpsOnPath.lineTo(x, h-mGpsOnOffset);
    905                             }
    906                             lastGpsOn = gpsOn;
    907                         }
    908 
    909                         final boolean flashlightOn =
    910                             (rec.states2&HistoryItem.STATE2_FLASHLIGHT_FLAG) != 0;
    911                         if (flashlightOn != lastFlashlightOn) {
    912                             if (flashlightOn) {
    913                                 mFlashlightOnPath.moveTo(x, h-mFlashlightOnOffset);
    914                             } else {
    915                                 mFlashlightOnPath.lineTo(x, h-mFlashlightOnOffset);
    916                             }
    917                             lastFlashlightOn = flashlightOn;
    918                         }
    919 
    920                         final boolean cameraOn =
    921                             (rec.states2&HistoryItem.STATE2_CAMERA_FLAG) != 0;
    922                         if (cameraOn != lastCameraOn) {
    923                             if (cameraOn) {
    924                                 mCameraOnPath.moveTo(x, h-mCameraOnOffset);
    925                             } else {
    926                                 mCameraOnPath.lineTo(x, h-mCameraOnOffset);
    927                             }
    928                             lastCameraOn = cameraOn;
    929                         }
    930 
    931                         final int wifiSupplState =
    932                             ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK)
    933                                     >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT);
    934                         boolean wifiRunning;
    935                         if (lastWifiSupplState != wifiSupplState) {
    936                             lastWifiSupplState = wifiSupplState;
    937                             switch (wifiSupplState) {
    938                                 case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED:
    939                                 case BatteryStats.WIFI_SUPPL_STATE_DORMANT:
    940                                 case BatteryStats.WIFI_SUPPL_STATE_INACTIVE:
    941                                 case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED:
    942                                 case BatteryStats.WIFI_SUPPL_STATE_INVALID:
    943                                 case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED:
    944                                     wifiRunning = lastWifiSupplRunning = false;
    945                                     break;
    946                                 default:
    947                                     wifiRunning = lastWifiSupplRunning = true;
    948                                     break;
    949                             }
    950                         } else {
    951                             wifiRunning = lastWifiSupplRunning;
    952                         }
    953                         if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG
    954                                 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG
    955                                 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) {
    956                             wifiRunning = true;
    957                         }
    958                         if (wifiRunning != lastWifiRunning) {
    959                             if (wifiRunning) {
    960                                 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset);
    961                             } else {
    962                                 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset);
    963                             }
    964                             lastWifiRunning = wifiRunning;
    965                         }
    966 
    967                         final boolean cpuRunning =
    968                             (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0;
    969                         if (cpuRunning != lastCpuRunning) {
    970                             if (cpuRunning) {
    971                                 mCpuRunningPath.moveTo(x, h - mCpuRunningOffset);
    972                             } else {
    973                                 mCpuRunningPath.lineTo(x, h - mCpuRunningOffset);
    974                             }
    975                             lastCpuRunning = cpuRunning;
    976                         }
    977 
    978                         if (mLargeMode && mHavePhoneSignal) {
    979                             int bin;
    980                             if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK)
    981                                     >> HistoryItem.STATE_PHONE_STATE_SHIFT)
    982                                     == ServiceState.STATE_POWER_OFF) {
    983                                 bin = 0;
    984                             } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) {
    985                                 bin = 1;
    986                             } else {
    987                                 bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK)
    988                                         >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT;
    989                                 bin += 2;
    990                             }
    991                             mPhoneSignalChart.addTick(x, bin);
    992                         }
    993                     }
    994 
    995                 } else {
    996                     long lastWalltime = curWalltime;
    997                     if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
    998                             || rec.cmd == HistoryItem.CMD_RESET) {
    999                         if (rec.currentTime >= mStartWallTime) {
   1000                             curWalltime = rec.currentTime;
   1001                         } else {
   1002                             curWalltime = mStartWallTime + (rec.time-mHistStart);
   1003                         }
   1004                         lastRealtime = rec.time;
   1005                     }
   1006 
   1007                     if (rec.cmd != HistoryItem.CMD_OVERFLOW
   1008                             && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
   1009                                     || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) {
   1010                         if (curLevelPath != null) {
   1011                             finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
   1012                                     lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn,
   1013                                     lastCameraOn, lastWifiRunning, lastCpuRunning, lastLinePath);
   1014                             lastX = lastY = -1;
   1015                             curLevelPath = null;
   1016                             lastLinePath = null;
   1017                             lastCharging = lastScreenOn = lastGpsOn = lastFlashlightOn =
   1018                                     lastCameraOn = lastCpuRunning = false;
   1019                         }
   1020                     }
   1021                 }
   1022 
   1023                 i++;
   1024             }
   1025             mStats.finishIteratingHistoryLocked();
   1026         }
   1027 
   1028         if (lastY < 0 || lastX < 0) {
   1029             // Didn't get any data...
   1030             x = lastX = mLevelLeft;
   1031             y = lastY = mLevelTop + levelh - ((mBatteryLevel-batLow)*(levelh-1))/batChange;
   1032             Path path;
   1033             byte value = (byte)mBatteryLevel;
   1034             if (value <= mBatteryCriticalLevel) path = mBatCriticalPath;
   1035             else if (value <= mBatteryWarnLevel) path = mBatWarnPath;
   1036             else path = null; //mBatGoodPath;
   1037             if (path != null) {
   1038                 path.moveTo(x, y);
   1039                 lastLinePath = path;
   1040             }
   1041             mBatLevelPath.moveTo(x, y);
   1042             curLevelPath = mBatLevelPath;
   1043             x = w;
   1044         } else {
   1045             // Figure out where the actual data ends on the screen.
   1046             x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange);
   1047             if (x < 0) {
   1048                 x = 0;
   1049             }
   1050         }
   1051 
   1052         finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX,
   1053                 lastCharging, lastScreenOn, lastGpsOn, lastFlashlightOn, lastCameraOn,
   1054                 lastWifiRunning, lastCpuRunning, lastLinePath);
   1055 
   1056         if (x < w) {
   1057             // If we reserved room for the remaining time, create a final path to draw
   1058             // that part of the UI.
   1059             mTimeRemainPath.moveTo(x, lastY);
   1060             int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange;
   1061             int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange;
   1062             if (mDischarging) {
   1063                 mTimeRemainPath.lineTo(mLevelRight, emptyY);
   1064             } else {
   1065                 mTimeRemainPath.lineTo(mLevelRight, fullY);
   1066                 mTimeRemainPath.lineTo(mLevelRight, emptyY);
   1067             }
   1068             mTimeRemainPath.lineTo(x, emptyY);
   1069             mTimeRemainPath.close();
   1070         }
   1071 
   1072         if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) {
   1073             // Create the time labels at the bottom.
   1074             boolean is24hr = is24Hour();
   1075             Calendar calStart = Calendar.getInstance();
   1076             calStart.setTimeInMillis(mStartWallTime);
   1077             calStart.set(Calendar.MILLISECOND, 0);
   1078             calStart.set(Calendar.SECOND, 0);
   1079             calStart.set(Calendar.MINUTE, 0);
   1080             long startRoundTime = calStart.getTimeInMillis();
   1081             if (startRoundTime < mStartWallTime) {
   1082                 calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1);
   1083                 startRoundTime = calStart.getTimeInMillis();
   1084             }
   1085             Calendar calEnd = Calendar.getInstance();
   1086             calEnd.setTimeInMillis(mEndWallTime);
   1087             calEnd.set(Calendar.MILLISECOND, 0);
   1088             calEnd.set(Calendar.SECOND, 0);
   1089             calEnd.set(Calendar.MINUTE, 0);
   1090             long endRoundTime = calEnd.getTimeInMillis();
   1091             if (startRoundTime < endRoundTime) {
   1092                 addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr);
   1093                 Calendar calMid = Calendar.getInstance();
   1094                 calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2));
   1095                 calMid.set(Calendar.MILLISECOND, 0);
   1096                 calMid.set(Calendar.SECOND, 0);
   1097                 calMid.set(Calendar.MINUTE, 0);
   1098                 long calMidMillis = calMid.getTimeInMillis();
   1099                 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
   1100                     addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr);
   1101                 }
   1102                 addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr);
   1103             }
   1104 
   1105             // Create the date labels if the chart includes multiple days
   1106             if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) ||
   1107                     calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) {
   1108                 boolean isDayFirst = isDayFirst();
   1109                 calStart.set(Calendar.HOUR_OF_DAY, 0);
   1110                 startRoundTime = calStart.getTimeInMillis();
   1111                 if (startRoundTime < mStartWallTime) {
   1112                     calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1);
   1113                     startRoundTime = calStart.getTimeInMillis();
   1114                 }
   1115                 calEnd.set(Calendar.HOUR_OF_DAY, 0);
   1116                 endRoundTime = calEnd.getTimeInMillis();
   1117                 if (startRoundTime < endRoundTime) {
   1118                     addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst);
   1119                     Calendar calMid = Calendar.getInstance();
   1120                     calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2));
   1121                     calMid.set(Calendar.HOUR_OF_DAY, 0);
   1122                     long calMidMillis = calMid.getTimeInMillis();
   1123                     if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) {
   1124                         addDateLabel(calMid, mLevelLeft, mLevelRight, isDayFirst);
   1125                     }
   1126                 }
   1127                 addDateLabel(calEnd, mLevelLeft, mLevelRight, isDayFirst);
   1128             }
   1129         }
   1130 
   1131         if (mTimeLabels.size() < 2) {
   1132             // If there are fewer than 2 time labels, then they are useless.  Just
   1133             // show an axis label giving the entire duration.
   1134             mDurationString = Formatter.formatShortElapsedTime(getContext(),
   1135                     mEndWallTime - mStartWallTime);
   1136             mDurationStringWidth = (int)mTextPaint.measureText(mDurationString);
   1137         } else {
   1138             mDurationString = null;
   1139             mDurationStringWidth = 0;
   1140         }
   1141     }
   1142 
   1143     void addTimeLabel(Calendar cal, int levelLeft, int levelRight, boolean is24hr) {
   1144         final long walltimeStart = mStartWallTime;
   1145         final long walltimeChange = mEndWallTime-walltimeStart;
   1146         mTimeLabels.add(new TimeLabel(mTextPaint,
   1147                 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
   1148                         / walltimeChange),
   1149                 cal, is24hr));
   1150     }
   1151 
   1152     void addDateLabel(Calendar cal, int levelLeft, int levelRight, boolean isDayFirst) {
   1153         final long walltimeStart = mStartWallTime;
   1154         final long walltimeChange = mEndWallTime-walltimeStart;
   1155         mDateLabels.add(new DateLabel(mTextPaint,
   1156                 levelLeft + (int)(((cal.getTimeInMillis()-walltimeStart)*(levelRight-levelLeft))
   1157                         / walltimeChange),
   1158                 cal, isDayFirst));
   1159     }
   1160 
   1161     @Override
   1162     protected void onDraw(Canvas canvas) {
   1163         super.onDraw(canvas);
   1164 
   1165         final int width = getWidth();
   1166         final int height = getHeight();
   1167 
   1168         //buildBitmap(width, height);
   1169 
   1170         if (DEBUG) Log.d(TAG, "onDraw: " + width + "x" + height);
   1171         //canvas.drawBitmap(mBitmap, 0, 0, null);
   1172         drawChart(canvas, width, height);
   1173     }
   1174 
   1175     void buildBitmap(int width, int height) {
   1176         if (mBitmap != null && width == mBitmap.getWidth() && height == mBitmap.getHeight()) {
   1177             return;
   1178         }
   1179 
   1180         if (DEBUG) Log.d(TAG, "buildBitmap: " + width + "x" + height);
   1181 
   1182         mBitmap = Bitmap.createBitmap(getResources().getDisplayMetrics(), width, height,
   1183                 Bitmap.Config.ARGB_8888);
   1184         mCanvas = new Canvas(mBitmap);
   1185         drawChart(mCanvas, width, height);
   1186     }
   1187 
   1188     void drawChart(Canvas canvas, int width, int height) {
   1189         final boolean layoutRtl = isLayoutRtl();
   1190         final int textStartX = layoutRtl ? width : 0;
   1191         final int textEndX = layoutRtl ? 0 : width;
   1192         final Paint.Align textAlignLeft = layoutRtl ? Paint.Align.RIGHT : Paint.Align.LEFT;
   1193         final Paint.Align textAlignRight = layoutRtl ? Paint.Align.LEFT : Paint.Align.RIGHT;
   1194 
   1195         if (DEBUG) {
   1196             canvas.drawRect(1, 1, width, height, mDebugRectPaint);
   1197         }
   1198 
   1199         if (DEBUG) Log.d(TAG, "Drawing level path.");
   1200         canvas.drawPath(mBatLevelPath, mBatteryBackgroundPaint);
   1201         if (!mTimeRemainPath.isEmpty()) {
   1202             if (DEBUG) Log.d(TAG, "Drawing time remain path.");
   1203             canvas.drawPath(mTimeRemainPath, mTimeRemainPaint);
   1204         }
   1205         if (mTimeLabels.size() > 1) {
   1206             int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
   1207             int ytick = mLevelBottom+mThinLineWidth+(mThinLineWidth/2);
   1208             mTextPaint.setTextAlign(Paint.Align.LEFT);
   1209             int lastX = 0;
   1210             for (int i=0; i<mTimeLabels.size(); i++) {
   1211                 TimeLabel label = mTimeLabels.get(i);
   1212                 if (i == 0) {
   1213                     int x = label.x - label.width/2;
   1214                     if (x < 0) {
   1215                         x = 0;
   1216                     }
   1217                     if (DEBUG) Log.d(TAG, "Drawing left label: " + label.label + " @ " + x);
   1218                     canvas.drawText(label.label, x, y, mTextPaint);
   1219                     canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
   1220                     lastX = x + label.width;
   1221                 } else if (i < (mTimeLabels.size()-1)) {
   1222                     int x = label.x - label.width/2;
   1223                     if (x < (lastX+mTextAscent)) {
   1224                         continue;
   1225                     }
   1226                     TimeLabel nextLabel = mTimeLabels.get(i+1);
   1227                     if (x > (width-nextLabel.width-mTextAscent)) {
   1228                         continue;
   1229                     }
   1230                     if (DEBUG) Log.d(TAG, "Drawing middle label: " + label.label + " @ " + x);
   1231                     canvas.drawText(label.label, x, y, mTextPaint);
   1232                     canvas.drawLine(label.x, ytick, label.x, ytick + mThinLineWidth, mTextPaint);
   1233                     lastX = x + label.width;
   1234                 } else {
   1235                     int x = label.x - label.width/2;
   1236                     if ((x+label.width) >= width) {
   1237                         x = width-1-label.width;
   1238                     }
   1239                     if (DEBUG) Log.d(TAG, "Drawing right label: " + label.label + " @ " + x);
   1240                     canvas.drawText(label.label, x, y, mTextPaint);
   1241                     canvas.drawLine(label.x, ytick, label.x, ytick+mThinLineWidth, mTextPaint);
   1242                 }
   1243             }
   1244         } else if (mDurationString != null) {
   1245             int y = mLevelBottom - mTextAscent + (mThinLineWidth*4);
   1246             mTextPaint.setTextAlign(Paint.Align.LEFT);
   1247             canvas.drawText(mDurationString,
   1248                     mLevelLeft + (mLevelRight-mLevelLeft)/2 - mDurationStringWidth/2,
   1249                     y, mTextPaint);
   1250         }
   1251 
   1252         int headerTop = -mHeaderTextAscent + (mHeaderTextDescent-mHeaderTextAscent)/3;
   1253         mHeaderTextPaint.setTextAlign(textAlignLeft);
   1254         if (DEBUG) Log.d(TAG, "Drawing charge label string: " + mChargeLabelString);
   1255         canvas.drawText(mChargeLabelString, textStartX, headerTop, mHeaderTextPaint);
   1256         int stringHalfWidth = mChargeDurationStringWidth / 2;
   1257         if (layoutRtl) stringHalfWidth = -stringHalfWidth;
   1258         int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2)
   1259                 + (layoutRtl ? mDrainStringWidth : mChargeLabelStringWidth);
   1260         if (DEBUG) Log.d(TAG, "Drawing charge duration string: " + mChargeDurationString);
   1261         canvas.drawText(mChargeDurationString, headerCenter - stringHalfWidth, headerTop,
   1262                 mHeaderTextPaint);
   1263         mHeaderTextPaint.setTextAlign(textAlignRight);
   1264         if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString);
   1265         canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint);
   1266 
   1267         if (!mBatGoodPath.isEmpty()) {
   1268             if (DEBUG) Log.d(TAG, "Drawing good battery path");
   1269             canvas.drawPath(mBatGoodPath, mBatteryGoodPaint);
   1270         }
   1271         if (!mBatWarnPath.isEmpty()) {
   1272             if (DEBUG) Log.d(TAG, "Drawing warn battery path");
   1273             canvas.drawPath(mBatWarnPath, mBatteryWarnPaint);
   1274         }
   1275         if (!mBatCriticalPath.isEmpty()) {
   1276             if (DEBUG) Log.d(TAG, "Drawing critical battery path");
   1277             canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint);
   1278         }
   1279         if (mHavePhoneSignal) {
   1280             if (DEBUG) Log.d(TAG, "Drawing phone signal path");
   1281             int top = height-mPhoneSignalOffset - (mLineWidth/2);
   1282             mPhoneSignalChart.draw(canvas, top, mLineWidth);
   1283         }
   1284         if (!mScreenOnPath.isEmpty()) {
   1285             if (DEBUG) Log.d(TAG, "Drawing screen on path");
   1286             canvas.drawPath(mScreenOnPath, mScreenOnPaint);
   1287         }
   1288         if (!mChargingPath.isEmpty()) {
   1289             if (DEBUG) Log.d(TAG, "Drawing charging path");
   1290             canvas.drawPath(mChargingPath, mChargingPaint);
   1291         }
   1292         if (mHaveGps) {
   1293             if (!mGpsOnPath.isEmpty()) {
   1294                 if (DEBUG) Log.d(TAG, "Drawing gps path");
   1295                 canvas.drawPath(mGpsOnPath, mGpsOnPaint);
   1296             }
   1297         }
   1298         if (mHaveFlashlight) {
   1299             if (!mFlashlightOnPath.isEmpty()) {
   1300                 if (DEBUG) Log.d(TAG, "Drawing flashlight path");
   1301                 canvas.drawPath(mFlashlightOnPath, mFlashlightOnPaint);
   1302             }
   1303         }
   1304         if (mHaveCamera) {
   1305             if (!mCameraOnPath.isEmpty()) {
   1306                 if (DEBUG) Log.d(TAG, "Drawing camera path");
   1307                 canvas.drawPath(mCameraOnPath, mCameraOnPaint);
   1308             }
   1309         }
   1310         if (mHaveWifi) {
   1311             if (!mWifiRunningPath.isEmpty()) {
   1312                 if (DEBUG) Log.d(TAG, "Drawing wifi path");
   1313                 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint);
   1314             }
   1315         }
   1316         if (!mCpuRunningPath.isEmpty()) {
   1317             if (DEBUG) Log.d(TAG, "Drawing running path");
   1318             canvas.drawPath(mCpuRunningPath, mCpuRunningPaint);
   1319         }
   1320 
   1321         if (mLargeMode) {
   1322             if (DEBUG) Log.d(TAG, "Drawing large mode labels");
   1323             Paint.Align align = mTextPaint.getTextAlign();
   1324             mTextPaint.setTextAlign(textAlignLeft);  // large-mode labels always aligned to start
   1325             if (mHavePhoneSignal) {
   1326                 canvas.drawText(mPhoneSignalLabel, textStartX,
   1327                         height - mPhoneSignalOffset - mTextDescent, mTextPaint);
   1328             }
   1329             if (mHaveGps) {
   1330                 canvas.drawText(mGpsOnLabel, textStartX,
   1331                         height - mGpsOnOffset - mTextDescent, mTextPaint);
   1332             }
   1333             if (mHaveFlashlight) {
   1334                 canvas.drawText(mFlashlightOnLabel, textStartX,
   1335                         height - mFlashlightOnOffset - mTextDescent, mTextPaint);
   1336             }
   1337             if (mHaveCamera) {
   1338                 canvas.drawText(mCameraOnLabel, textStartX,
   1339                         height - mCameraOnOffset - mTextDescent, mTextPaint);
   1340             }
   1341             if (mHaveWifi) {
   1342                 canvas.drawText(mWifiRunningLabel, textStartX,
   1343                         height - mWifiRunningOffset - mTextDescent, mTextPaint);
   1344             }
   1345             canvas.drawText(mCpuRunningLabel, textStartX,
   1346                     height - mCpuRunningOffset - mTextDescent, mTextPaint);
   1347             canvas.drawText(mChargingLabel, textStartX,
   1348                     height - mChargingOffset - mTextDescent, mTextPaint);
   1349             canvas.drawText(mScreenOnLabel, textStartX,
   1350                     height - mScreenOnOffset - mTextDescent, mTextPaint);
   1351             mTextPaint.setTextAlign(align);
   1352         }
   1353 
   1354         canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth,
   1355                 mLevelBottom+(mThinLineWidth/2), mTextPaint);
   1356         if (mLargeMode) {
   1357             for (int i=0; i<10; i++) {
   1358                 int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10;
   1359                 canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y,
   1360                         mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint);
   1361             }
   1362         }
   1363         if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth
   1364                 + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString));
   1365         canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint);
   1366         canvas.drawText(mMinPercentLabelString,
   1367                 mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth,
   1368                 mLevelBottom - mThinLineWidth, mTextPaint);
   1369         canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width,
   1370                 mLevelBottom+mThinLineWidth, mTextPaint);
   1371 
   1372         if (mDateLabels.size() > 0) {
   1373             int ytop = mLevelTop + mTextAscent;
   1374             int ybottom = mLevelBottom;
   1375             int lastLeft = mLevelRight;
   1376             mTextPaint.setTextAlign(Paint.Align.LEFT);
   1377             for (int i=mDateLabels.size()-1; i>=0; i--) {
   1378                 DateLabel label = mDateLabels.get(i);
   1379                 int left = label.x - mThinLineWidth;
   1380                 int x = label.x + mThinLineWidth*2;
   1381                 if ((x+label.width) >= lastLeft) {
   1382                     x = label.x - mThinLineWidth*2 - label.width;
   1383                     left = x - mThinLineWidth;
   1384                     if (left >= lastLeft) {
   1385                         // okay we give up.
   1386                         continue;
   1387                     }
   1388                 }
   1389                 if (left < mLevelLeft) {
   1390                     // Won't fit on left, give up.
   1391                     continue;
   1392                 }
   1393                 mDateLinePath.reset();
   1394                 mDateLinePath.moveTo(label.x, ytop);
   1395                 mDateLinePath.lineTo(label.x, ybottom);
   1396                 canvas.drawPath(mDateLinePath, mDateLinePaint);
   1397                 canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint);
   1398             }
   1399         }
   1400     }
   1401 }
   1402