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