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