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