• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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