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