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