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 17package com.android.settings.fuelgauge; 18 19import android.content.Intent; 20import android.graphics.Bitmap; 21import android.graphics.DashPathEffect; 22import android.os.BatteryManager; 23import android.provider.Settings; 24import android.text.format.DateFormat; 25import android.text.format.Formatter; 26import android.util.Log; 27import android.util.TimeUtils; 28import com.android.settings.R; 29import com.android.settings.Utils; 30 31import android.content.Context; 32import android.content.res.ColorStateList; 33import android.content.res.TypedArray; 34import android.graphics.Canvas; 35import android.graphics.Paint; 36import android.graphics.Path; 37import android.graphics.Typeface; 38import android.os.BatteryStats; 39import android.os.SystemClock; 40import android.os.BatteryStats.HistoryItem; 41import android.telephony.ServiceState; 42import android.text.TextPaint; 43import android.util.AttributeSet; 44import android.util.TypedValue; 45import android.view.View; 46import libcore.icu.LocaleData; 47 48import java.util.ArrayList; 49import java.util.Calendar; 50import java.util.Locale; 51 52public 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 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 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 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 96 void finish(int width) { 97 if (mLastBin != 0) { 98 addTick(width, 0); 99 } 100 } 101 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 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 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 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 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 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 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 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 long remainingTimeUs = 0; 498 mDischarging = true; 499 if (mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) == 0) { 500 final long drainTime = mStats.computeBatteryTimeRemaining(elapsedRealtimeUs); 501 if (drainTime > 0) { 502 remainingTimeUs = drainTime; 503 String timeString = Formatter.formatShortElapsedTime(getContext(), 504 drainTime / 1000); 505 mChargeLabelString = getContext().getResources().getString( 506 R.string.power_discharging_duration, mBatteryLevel, timeString); 507 } else { 508 mChargeLabelString = Utils.formatPercentage(mBatteryLevel); 509 } 510 } else { 511 final long chargeTime = mStats.computeChargeTimeRemaining(elapsedRealtimeUs); 512 final String statusLabel = com.android.settings.Utils.getBatteryStatus(getResources(), 513 mBatteryBroadcast); 514 final int status = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS, 515 BatteryManager.BATTERY_STATUS_UNKNOWN); 516 if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) { 517 mDischarging = false; 518 remainingTimeUs = chargeTime; 519 String timeString = Formatter.formatShortElapsedTime(getContext(), 520 chargeTime / 1000); 521 int plugType = mBatteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); 522 int resId; 523 if (plugType == BatteryManager.BATTERY_PLUGGED_AC) { 524 resId = R.string.power_charging_duration_ac; 525 } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) { 526 resId = R.string.power_charging_duration_usb; 527 } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) { 528 resId = R.string.power_charging_duration_wireless; 529 } else { 530 resId = R.string.power_charging_duration; 531 } 532 mChargeLabelString = getContext().getResources().getString( 533 resId, mBatteryLevel, timeString); 534 } else { 535 mChargeLabelString = getContext().getResources().getString( 536 R.string.power_charging, mBatteryLevel, statusLabel); 537 } 538 } 539 mDrainString = ""; 540 mChargeDurationString = ""; 541 setContentDescription(mChargeLabelString); 542 543 int pos = 0; 544 int lastInteresting = 0; 545 byte lastLevel = -1; 546 mBatLow = 0; 547 mBatHigh = 100; 548 mStartWallTime = 0; 549 mEndDataWallTime = 0; 550 mEndWallTime = 0; 551 mHistStart = 0; 552 mHistEnd = 0; 553 long lastWallTime = 0; 554 long lastRealtime = 0; 555 int aggrStates = 0; 556 int aggrStates2 = 0; 557 boolean first = true; 558 if (stats.startIteratingHistoryLocked()) { 559 final HistoryItem rec = new HistoryItem(); 560 while (stats.getNextHistoryLocked(rec)) { 561 pos++; 562 if (first) { 563 first = false; 564 mHistStart = rec.time; 565 } 566 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME 567 || rec.cmd == HistoryItem.CMD_RESET) { 568 // If there is a ridiculously large jump in time, then we won't be 569 // able to create a good chart with that data, so just ignore the 570 // times we got before and pretend like our data extends back from 571 // the time we have now. 572 // Also, if we are getting a time change and we are less than 5 minutes 573 // since the start of the history real time, then also use this new 574 // time to compute the base time, since whatever time we had before is 575 // pretty much just noise. 576 if (rec.currentTime > (lastWallTime+(180*24*60*60*1000L)) 577 || rec.time < (mHistStart+(5*60*1000L))) { 578 mStartWallTime = 0; 579 } 580 lastWallTime = rec.currentTime; 581 lastRealtime = rec.time; 582 if (mStartWallTime == 0) { 583 mStartWallTime = lastWallTime - (lastRealtime-mHistStart); 584 } 585 } 586 if (rec.isDeltaData()) { 587 if (rec.batteryLevel != lastLevel || pos == 1) { 588 lastLevel = rec.batteryLevel; 589 } 590 lastInteresting = pos; 591 mHistDataEnd = rec.time; 592 aggrStates |= rec.states; 593 aggrStates2 |= rec.states2; 594 } 595 } 596 } 597 mHistEnd = mHistDataEnd + (remainingTimeUs/1000); 598 mEndDataWallTime = lastWallTime + mHistDataEnd - lastRealtime; 599 mEndWallTime = mEndDataWallTime + (remainingTimeUs/1000); 600 mNumHist = lastInteresting; 601 mHaveGps = (aggrStates&HistoryItem.STATE_GPS_ON_FLAG) != 0; 602 mHaveWifi = (aggrStates2&HistoryItem.STATE2_WIFI_RUNNING_FLAG) != 0 603 || (aggrStates&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG 604 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG 605 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0; 606 if (!com.android.settings.Utils.isWifiOnly(getContext())) { 607 mHavePhoneSignal = true; 608 } 609 if (mHistEnd <= mHistStart) mHistEnd = mHistStart+1; 610 } 611 612 @Override 613 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 614 mMaxPercentLabelStringWidth = (int)mTextPaint.measureText(mMaxPercentLabelString); 615 mMinPercentLabelStringWidth = (int)mTextPaint.measureText(mMinPercentLabelString); 616 mDrainStringWidth = (int)mHeaderTextPaint.measureText(mDrainString); 617 mChargeLabelStringWidth = (int)mHeaderTextPaint.measureText(mChargeLabelString); 618 mChargeDurationStringWidth = (int)mHeaderTextPaint.measureText(mChargeDurationString); 619 mTextAscent = (int)mTextPaint.ascent(); 620 mTextDescent = (int)mTextPaint.descent(); 621 mHeaderTextAscent = (int)mHeaderTextPaint.ascent(); 622 mHeaderTextDescent = (int)mHeaderTextPaint.descent(); 623 int headerTextHeight = mHeaderTextDescent - mHeaderTextAscent; 624 mHeaderHeight = headerTextHeight*2 - mTextAscent; 625 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 626 getDefaultSize(mChartMinHeight+mHeaderHeight, heightMeasureSpec)); 627 } 628 629 void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath, 630 int lastX, boolean lastCharging, boolean lastScreenOn, boolean lastGpsOn, 631 boolean lastWifiRunning, boolean lastCpuRunning, Path lastPath) { 632 if (curLevelPath != null) { 633 if (lastX >= 0 && lastX < w) { 634 if (lastPath != null) { 635 lastPath.lineTo(w, y); 636 } 637 curLevelPath.lineTo(w, y); 638 } 639 curLevelPath.lineTo(w, mLevelTop+levelh); 640 curLevelPath.lineTo(startX, mLevelTop+levelh); 641 curLevelPath.close(); 642 } 643 644 if (lastCharging) { 645 mChargingPath.lineTo(w, h-mChargingOffset); 646 } 647 if (lastScreenOn) { 648 mScreenOnPath.lineTo(w, h-mScreenOnOffset); 649 } 650 if (lastGpsOn) { 651 mGpsOnPath.lineTo(w, h-mGpsOnOffset); 652 } 653 if (lastWifiRunning) { 654 mWifiRunningPath.lineTo(w, h-mWifiRunningOffset); 655 } 656 if (lastCpuRunning) { 657 mCpuRunningPath.lineTo(w, h - mCpuRunningOffset); 658 } 659 if (mHavePhoneSignal) { 660 mPhoneSignalChart.finish(w); 661 } 662 } 663 664 private boolean is24Hour() { 665 return DateFormat.is24HourFormat(getContext()); 666 } 667 668 private boolean isDayFirst() { 669 String value = Settings.System.getString(mContext.getContentResolver(), 670 Settings.System.DATE_FORMAT); 671 if (value == null) { 672 LocaleData d = LocaleData.get(mContext.getResources().getConfiguration().locale); 673 value = d.shortDateFormat4; 674 } 675 return value.indexOf('M') > value.indexOf('d'); 676 } 677 678 /* 679 private void buildTime() { 680 java.text.DateFormat shortDateFormat = DateFormat.getDateFormat(context); 681 final Calendar now = Calendar.getInstance(); 682 mDummyDate.setTimeZone(now.getTimeZone()); 683 // We use December 31st because it's unambiguous when demonstrating the date format. 684 // We use 13:00 so we can demonstrate the 12/24 hour options. 685 mDummyDate.set(now.get(Calendar.YEAR), 11, 31, 13, 0, 0); 686 Date dummyDate = mDummyDate.getTime(); 687 mTimePref.setSummary(DateFormat.getTimeFormat(getActivity()).format(now.getTime())); 688 mTimeZone.setSummary(getTimeZoneText(now.getTimeZone(), true)); 689 mDatePref.setSummary(shortDateFormat.format(now.getTime())); 690 mDateFormat.setSummary(shortDateFormat.format(dummyDate)); 691 mTime24Pref.setSummary(DateFormat.getTimeFormat(getActivity()).format(dummyDate)); 692 693 } 694 */ 695 696 @Override 697 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 698 super.onSizeChanged(w, h, oldw, oldh); 699 700 if (DEBUG) Log.d(TAG, "onSizeChanged: " + oldw + "x" + oldh + " to " + w + "x" + h); 701 702 if (mLastWidth == w && mLastHeight == h) { 703 return; 704 } 705 706 if (mLastWidth == 0 || mLastHeight == 0) { 707 return; 708 } 709 710 if (DEBUG) Log.d(TAG, "Rebuilding chart for: " + w + "x" + h); 711 712 mLastWidth = w; 713 mLastHeight = h; 714 mBitmap = null; 715 mCanvas = null; 716 717 int textHeight = mTextDescent - mTextAscent; 718 if (h > ((textHeight*10)+mChartMinHeight)) { 719 mLargeMode = true; 720 if (h > (textHeight*15)) { 721 // Plenty of room for the chart. 722 mLineWidth = textHeight/2; 723 } else { 724 // Compress lines to make more room for chart. 725 mLineWidth = textHeight/3; 726 } 727 } else { 728 mLargeMode = false; 729 mLineWidth = mThinLineWidth; 730 } 731 if (mLineWidth <= 0) mLineWidth = 1; 732 733 mLevelTop = mHeaderHeight; 734 mLevelLeft = mMaxPercentLabelStringWidth + mThinLineWidth*3; 735 mLevelRight = w; 736 int levelWidth = mLevelRight-mLevelLeft; 737 738 mTextPaint.setStrokeWidth(mThinLineWidth); 739 mBatteryGoodPaint.setStrokeWidth(mThinLineWidth); 740 mBatteryWarnPaint.setStrokeWidth(mThinLineWidth); 741 mBatteryCriticalPaint.setStrokeWidth(mThinLineWidth); 742 mChargingPaint.setStrokeWidth(mLineWidth); 743 mScreenOnPaint.setStrokeWidth(mLineWidth); 744 mGpsOnPaint.setStrokeWidth(mLineWidth); 745 mWifiRunningPaint.setStrokeWidth(mLineWidth); 746 mCpuRunningPaint.setStrokeWidth(mLineWidth); 747 mDebugRectPaint.setStrokeWidth(1); 748 749 int fullBarOffset = textHeight + mLineWidth; 750 751 if (mLargeMode) { 752 mChargingOffset = mLineWidth; 753 mScreenOnOffset = mChargingOffset + fullBarOffset; 754 mCpuRunningOffset = mScreenOnOffset + fullBarOffset; 755 mWifiRunningOffset = mCpuRunningOffset + fullBarOffset; 756 mGpsOnOffset = mWifiRunningOffset + (mHaveWifi ? fullBarOffset : 0); 757 mPhoneSignalOffset = mGpsOnOffset + (mHaveGps ? fullBarOffset : 0); 758 mLevelOffset = mPhoneSignalOffset + (mHavePhoneSignal ? fullBarOffset : 0) 759 + mLineWidth*2 + mLineWidth/2; 760 if (mHavePhoneSignal) { 761 mPhoneSignalChart.init(w); 762 } 763 } else { 764 mScreenOnOffset = mGpsOnOffset = mWifiRunningOffset 765 = mCpuRunningOffset = mChargingOffset = mPhoneSignalOffset = 0; 766 mLevelOffset = fullBarOffset + mThinLineWidth*4; 767 if (mHavePhoneSignal) { 768 mPhoneSignalChart.init(0); 769 } 770 } 771 772 mBatLevelPath.reset(); 773 mBatGoodPath.reset(); 774 mBatWarnPath.reset(); 775 mTimeRemainPath.reset(); 776 mBatCriticalPath.reset(); 777 mScreenOnPath.reset(); 778 mGpsOnPath.reset(); 779 mWifiRunningPath.reset(); 780 mCpuRunningPath.reset(); 781 mChargingPath.reset(); 782 783 mTimeLabels.clear(); 784 mDateLabels.clear(); 785 786 final long walltimeStart = mStartWallTime; 787 final long walltimeChange = mEndWallTime > walltimeStart 788 ? (mEndWallTime-walltimeStart) : 1; 789 long curWalltime = mStartWallTime; 790 long lastRealtime = 0; 791 792 final int batLow = mBatLow; 793 final int batChange = mBatHigh-mBatLow; 794 795 final int levelh = h - mLevelOffset - mLevelTop; 796 mLevelBottom = mLevelTop + levelh; 797 798 int x = mLevelLeft, y = 0, startX = mLevelLeft, lastX = -1, lastY = -1; 799 int i = 0; 800 Path curLevelPath = null; 801 Path lastLinePath = null; 802 boolean lastCharging = false, lastScreenOn = false, lastGpsOn = false; 803 boolean lastWifiRunning = false, lastWifiSupplRunning = false, lastCpuRunning = false; 804 int lastWifiSupplState = BatteryStats.WIFI_SUPPL_STATE_INVALID; 805 final int N = mNumHist; 806 if (mEndDataWallTime > mStartWallTime && mStats.startIteratingHistoryLocked()) { 807 final HistoryItem rec = new HistoryItem(); 808 while (mStats.getNextHistoryLocked(rec) && i < N) { 809 if (rec.isDeltaData()) { 810 curWalltime += rec.time-lastRealtime; 811 lastRealtime = rec.time; 812 x = mLevelLeft + (int)(((curWalltime-walltimeStart)*levelWidth)/walltimeChange); 813 if (x < 0) { 814 x = 0; 815 } 816 if (false) { 817 StringBuilder sb = new StringBuilder(128); 818 sb.append("walloff="); 819 TimeUtils.formatDuration(curWalltime - walltimeStart, sb); 820 sb.append(" wallchange="); 821 TimeUtils.formatDuration(walltimeChange, sb); 822 sb.append(" x="); 823 sb.append(x); 824 Log.d("foo", sb.toString()); 825 } 826 y = mLevelTop + levelh - ((rec.batteryLevel-batLow)*(levelh-1))/batChange; 827 828 if (lastX != x) { 829 // We have moved by at least a pixel. 830 if (lastY != y) { 831 // Don't plot changes within a pixel. 832 Path path; 833 byte value = rec.batteryLevel; 834 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath; 835 else if (value <= mBatteryWarnLevel) path = mBatWarnPath; 836 else path = null; //mBatGoodPath; 837 838 if (path != lastLinePath) { 839 if (lastLinePath != null) { 840 lastLinePath.lineTo(x, y); 841 } 842 if (path != null) { 843 path.moveTo(x, y); 844 } 845 lastLinePath = path; 846 } else if (path != null) { 847 path.lineTo(x, y); 848 } 849 850 if (curLevelPath == null) { 851 curLevelPath = mBatLevelPath; 852 curLevelPath.moveTo(x, y); 853 startX = x; 854 } else { 855 curLevelPath.lineTo(x, y); 856 } 857 lastX = x; 858 lastY = y; 859 } 860 } 861 862 if (mLargeMode) { 863 final boolean charging = 864 (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0; 865 if (charging != lastCharging) { 866 if (charging) { 867 mChargingPath.moveTo(x, h-mChargingOffset); 868 } else { 869 mChargingPath.lineTo(x, h-mChargingOffset); 870 } 871 lastCharging = charging; 872 } 873 874 final boolean screenOn = 875 (rec.states&HistoryItem.STATE_SCREEN_ON_FLAG) != 0; 876 if (screenOn != lastScreenOn) { 877 if (screenOn) { 878 mScreenOnPath.moveTo(x, h-mScreenOnOffset); 879 } else { 880 mScreenOnPath.lineTo(x, h-mScreenOnOffset); 881 } 882 lastScreenOn = screenOn; 883 } 884 885 final boolean gpsOn = 886 (rec.states&HistoryItem.STATE_GPS_ON_FLAG) != 0; 887 if (gpsOn != lastGpsOn) { 888 if (gpsOn) { 889 mGpsOnPath.moveTo(x, h-mGpsOnOffset); 890 } else { 891 mGpsOnPath.lineTo(x, h-mGpsOnOffset); 892 } 893 lastGpsOn = gpsOn; 894 } 895 896 final int wifiSupplState = 897 ((rec.states2&HistoryItem.STATE2_WIFI_SUPPL_STATE_MASK) 898 >> HistoryItem.STATE2_WIFI_SUPPL_STATE_SHIFT); 899 boolean wifiRunning; 900 if (lastWifiSupplState != wifiSupplState) { 901 lastWifiSupplState = wifiSupplState; 902 switch (wifiSupplState) { 903 case BatteryStats.WIFI_SUPPL_STATE_DISCONNECTED: 904 case BatteryStats.WIFI_SUPPL_STATE_DORMANT: 905 case BatteryStats.WIFI_SUPPL_STATE_INACTIVE: 906 case BatteryStats.WIFI_SUPPL_STATE_INTERFACE_DISABLED: 907 case BatteryStats.WIFI_SUPPL_STATE_INVALID: 908 case BatteryStats.WIFI_SUPPL_STATE_UNINITIALIZED: 909 wifiRunning = lastWifiSupplRunning = false; 910 break; 911 default: 912 wifiRunning = lastWifiSupplRunning = true; 913 break; 914 } 915 } else { 916 wifiRunning = lastWifiSupplRunning; 917 } 918 if ((rec.states&(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG 919 |HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG 920 |HistoryItem.STATE_WIFI_SCAN_FLAG)) != 0) { 921 wifiRunning = true; 922 } 923 if (wifiRunning != lastWifiRunning) { 924 if (wifiRunning) { 925 mWifiRunningPath.moveTo(x, h-mWifiRunningOffset); 926 } else { 927 mWifiRunningPath.lineTo(x, h-mWifiRunningOffset); 928 } 929 lastWifiRunning = wifiRunning; 930 } 931 932 final boolean cpuRunning = 933 (rec.states&HistoryItem.STATE_CPU_RUNNING_FLAG) != 0; 934 if (cpuRunning != lastCpuRunning) { 935 if (cpuRunning) { 936 mCpuRunningPath.moveTo(x, h - mCpuRunningOffset); 937 } else { 938 mCpuRunningPath.lineTo(x, h - mCpuRunningOffset); 939 } 940 lastCpuRunning = cpuRunning; 941 } 942 943 if (mLargeMode && mHavePhoneSignal) { 944 int bin; 945 if (((rec.states&HistoryItem.STATE_PHONE_STATE_MASK) 946 >> HistoryItem.STATE_PHONE_STATE_SHIFT) 947 == ServiceState.STATE_POWER_OFF) { 948 bin = 0; 949 } else if ((rec.states&HistoryItem.STATE_PHONE_SCANNING_FLAG) != 0) { 950 bin = 1; 951 } else { 952 bin = (rec.states&HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_MASK) 953 >> HistoryItem.STATE_PHONE_SIGNAL_STRENGTH_SHIFT; 954 bin += 2; 955 } 956 mPhoneSignalChart.addTick(x, bin); 957 } 958 } 959 960 } else { 961 long lastWalltime = curWalltime; 962 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME 963 || rec.cmd == HistoryItem.CMD_RESET) { 964 if (rec.currentTime >= mStartWallTime) { 965 curWalltime = rec.currentTime; 966 } else { 967 curWalltime = mStartWallTime + (rec.time-mHistStart); 968 } 969 lastRealtime = rec.time; 970 } 971 972 if (rec.cmd != HistoryItem.CMD_OVERFLOW 973 && (rec.cmd != HistoryItem.CMD_CURRENT_TIME 974 || Math.abs(lastWalltime-curWalltime) > (60*60*1000))) { 975 if (curLevelPath != null) { 976 finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX, 977 lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning, 978 lastCpuRunning, lastLinePath); 979 lastX = lastY = -1; 980 curLevelPath = null; 981 lastLinePath = null; 982 lastCharging = lastScreenOn = lastGpsOn = lastCpuRunning = false; 983 } 984 } 985 } 986 987 i++; 988 } 989 mStats.finishIteratingHistoryLocked(); 990 } 991 992 if (lastY < 0 || lastX < 0) { 993 // Didn't get any data... 994 x = lastX = mLevelLeft; 995 y = lastY = mLevelTop + levelh - ((mBatteryLevel-batLow)*(levelh-1))/batChange; 996 Path path; 997 byte value = (byte)mBatteryLevel; 998 if (value <= mBatteryCriticalLevel) path = mBatCriticalPath; 999 else if (value <= mBatteryWarnLevel) path = mBatWarnPath; 1000 else path = null; //mBatGoodPath; 1001 if (path != null) { 1002 path.moveTo(x, y); 1003 lastLinePath = path; 1004 } 1005 mBatLevelPath.moveTo(x, y); 1006 curLevelPath = mBatLevelPath; 1007 x = w; 1008 } else { 1009 // Figure out where the actual data ends on the screen. 1010 x = mLevelLeft + (int)(((mEndDataWallTime-walltimeStart)*levelWidth)/walltimeChange); 1011 if (x < 0) { 1012 x = 0; 1013 } 1014 } 1015 1016 finishPaths(x, h, levelh, startX, lastY, curLevelPath, lastX, 1017 lastCharging, lastScreenOn, lastGpsOn, lastWifiRunning, 1018 lastCpuRunning, lastLinePath); 1019 1020 if (x < w) { 1021 // If we reserved room for the remaining time, create a final path to draw 1022 // that part of the UI. 1023 mTimeRemainPath.moveTo(x, lastY); 1024 int fullY = mLevelTop + levelh - ((100-batLow)*(levelh-1))/batChange; 1025 int emptyY = mLevelTop + levelh - ((0-batLow)*(levelh-1))/batChange; 1026 if (mDischarging) { 1027 mTimeRemainPath.lineTo(mLevelRight, emptyY); 1028 } else { 1029 mTimeRemainPath.lineTo(mLevelRight, fullY); 1030 mTimeRemainPath.lineTo(mLevelRight, emptyY); 1031 } 1032 mTimeRemainPath.lineTo(x, emptyY); 1033 mTimeRemainPath.close(); 1034 } 1035 1036 if (mStartWallTime > 0 && mEndWallTime > mStartWallTime) { 1037 // Create the time labels at the bottom. 1038 boolean is24hr = is24Hour(); 1039 Calendar calStart = Calendar.getInstance(); 1040 calStart.setTimeInMillis(mStartWallTime); 1041 calStart.set(Calendar.MILLISECOND, 0); 1042 calStart.set(Calendar.SECOND, 0); 1043 calStart.set(Calendar.MINUTE, 0); 1044 long startRoundTime = calStart.getTimeInMillis(); 1045 if (startRoundTime < mStartWallTime) { 1046 calStart.set(Calendar.HOUR_OF_DAY, calStart.get(Calendar.HOUR_OF_DAY)+1); 1047 startRoundTime = calStart.getTimeInMillis(); 1048 } 1049 Calendar calEnd = Calendar.getInstance(); 1050 calEnd.setTimeInMillis(mEndWallTime); 1051 calEnd.set(Calendar.MILLISECOND, 0); 1052 calEnd.set(Calendar.SECOND, 0); 1053 calEnd.set(Calendar.MINUTE, 0); 1054 long endRoundTime = calEnd.getTimeInMillis(); 1055 if (startRoundTime < endRoundTime) { 1056 addTimeLabel(calStart, mLevelLeft, mLevelRight, is24hr); 1057 Calendar calMid = Calendar.getInstance(); 1058 calMid.setTimeInMillis(mStartWallTime+((mEndWallTime-mStartWallTime)/2)); 1059 calMid.set(Calendar.MILLISECOND, 0); 1060 calMid.set(Calendar.SECOND, 0); 1061 calMid.set(Calendar.MINUTE, 0); 1062 long calMidMillis = calMid.getTimeInMillis(); 1063 if (calMidMillis > startRoundTime && calMidMillis < endRoundTime) { 1064 addTimeLabel(calMid, mLevelLeft, mLevelRight, is24hr); 1065 } 1066 addTimeLabel(calEnd, mLevelLeft, mLevelRight, is24hr); 1067 } 1068 1069 // Create the date labels if the chart includes multiple days 1070 if (calStart.get(Calendar.DAY_OF_YEAR) != calEnd.get(Calendar.DAY_OF_YEAR) || 1071 calStart.get(Calendar.YEAR) != calEnd.get(Calendar.YEAR)) { 1072 boolean isDayFirst = isDayFirst(); 1073 calStart.set(Calendar.HOUR_OF_DAY, 0); 1074 startRoundTime = calStart.getTimeInMillis(); 1075 if (startRoundTime < mStartWallTime) { 1076 calStart.set(Calendar.DAY_OF_YEAR, calStart.get(Calendar.DAY_OF_YEAR) + 1); 1077 startRoundTime = calStart.getTimeInMillis(); 1078 } 1079 calEnd.set(Calendar.HOUR_OF_DAY, 0); 1080 endRoundTime = calEnd.getTimeInMillis(); 1081 if (startRoundTime < endRoundTime) { 1082 addDateLabel(calStart, mLevelLeft, mLevelRight, isDayFirst); 1083 Calendar calMid = Calendar.getInstance(); 1084 calMid.setTimeInMillis(startRoundTime + ((endRoundTime - startRoundTime) / 2)); 1085 calMid.set(Calendar.HOUR_OF_DAY, 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: " + mChargeLabelString); 1219 canvas.drawText(mChargeLabelString, textStartX, headerTop, mHeaderTextPaint); 1220 int stringHalfWidth = mChargeDurationStringWidth / 2; 1221 if (layoutRtl) stringHalfWidth = -stringHalfWidth; 1222 int headerCenter = ((width-mChargeDurationStringWidth-mDrainStringWidth)/2) 1223 + (layoutRtl ? mDrainStringWidth : mChargeLabelStringWidth); 1224 if (DEBUG) Log.d(TAG, "Drawing charge duration string: " + mChargeDurationString); 1225 canvas.drawText(mChargeDurationString, headerCenter - stringHalfWidth, headerTop, 1226 mHeaderTextPaint); 1227 mHeaderTextPaint.setTextAlign(textAlignRight); 1228 if (DEBUG) Log.d(TAG, "Drawing drain string: " + mDrainString); 1229 canvas.drawText(mDrainString, textEndX, headerTop, mHeaderTextPaint); 1230 1231 if (!mBatGoodPath.isEmpty()) { 1232 if (DEBUG) Log.d(TAG, "Drawing good battery path"); 1233 canvas.drawPath(mBatGoodPath, mBatteryGoodPaint); 1234 } 1235 if (!mBatWarnPath.isEmpty()) { 1236 if (DEBUG) Log.d(TAG, "Drawing warn battery path"); 1237 canvas.drawPath(mBatWarnPath, mBatteryWarnPaint); 1238 } 1239 if (!mBatCriticalPath.isEmpty()) { 1240 if (DEBUG) Log.d(TAG, "Drawing critical battery path"); 1241 canvas.drawPath(mBatCriticalPath, mBatteryCriticalPaint); 1242 } 1243 if (mHavePhoneSignal) { 1244 if (DEBUG) Log.d(TAG, "Drawing phone signal path"); 1245 int top = height-mPhoneSignalOffset - (mLineWidth/2); 1246 mPhoneSignalChart.draw(canvas, top, mLineWidth); 1247 } 1248 if (!mScreenOnPath.isEmpty()) { 1249 if (DEBUG) Log.d(TAG, "Drawing screen on path"); 1250 canvas.drawPath(mScreenOnPath, mScreenOnPaint); 1251 } 1252 if (!mChargingPath.isEmpty()) { 1253 if (DEBUG) Log.d(TAG, "Drawing charging path"); 1254 canvas.drawPath(mChargingPath, mChargingPaint); 1255 } 1256 if (mHaveGps) { 1257 if (!mGpsOnPath.isEmpty()) { 1258 if (DEBUG) Log.d(TAG, "Drawing gps path"); 1259 canvas.drawPath(mGpsOnPath, mGpsOnPaint); 1260 } 1261 } 1262 if (mHaveWifi) { 1263 if (!mWifiRunningPath.isEmpty()) { 1264 if (DEBUG) Log.d(TAG, "Drawing wifi path"); 1265 canvas.drawPath(mWifiRunningPath, mWifiRunningPaint); 1266 } 1267 } 1268 if (!mCpuRunningPath.isEmpty()) { 1269 if (DEBUG) Log.d(TAG, "Drawing running path"); 1270 canvas.drawPath(mCpuRunningPath, mCpuRunningPaint); 1271 } 1272 1273 if (mLargeMode) { 1274 if (DEBUG) Log.d(TAG, "Drawing large mode labels"); 1275 Paint.Align align = mTextPaint.getTextAlign(); 1276 mTextPaint.setTextAlign(textAlignLeft); // large-mode labels always aligned to start 1277 if (mHavePhoneSignal) { 1278 canvas.drawText(mPhoneSignalLabel, textStartX, 1279 height - mPhoneSignalOffset - mTextDescent, mTextPaint); 1280 } 1281 if (mHaveGps) { 1282 canvas.drawText(mGpsOnLabel, textStartX, 1283 height - mGpsOnOffset - mTextDescent, mTextPaint); 1284 } 1285 if (mHaveWifi) { 1286 canvas.drawText(mWifiRunningLabel, textStartX, 1287 height - mWifiRunningOffset - mTextDescent, mTextPaint); 1288 } 1289 canvas.drawText(mCpuRunningLabel, textStartX, 1290 height - mCpuRunningOffset - mTextDescent, mTextPaint); 1291 canvas.drawText(mChargingLabel, textStartX, 1292 height - mChargingOffset - mTextDescent, mTextPaint); 1293 canvas.drawText(mScreenOnLabel, textStartX, 1294 height - mScreenOnOffset - mTextDescent, mTextPaint); 1295 mTextPaint.setTextAlign(align); 1296 } 1297 1298 canvas.drawLine(mLevelLeft-mThinLineWidth, mLevelTop, mLevelLeft-mThinLineWidth, 1299 mLevelBottom+(mThinLineWidth/2), mTextPaint); 1300 if (mLargeMode) { 1301 for (int i=0; i<10; i++) { 1302 int y = mLevelTop + mThinLineWidth/2 + ((mLevelBottom-mLevelTop)*i)/10; 1303 canvas.drawLine(mLevelLeft-mThinLineWidth*2-mThinLineWidth/2, y, 1304 mLevelLeft-mThinLineWidth-mThinLineWidth/2, y, mTextPaint); 1305 } 1306 } 1307 if (DEBUG) Log.d(TAG, "Drawing max percent, origw=" + mMaxPercentLabelStringWidth 1308 + ", noww=" + (int)mTextPaint.measureText(mMaxPercentLabelString)); 1309 canvas.drawText(mMaxPercentLabelString, 0, mLevelTop, mTextPaint); 1310 canvas.drawText(mMinPercentLabelString, 1311 mMaxPercentLabelStringWidth-mMinPercentLabelStringWidth, 1312 mLevelBottom - mThinLineWidth, mTextPaint); 1313 canvas.drawLine(mLevelLeft/2, mLevelBottom+mThinLineWidth, width, 1314 mLevelBottom+mThinLineWidth, mTextPaint); 1315 1316 if (mDateLabels.size() > 0) { 1317 int ytop = mLevelTop + mTextAscent; 1318 int ybottom = mLevelBottom; 1319 int lastLeft = mLevelRight; 1320 mTextPaint.setTextAlign(Paint.Align.LEFT); 1321 for (int i=mDateLabels.size()-1; i>=0; i--) { 1322 DateLabel label = mDateLabels.get(i); 1323 int left = label.x - mThinLineWidth; 1324 int x = label.x + mThinLineWidth*2; 1325 if ((x+label.width) >= lastLeft) { 1326 x = label.x - mThinLineWidth*2 - label.width; 1327 left = x - mThinLineWidth; 1328 if (left >= lastLeft) { 1329 // okay we give up. 1330 continue; 1331 } 1332 } 1333 if (left < mLevelLeft) { 1334 // Won't fit on left, give up. 1335 continue; 1336 } 1337 mDateLinePath.reset(); 1338 mDateLinePath.moveTo(label.x, ytop); 1339 mDateLinePath.lineTo(label.x, ybottom); 1340 canvas.drawPath(mDateLinePath, mDateLinePaint); 1341 canvas.drawText(label.label, x, ytop - mTextAscent, mTextPaint); 1342 } 1343 } 1344 } 1345} 1346