1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.settings.fuelgauge; 16 17 import android.content.Context; 18 import android.content.Intent; 19 import android.content.IntentFilter; 20 import android.content.res.Resources; 21 import android.os.AsyncTask; 22 import android.os.BatteryManager; 23 import android.os.BatteryStats.HistoryItem; 24 import android.os.BatteryStatsManager; 25 import android.os.BatteryUsageStats; 26 import android.os.SystemClock; 27 import android.provider.Settings; 28 import android.text.format.Formatter; 29 import android.util.Log; 30 import android.util.SparseIntArray; 31 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 import androidx.annotation.WorkerThread; 35 36 import com.android.internal.os.BatteryStatsHistoryIterator; 37 import com.android.settings.Utils; 38 import com.android.settings.overlay.FeatureFactory; 39 import com.android.settings.widget.UsageView; 40 import com.android.settingslib.R; 41 import com.android.settingslib.fuelgauge.BatteryStatus; 42 import com.android.settingslib.fuelgauge.Estimate; 43 import com.android.settingslib.fuelgauge.EstimateKt; 44 import com.android.settingslib.utils.PowerUtil; 45 import com.android.settingslib.utils.StringUtil; 46 47 public class BatteryInfo { 48 private static final String TAG = "BatteryInfo"; 49 50 public CharSequence chargeLabel; 51 public CharSequence remainingLabel; 52 public int batteryLevel; 53 public int batteryStatus; 54 public int pluggedStatus; 55 public boolean discharging = true; 56 public boolean isBatteryDefender = false; 57 public boolean isLongLife = false; 58 public boolean isFastCharging; 59 public long remainingTimeUs = 0; 60 public long averageTimeToDischarge = EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN; 61 public String batteryPercentString; 62 public String statusLabel; 63 public String suggestionLabel; 64 private boolean mCharging; 65 private BatteryUsageStats mBatteryUsageStats; 66 private static final String LOG_TAG = "BatteryInfo"; 67 private long timePeriod; 68 69 public interface Callback { onBatteryInfoLoaded(BatteryInfo info)70 void onBatteryInfoLoaded(BatteryInfo info); 71 } 72 bindHistory(final UsageView view, BatteryDataParser... parsers)73 public void bindHistory(final UsageView view, BatteryDataParser... parsers) { 74 final Context context = view.getContext(); 75 BatteryDataParser parser = 76 new BatteryDataParser() { 77 SparseIntArray mPoints = new SparseIntArray(); 78 long mStartTime; 79 int mLastTime = -1; 80 byte mLastLevel; 81 82 @Override 83 public void onParsingStarted(long startTime, long endTime) { 84 this.mStartTime = startTime; 85 timePeriod = endTime - startTime; 86 view.clearPaths(); 87 // Initially configure the graph for history only. 88 view.configureGraph((int) timePeriod, 100); 89 } 90 91 @Override 92 public void onDataPoint(long time, HistoryItem record) { 93 mLastTime = (int) time; 94 mLastLevel = record.batteryLevel; 95 mPoints.put(mLastTime, mLastLevel); 96 } 97 98 @Override 99 public void onDataGap() { 100 if (mPoints.size() > 1) { 101 view.addPath(mPoints); 102 } 103 mPoints.clear(); 104 } 105 106 @Override 107 public void onParsingDone() { 108 onDataGap(); 109 110 // Add projection if we have an estimate. 111 if (remainingTimeUs != 0) { 112 PowerUsageFeatureProvider provider = 113 FeatureFactory.getFeatureFactory() 114 .getPowerUsageFeatureProvider(); 115 if (!mCharging 116 && provider.isEnhancedBatteryPredictionEnabled(context)) { 117 mPoints = 118 provider.getEnhancedBatteryPredictionCurve( 119 context, mStartTime); 120 } else { 121 // Linear extrapolation. 122 if (mLastTime >= 0) { 123 mPoints.put(mLastTime, mLastLevel); 124 mPoints.put( 125 (int) 126 (timePeriod 127 + PowerUtil.convertUsToMs( 128 remainingTimeUs)), 129 mCharging ? 100 : 0); 130 } 131 } 132 } 133 134 // If we have a projection, reconfigure the graph to show it. 135 if (mPoints != null && mPoints.size() > 0) { 136 int maxTime = mPoints.keyAt(mPoints.size() - 1); 137 view.configureGraph(maxTime, 100); 138 view.addProjectedPath(mPoints); 139 } 140 } 141 }; 142 BatteryDataParser[] parserList = new BatteryDataParser[parsers.length + 1]; 143 for (int i = 0; i < parsers.length; i++) { 144 parserList[i] = parsers[i]; 145 } 146 parserList[parsers.length] = parser; 147 parseBatteryHistory(parserList); 148 String timeString = 149 context.getString( 150 R.string.charge_length_format, 151 Formatter.formatShortElapsedTime(context, timePeriod)); 152 String remaining = ""; 153 if (remainingTimeUs != 0) { 154 remaining = 155 context.getString( 156 R.string.remaining_length_format, 157 Formatter.formatShortElapsedTime(context, remainingTimeUs / 1000)); 158 } 159 view.setBottomLabels(new CharSequence[] {timeString, remaining}); 160 } 161 162 /** Gets battery info */ getBatteryInfo( final Context context, final Callback callback, boolean shortString)163 public static void getBatteryInfo( 164 final Context context, final Callback callback, boolean shortString) { 165 BatteryInfo.getBatteryInfo(context, callback, /* batteryUsageStats */ null, shortString); 166 } 167 getSettingsChargeTimeRemaining(final Context context)168 static long getSettingsChargeTimeRemaining(final Context context) { 169 return Settings.Global.getLong( 170 context.getContentResolver(), 171 com.android.settingslib.fuelgauge.BatteryUtils.GLOBAL_TIME_TO_FULL_MILLIS, 172 -1); 173 } 174 175 /** Gets battery info */ getBatteryInfo( final Context context, final Callback callback, @Nullable final BatteryUsageStats batteryUsageStats, boolean shortString)176 public static void getBatteryInfo( 177 final Context context, 178 final Callback callback, 179 @Nullable final BatteryUsageStats batteryUsageStats, 180 boolean shortString) { 181 new AsyncTask<Void, Void, BatteryInfo>() { 182 @Override 183 protected BatteryInfo doInBackground(Void... params) { 184 boolean shouldCloseBatteryUsageStats = false; 185 BatteryUsageStats stats; 186 if (batteryUsageStats != null) { 187 stats = batteryUsageStats; 188 } else { 189 try { 190 stats = 191 context.getSystemService(BatteryStatsManager.class) 192 .getBatteryUsageStats(); 193 shouldCloseBatteryUsageStats = true; 194 } catch (RuntimeException e) { 195 Log.e(TAG, "getBatteryInfo() from getBatteryUsageStats()", e); 196 // Use default BatteryUsageStats. 197 stats = new BatteryUsageStats.Builder(new String[0]).build(); 198 } 199 } 200 final BatteryInfo batteryInfo = getBatteryInfo(context, stats, shortString); 201 if (shouldCloseBatteryUsageStats) { 202 try { 203 stats.close(); 204 } catch (Exception e) { 205 Log.e(TAG, "BatteryUsageStats.close() failed", e); 206 } 207 } 208 return batteryInfo; 209 } 210 211 @Override 212 protected void onPostExecute(BatteryInfo batteryInfo) { 213 final long startTime = System.currentTimeMillis(); 214 callback.onBatteryInfoLoaded(batteryInfo); 215 BatteryUtils.logRuntime(LOG_TAG, "time for callback", startTime); 216 } 217 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 218 } 219 220 /** Creates a BatteryInfo based on BatteryUsageStats */ 221 @WorkerThread getBatteryInfo( final Context context, @NonNull final BatteryUsageStats batteryUsageStats, boolean shortString)222 public static BatteryInfo getBatteryInfo( 223 final Context context, 224 @NonNull final BatteryUsageStats batteryUsageStats, 225 boolean shortString) { 226 final long batteryStatsTime = System.currentTimeMillis(); 227 BatteryUtils.logRuntime(LOG_TAG, "time for getStats", batteryStatsTime); 228 229 final long startTime = System.currentTimeMillis(); 230 PowerUsageFeatureProvider provider = 231 FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider(); 232 final long elapsedRealtimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime()); 233 234 final Intent batteryBroadcast = 235 context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); 236 // 0 means we are discharging, anything else means charging 237 final boolean discharging = 238 batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0; 239 240 if (discharging && provider.isEnhancedBatteryPredictionEnabled(context)) { 241 Estimate estimate = provider.getEnhancedBatteryPrediction(context); 242 if (estimate != null) { 243 Estimate.storeCachedEstimate(context, estimate); 244 BatteryUtils.logRuntime(LOG_TAG, "time for enhanced BatteryInfo", startTime); 245 return BatteryInfo.getBatteryInfo( 246 context, 247 batteryBroadcast, 248 batteryUsageStats, 249 estimate, 250 elapsedRealtimeUs, 251 shortString); 252 } 253 } 254 final long prediction = discharging ? batteryUsageStats.getBatteryTimeRemainingMs() : 0; 255 final Estimate estimate = 256 new Estimate( 257 prediction, 258 false, /* isBasedOnUsage */ 259 EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); 260 BatteryUtils.logRuntime(LOG_TAG, "time for regular BatteryInfo", startTime); 261 return BatteryInfo.getBatteryInfo( 262 context, 263 batteryBroadcast, 264 batteryUsageStats, 265 estimate, 266 elapsedRealtimeUs, 267 shortString); 268 } 269 270 @WorkerThread getBatteryInfoOld( Context context, Intent batteryBroadcast, BatteryUsageStats batteryUsageStats, long elapsedRealtimeUs, boolean shortString)271 public static BatteryInfo getBatteryInfoOld( 272 Context context, 273 Intent batteryBroadcast, 274 BatteryUsageStats batteryUsageStats, 275 long elapsedRealtimeUs, 276 boolean shortString) { 277 Estimate estimate = 278 new Estimate( 279 batteryUsageStats.getBatteryTimeRemainingMs(), 280 false, 281 EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN); 282 return getBatteryInfo( 283 context, 284 batteryBroadcast, 285 batteryUsageStats, 286 estimate, 287 elapsedRealtimeUs, 288 shortString); 289 } 290 291 @WorkerThread getBatteryInfo( Context context, Intent batteryBroadcast, @NonNull BatteryUsageStats batteryUsageStats, Estimate estimate, long elapsedRealtimeUs, boolean shortString, long currentTimeMs)292 public static BatteryInfo getBatteryInfo( 293 Context context, 294 Intent batteryBroadcast, 295 @NonNull BatteryUsageStats batteryUsageStats, 296 Estimate estimate, 297 long elapsedRealtimeUs, 298 boolean shortString, 299 long currentTimeMs) { 300 final boolean isCompactStatus = 301 context.getResources() 302 .getBoolean(com.android.settings.R.bool.config_use_compact_battery_status); 303 BatteryInfo info = new BatteryInfo(); 304 info.mBatteryUsageStats = batteryUsageStats; 305 info.batteryLevel = Utils.getBatteryLevel(batteryBroadcast); 306 info.batteryPercentString = Utils.formatPercentage(info.batteryLevel); 307 info.pluggedStatus = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0); 308 info.mCharging = info.pluggedStatus != 0; 309 info.averageTimeToDischarge = estimate.getAverageDischargeTime(); 310 311 final int chargingPolicy = 312 batteryBroadcast.getIntExtra( 313 BatteryManager.EXTRA_CHARGING_STATUS, 314 BatteryManager.CHARGING_POLICY_DEFAULT); 315 316 info.isLongLife = chargingPolicy == BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE; 317 info.statusLabel = Utils.getBatteryStatus(context, batteryBroadcast, isCompactStatus); 318 info.batteryStatus = 319 batteryBroadcast.getIntExtra( 320 BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); 321 info.isFastCharging = 322 BatteryStatus.getChargingSpeed(context, batteryBroadcast) 323 == BatteryStatus.CHARGING_FAST; 324 if (info.isLongLife) { 325 info.isBatteryDefender = 326 FeatureFactory.getFeatureFactory() 327 .getPowerUsageFeatureProvider() 328 .isBatteryDefend(info); 329 } 330 Log.d( 331 TAG, 332 "chargingPolicy = " 333 + chargingPolicy 334 + ", pluggedStatus = " 335 + info.pluggedStatus 336 + ", batteryStatus = " 337 + info.batteryStatus); 338 if (!isPluggedIn(context, info.mCharging, chargingPolicy)) { 339 updateBatteryInfoDischarging(context, shortString, estimate, info); 340 } else { 341 updateBatteryInfoCharging( 342 context, 343 batteryBroadcast, 344 batteryUsageStats, 345 info, 346 isCompactStatus, 347 currentTimeMs); 348 } 349 BatteryUtils.logRuntime(LOG_TAG, "time for getBatteryInfo", currentTimeMs); 350 return info; 351 } 352 353 /** Returns a {@code BatteryInfo} with battery and charging relative information. */ 354 @WorkerThread getBatteryInfo( Context context, Intent batteryBroadcast, BatteryUsageStats batteryUsageStats, Estimate estimate, long elapsedRealtimeUs, boolean shortString)355 public static BatteryInfo getBatteryInfo( 356 Context context, 357 Intent batteryBroadcast, 358 BatteryUsageStats batteryUsageStats, 359 Estimate estimate, 360 long elapsedRealtimeUs, 361 boolean shortString) { 362 long currentTimeMs = System.currentTimeMillis(); 363 return getBatteryInfo( 364 context, 365 batteryBroadcast, 366 batteryUsageStats, 367 estimate, 368 elapsedRealtimeUs, 369 shortString, 370 currentTimeMs); 371 } 372 updateBatteryInfoCharging( Context context, Intent batteryBroadcast, BatteryUsageStats stats, BatteryInfo info, boolean compactStatus, long currentTimeMs)373 private static void updateBatteryInfoCharging( 374 Context context, 375 Intent batteryBroadcast, 376 BatteryUsageStats stats, 377 BatteryInfo info, 378 boolean compactStatus, 379 long currentTimeMs) { 380 final Resources resources = context.getResources(); 381 final long chargeTimeMs = stats.getChargeTimeRemainingMs(); 382 if (getSettingsChargeTimeRemaining(context) != chargeTimeMs) { 383 Settings.Global.putLong( 384 context.getContentResolver(), 385 com.android.settingslib.fuelgauge.BatteryUtils.GLOBAL_TIME_TO_FULL_MILLIS, 386 chargeTimeMs); 387 } 388 389 final int status = 390 batteryBroadcast.getIntExtra( 391 BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); 392 info.discharging = false; 393 info.suggestionLabel = null; 394 int dockDefenderMode = BatteryUtils.getCurrentDockDefenderMode(context, info); 395 if ((info.isBatteryDefender 396 && status != BatteryManager.BATTERY_STATUS_FULL 397 && dockDefenderMode == BatteryUtils.DockDefenderMode.DISABLED) 398 || dockDefenderMode == BatteryUtils.DockDefenderMode.ACTIVE) { 399 // Battery defender active, battery charging paused 400 info.remainingLabel = null; 401 int chargingLimitedResId = R.string.power_charging_limited; 402 info.chargeLabel = context.getString(chargingLimitedResId, info.batteryPercentString); 403 return; 404 } 405 final BatterySettingsFeatureProvider featureProvider = 406 FeatureFactory.getFeatureFactory().getBatterySettingsFeatureProvider(); 407 if (featureProvider.isChargingOptimizationMode(context, info.isLongLife)) { 408 final CharSequence chargeLabel = 409 featureProvider.getChargingOptimizationChargeLabel( 410 context, 411 info.batteryLevel, 412 info.batteryPercentString, 413 chargeTimeMs, 414 currentTimeMs); 415 if (chargeLabel != null) { 416 final CharSequence remainingLabel = 417 featureProvider.getChargingOptimizationRemainingLabel( 418 context, 419 info.batteryLevel, 420 info.pluggedStatus, 421 chargeTimeMs, 422 currentTimeMs); 423 if (remainingLabel != null) { 424 info.chargeLabel = chargeLabel; 425 info.remainingLabel = remainingLabel; 426 return; 427 } 428 } 429 } 430 if ((chargeTimeMs > 0 431 && status != BatteryManager.BATTERY_STATUS_FULL 432 && dockDefenderMode == BatteryUtils.DockDefenderMode.DISABLED) 433 || dockDefenderMode == BatteryUtils.DockDefenderMode.TEMPORARILY_BYPASSED) { 434 // Battery is charging to full 435 info.remainingTimeUs = PowerUtil.convertMsToUs(chargeTimeMs); 436 int resId = getChargingDurationResId(info.isFastCharging); 437 info.remainingLabel = 438 chargeTimeMs <= 0 439 ? null 440 : getPowerRemainingChargingLabel( 441 context, 442 chargeTimeMs, 443 info.isFastCharging, 444 info.pluggedStatus, 445 currentTimeMs, 446 featureProvider); 447 448 info.chargeLabel = 449 chargeTimeMs <= 0 450 ? info.batteryPercentString 451 : getChargeLabelWithTimeToFull( 452 context, 453 resId, 454 info.batteryPercentString, 455 chargeTimeMs, 456 info.isFastCharging, 457 currentTimeMs); 458 } else if (dockDefenderMode == BatteryUtils.DockDefenderMode.FUTURE_BYPASS) { 459 // Dock defender will be triggered in the future, charging will be optimized. 460 info.chargeLabel = 461 context.getString( 462 R.string.power_charging_future_paused, info.batteryPercentString); 463 } else { 464 final String chargeStatusLabel = 465 Utils.getBatteryStatus(context, batteryBroadcast, compactStatus); 466 info.remainingLabel = null; 467 info.chargeLabel = 468 info.batteryLevel == 100 469 ? info.batteryPercentString 470 : resources.getString( 471 R.string.power_charging, 472 info.batteryPercentString, 473 chargeStatusLabel); 474 } 475 } 476 getPowerRemainingChargingLabel( Context context, long chargeRemainingTimeMs, boolean isFastCharging, int pluggedStatus, long currentTimeMs, BatterySettingsFeatureProvider featureProvider)477 private static CharSequence getPowerRemainingChargingLabel( 478 Context context, 479 long chargeRemainingTimeMs, 480 boolean isFastCharging, 481 int pluggedStatus, 482 long currentTimeMs, 483 BatterySettingsFeatureProvider featureProvider) { 484 if (pluggedStatus == BatteryManager.BATTERY_PLUGGED_WIRELESS) { 485 final CharSequence wirelessChargingRemainingLabel = 486 featureProvider.getWirelessChargingRemainingLabel( 487 context, chargeRemainingTimeMs, currentTimeMs); 488 if (wirelessChargingRemainingLabel != null) { 489 return wirelessChargingRemainingLabel; 490 } 491 } 492 if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { 493 int chargeLabelResId = 494 isFastCharging 495 ? R.string.power_remaining_fast_charging_duration_only_v2 496 : R.string.power_remaining_charging_duration_only_v2; 497 String timeString = 498 PowerUtil.getTargetTimeShortString( 499 context, chargeRemainingTimeMs, currentTimeMs); 500 return context.getString(chargeLabelResId, timeString); 501 } 502 final CharSequence timeString = 503 StringUtil.formatElapsedTime( 504 context, 505 chargeRemainingTimeMs, 506 /* withSeconds= */ false, 507 /* collapseTimeUnit= */ true); 508 return context.getString(R.string.power_remaining_charging_duration_only, timeString); 509 } 510 getChargeLabelWithTimeToFull( Context context, int chargeLabelResId, String batteryPercentString, long chargeTimeMs, boolean isFastCharging, long currentTimeMs)511 private static CharSequence getChargeLabelWithTimeToFull( 512 Context context, 513 int chargeLabelResId, 514 String batteryPercentString, 515 long chargeTimeMs, 516 boolean isFastCharging, 517 long currentTimeMs) { 518 if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { 519 var timeString = 520 PowerUtil.getTargetTimeShortString(context, chargeTimeMs, currentTimeMs); 521 522 return isFastCharging 523 ? context.getString( 524 chargeLabelResId, 525 batteryPercentString, 526 context.getString(R.string.battery_info_status_charging_fast_v2), 527 timeString) 528 : context.getString(chargeLabelResId, batteryPercentString, timeString); 529 } else { 530 var timeString = 531 StringUtil.formatElapsedTime( 532 context, 533 (double) chargeTimeMs, 534 /* withSeconds= */ false, 535 /* collapseTimeUnit= */ true); 536 return context.getString(chargeLabelResId, batteryPercentString, timeString); 537 } 538 } 539 getChargingDurationResId(boolean isFastCharging)540 private static int getChargingDurationResId(boolean isFastCharging) { 541 if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) { 542 return isFastCharging 543 ? R.string.power_fast_charging_duration_v2 544 : R.string.power_charging_duration_v2; 545 } 546 return R.string.power_charging_duration; 547 } 548 updateBatteryInfoDischarging( Context context, boolean shortString, Estimate estimate, BatteryInfo info)549 private static void updateBatteryInfoDischarging( 550 Context context, boolean shortString, Estimate estimate, BatteryInfo info) { 551 final long drainTimeUs = PowerUtil.convertMsToUs(estimate.getEstimateMillis()); 552 if (drainTimeUs > 0) { 553 info.remainingTimeUs = drainTimeUs; 554 info.remainingLabel = 555 PowerUtil.getBatteryRemainingShortStringFormatted( 556 context, PowerUtil.convertUsToMs(drainTimeUs)); 557 info.chargeLabel = info.remainingLabel; 558 info.suggestionLabel = 559 PowerUtil.getBatteryTipStringFormatted( 560 context, PowerUtil.convertUsToMs(drainTimeUs)); 561 } else { 562 info.remainingLabel = null; 563 info.suggestionLabel = null; 564 info.chargeLabel = info.batteryPercentString; 565 } 566 } 567 isPluggedIn(Context context, boolean isCharging, int chargingPolicy)568 private static boolean isPluggedIn(Context context, boolean isCharging, int chargingPolicy) { 569 return isCharging 570 || FeatureFactory.getFeatureFactory() 571 .getBatterySettingsFeatureProvider() 572 .isChargingOptimizationMode( 573 context, 574 chargingPolicy == BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE); 575 } 576 577 public interface BatteryDataParser { onParsingStarted(long startTime, long endTime)578 void onParsingStarted(long startTime, long endTime); 579 onDataPoint(long time, HistoryItem record)580 void onDataPoint(long time, HistoryItem record); 581 onDataGap()582 void onDataGap(); 583 onParsingDone()584 void onParsingDone(); 585 } 586 587 /** 588 * Iterates over battery history included in the BatteryUsageStats that this object was 589 * initialized with. 590 */ parseBatteryHistory(BatteryDataParser... parsers)591 public void parseBatteryHistory(BatteryDataParser... parsers) { 592 long startWalltime = 0; 593 long endWalltime = 0; 594 long historyStart = 0; 595 long historyEnd = 0; 596 long curWalltime = startWalltime; 597 long lastWallTime = 0; 598 long lastRealtime = 0; 599 int lastInteresting = 0; 600 int pos = 0; 601 boolean first = true; 602 final BatteryStatsHistoryIterator iterator1 = 603 mBatteryUsageStats.iterateBatteryStatsHistory(); 604 HistoryItem rec; 605 while ((rec = iterator1.next()) != null) { 606 pos++; 607 if (first) { 608 first = false; 609 historyStart = rec.time; 610 } 611 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME || rec.cmd == HistoryItem.CMD_RESET) { 612 // If there is a ridiculously large jump in time, then we won't be 613 // able to create a good chart with that data, so just ignore the 614 // times we got before and pretend like our data extends back from 615 // the time we have now. 616 // Also, if we are getting a time change and we are less than 5 minutes 617 // since the start of the history real time, then also use this new 618 // time to compute the base time, since whatever time we had before is 619 // pretty much just noise. 620 if (rec.currentTime > (lastWallTime + (180 * 24 * 60 * 60 * 1000L)) 621 || rec.time < (historyStart + (5 * 60 * 1000L))) { 622 startWalltime = 0; 623 } 624 lastWallTime = rec.currentTime; 625 lastRealtime = rec.time; 626 if (startWalltime == 0) { 627 startWalltime = lastWallTime - (lastRealtime - historyStart); 628 } 629 } 630 if (rec.isDeltaData()) { 631 lastInteresting = pos; 632 historyEnd = rec.time; 633 } 634 } 635 636 endWalltime = lastWallTime + historyEnd - lastRealtime; 637 638 int i = 0; 639 final int N = lastInteresting; 640 641 for (int j = 0; j < parsers.length; j++) { 642 parsers[j].onParsingStarted(startWalltime, endWalltime); 643 } 644 645 if (endWalltime > startWalltime) { 646 final BatteryStatsHistoryIterator iterator2 = 647 mBatteryUsageStats.iterateBatteryStatsHistory(); 648 while ((rec = iterator2.next()) != null && i < N) { 649 if (rec.isDeltaData()) { 650 curWalltime += rec.time - lastRealtime; 651 lastRealtime = rec.time; 652 long x = (curWalltime - startWalltime); 653 if (x < 0) { 654 x = 0; 655 } 656 for (int j = 0; j < parsers.length; j++) { 657 parsers[j].onDataPoint(x, rec); 658 } 659 } else { 660 long lastWalltime = curWalltime; 661 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME 662 || rec.cmd == HistoryItem.CMD_RESET) { 663 if (rec.currentTime >= startWalltime) { 664 curWalltime = rec.currentTime; 665 } else { 666 curWalltime = startWalltime + (rec.time - historyStart); 667 } 668 lastRealtime = rec.time; 669 } 670 671 if (rec.cmd != HistoryItem.CMD_OVERFLOW 672 && (rec.cmd != HistoryItem.CMD_CURRENT_TIME 673 || Math.abs(lastWalltime - curWalltime) > (60 * 60 * 1000))) { 674 for (int j = 0; j < parsers.length; j++) { 675 parsers[j].onDataGap(); 676 } 677 } 678 } 679 i++; 680 } 681 } 682 683 for (int j = 0; j < parsers.length; j++) { 684 parsers[j].onParsingDone(); 685 } 686 } 687 } 688