Home | History | Annotate | Download | only in settingslib
      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.settingslib;
     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;
     24 import android.os.BatteryStats.HistoryItem;
     25 import android.os.Bundle;
     26 import android.os.SystemClock;
     27 import android.text.format.Formatter;
     28 import android.util.Log;
     29 import android.util.SparseIntArray;
     30 import com.android.internal.os.BatteryStatsHelper;
     31 import com.android.settingslib.graph.UsageView;
     32 
     33 public class BatteryInfo {
     34 
     35     public String mChargeLabelString;
     36     public int mBatteryLevel;
     37     public boolean mDischarging = true;
     38     public long remainingTimeUs = 0;
     39     public String batteryPercentString;
     40     public String remainingLabel;
     41     private BatteryStats mStats;
     42     private boolean mCharging;
     43     private long timePeriod;
     44 
     45     public interface Callback {
     46         void onBatteryInfoLoaded(BatteryInfo info);
     47     }
     48 
     49     public void bindHistory(final UsageView view, BatteryDataParser... parsers) {
     50         BatteryDataParser parser = new BatteryDataParser() {
     51             SparseIntArray points = new SparseIntArray();
     52 
     53             @Override
     54             public void onParsingStarted(long startTime, long endTime) {
     55                 timePeriod = endTime - startTime - remainingTimeUs / 1000;
     56                 view.clearPaths();
     57                 view.configureGraph((int) (endTime - startTime), 100, remainingTimeUs != 0,
     58                         mCharging);
     59             }
     60 
     61             @Override
     62             public void onDataPoint(long time, HistoryItem record) {
     63                 points.put((int) time, record.batteryLevel);
     64             }
     65 
     66             @Override
     67             public void onDataGap() {
     68                 if (points.size() > 1) {
     69                     view.addPath(points);
     70                 }
     71                 points.clear();
     72             }
     73 
     74             @Override
     75             public void onParsingDone() {
     76                 if (points.size() > 1) {
     77                     view.addPath(points);
     78                 }
     79             }
     80         };
     81         BatteryDataParser[] parserList = new BatteryDataParser[parsers.length + 1];
     82         for (int i = 0; i < parsers.length; i++) {
     83             parserList[i] = parsers[i];
     84         }
     85         parserList[parsers.length] = parser;
     86         parse(mStats, remainingTimeUs, parserList);
     87         final Context context = view.getContext();
     88         String timeString = context.getString(R.string.charge_length_format,
     89                 Formatter.formatShortElapsedTime(context, timePeriod));
     90         String remaining = "";
     91         if (remainingTimeUs != 0) {
     92             remaining = context.getString(R.string.remaining_length_format,
     93                     Formatter.formatShortElapsedTime(context, remainingTimeUs / 1000));
     94         }
     95         view.setBottomLabels(new CharSequence[]{timeString, remaining});
     96     }
     97 
     98     public static void getBatteryInfo(final Context context, final Callback callback) {
     99         BatteryInfo.getBatteryInfo(context, callback, false);
    100     }
    101 
    102     public static void getBatteryInfo(final Context context, final Callback callback,
    103             boolean shortString) {
    104         new AsyncTask<Void, Void, BatteryStats>() {
    105             @Override
    106             protected BatteryStats doInBackground(Void... params) {
    107                 BatteryStatsHelper statsHelper = new BatteryStatsHelper(context, true);
    108                 statsHelper.create((Bundle) null);
    109                 return statsHelper.getStats();
    110             }
    111 
    112             @Override
    113             protected void onPostExecute(BatteryStats batteryStats) {
    114                 final long elapsedRealtimeUs = SystemClock.elapsedRealtime() * 1000;
    115                 Intent batteryBroadcast = context.registerReceiver(null,
    116                         new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    117                 BatteryInfo batteryInfo = BatteryInfo.getBatteryInfo(context,
    118                         batteryBroadcast, batteryStats, elapsedRealtimeUs, shortString);
    119                 callback.onBatteryInfoLoaded(batteryInfo);
    120             }
    121         }.execute();
    122     }
    123 
    124     public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast,
    125                                              BatteryStats stats, long elapsedRealtimeUs) {
    126         return BatteryInfo.getBatteryInfo(context, batteryBroadcast, stats, elapsedRealtimeUs,
    127                 false);
    128     }
    129 
    130     public static BatteryInfo getBatteryInfo(Context context, Intent batteryBroadcast,
    131             BatteryStats stats, long elapsedRealtimeUs, boolean shortString) {
    132         BatteryInfo info = new BatteryInfo();
    133         info.mStats = stats;
    134         info.mBatteryLevel = Utils.getBatteryLevel(batteryBroadcast);
    135         info.batteryPercentString = Utils.formatPercentage(info.mBatteryLevel);
    136         info.mCharging = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
    137         final Resources resources = context.getResources();
    138         if (!info.mCharging) {
    139             final long drainTime = stats.computeBatteryTimeRemaining(elapsedRealtimeUs);
    140             if (drainTime > 0) {
    141                 info.remainingTimeUs = drainTime;
    142                 String timeString = Formatter.formatShortElapsedTime(context,
    143                         drainTime / 1000);
    144                 info.remainingLabel = resources.getString(
    145                         shortString ? R.string.power_remaining_duration_only_short
    146                                 : R.string.power_remaining_duration_only,
    147                         timeString);
    148                 info.mChargeLabelString = resources.getString(
    149                         shortString ? R.string.power_discharging_duration_short
    150                                 : R.string.power_discharging_duration,
    151                         info.batteryPercentString, timeString);
    152             } else {
    153                 info.remainingLabel = null;
    154                 info.mChargeLabelString = info.batteryPercentString;
    155             }
    156         } else {
    157             final long chargeTime = stats.computeChargeTimeRemaining(elapsedRealtimeUs);
    158             final String statusLabel = Utils.getBatteryStatus(
    159                     resources, batteryBroadcast, shortString);
    160             final int status = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_STATUS,
    161                     BatteryManager.BATTERY_STATUS_UNKNOWN);
    162             if (chargeTime > 0 && status != BatteryManager.BATTERY_STATUS_FULL) {
    163                 info.mDischarging = false;
    164                 info.remainingTimeUs = chargeTime;
    165                 String timeString = Formatter.formatShortElapsedTime(context,
    166                         chargeTime / 1000);
    167                 int plugType = batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
    168                 int resId;
    169                 if (plugType == BatteryManager.BATTERY_PLUGGED_AC) {
    170                     resId = shortString ? R.string.power_charging_duration_ac_short
    171                             : R.string.power_charging_duration_ac;
    172                 } else if (plugType == BatteryManager.BATTERY_PLUGGED_USB) {
    173                     resId = shortString ? R.string.power_charging_duration_usb_short
    174                             : R.string.power_charging_duration_usb;
    175                 } else if (plugType == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
    176                     resId = shortString ? R.string.power_charging_duration_wireless_short
    177                             : R.string.power_charging_duration_wireless;
    178                 } else {
    179                     resId = shortString ? R.string.power_charging_duration_short
    180                             : R.string.power_charging_duration;
    181                 }
    182                 info.remainingLabel = resources.getString(R.string.power_remaining_duration_only,
    183                         timeString);
    184                 info.mChargeLabelString = resources.getString(
    185                         resId, info.batteryPercentString, timeString);
    186             } else {
    187                 info.remainingLabel = statusLabel;
    188                 info.mChargeLabelString = resources.getString(
    189                         R.string.power_charging, info.batteryPercentString, statusLabel);
    190             }
    191         }
    192         return info;
    193     }
    194 
    195     public interface BatteryDataParser {
    196         void onParsingStarted(long startTime, long endTime);
    197 
    198         void onDataPoint(long time, HistoryItem record);
    199 
    200         void onDataGap();
    201 
    202         void onParsingDone();
    203     }
    204 
    205     private static void parse(BatteryStats stats, long remainingTimeUs,
    206             BatteryDataParser... parsers) {
    207         long startWalltime = 0;
    208         long endDateWalltime = 0;
    209         long endWalltime = 0;
    210         long historyStart = 0;
    211         long historyEnd = 0;
    212         byte lastLevel = -1;
    213         long curWalltime = startWalltime;
    214         long lastWallTime = 0;
    215         long lastRealtime = 0;
    216         int lastInteresting = 0;
    217         int pos = 0;
    218         boolean first = true;
    219         if (stats.startIteratingHistoryLocked()) {
    220             final HistoryItem rec = new HistoryItem();
    221             while (stats.getNextHistoryLocked(rec)) {
    222                 pos++;
    223                 if (first) {
    224                     first = false;
    225                     historyStart = rec.time;
    226                 }
    227                 if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
    228                         || rec.cmd == HistoryItem.CMD_RESET) {
    229                     // If there is a ridiculously large jump in time, then we won't be
    230                     // able to create a good chart with that data, so just ignore the
    231                     // times we got before and pretend like our data extends back from
    232                     // the time we have now.
    233                     // Also, if we are getting a time change and we are less than 5 minutes
    234                     // since the start of the history real time, then also use this new
    235                     // time to compute the base time, since whatever time we had before is
    236                     // pretty much just noise.
    237                     if (rec.currentTime > (lastWallTime + (180 * 24 * 60 * 60 * 1000L))
    238                             || rec.time < (historyStart + (5 * 60 * 1000L))) {
    239                         startWalltime = 0;
    240                     }
    241                     lastWallTime = rec.currentTime;
    242                     lastRealtime = rec.time;
    243                     if (startWalltime == 0) {
    244                         startWalltime = lastWallTime - (lastRealtime - historyStart);
    245                     }
    246                 }
    247                 if (rec.isDeltaData()) {
    248                     if (rec.batteryLevel != lastLevel || pos == 1) {
    249                         lastLevel = rec.batteryLevel;
    250                     }
    251                     lastInteresting = pos;
    252                     historyEnd = rec.time;
    253                 }
    254             }
    255         }
    256         stats.finishIteratingHistoryLocked();
    257         endDateWalltime = lastWallTime + historyEnd - lastRealtime;
    258         endWalltime = endDateWalltime + (remainingTimeUs / 1000);
    259 
    260         int i = 0;
    261         final int N = lastInteresting;
    262 
    263         for (int j = 0; j < parsers.length; j++) {
    264             parsers[j].onParsingStarted(startWalltime, endWalltime);
    265         }
    266         if (endDateWalltime > startWalltime && stats.startIteratingHistoryLocked()) {
    267             final HistoryItem rec = new HistoryItem();
    268             while (stats.getNextHistoryLocked(rec) && i < N) {
    269                 if (rec.isDeltaData()) {
    270                     curWalltime += rec.time - lastRealtime;
    271                     lastRealtime = rec.time;
    272                     long x = (curWalltime - startWalltime);
    273                     if (x < 0) {
    274                         x = 0;
    275                     }
    276                     for (int j = 0; j < parsers.length; j++) {
    277                         parsers[j].onDataPoint(x, rec);
    278                     }
    279                 } else {
    280                     long lastWalltime = curWalltime;
    281                     if (rec.cmd == HistoryItem.CMD_CURRENT_TIME
    282                             || rec.cmd == HistoryItem.CMD_RESET) {
    283                         if (rec.currentTime >= startWalltime) {
    284                             curWalltime = rec.currentTime;
    285                         } else {
    286                             curWalltime = startWalltime + (rec.time - historyStart);
    287                         }
    288                         lastRealtime = rec.time;
    289                     }
    290 
    291                     if (rec.cmd != HistoryItem.CMD_OVERFLOW
    292                             && (rec.cmd != HistoryItem.CMD_CURRENT_TIME
    293                             || Math.abs(lastWalltime - curWalltime) > (60 * 60 * 1000))) {
    294                         for (int j = 0; j < parsers.length; j++) {
    295                             parsers[j].onDataGap();
    296                         }
    297                     }
    298                 }
    299                 i++;
    300             }
    301         }
    302 
    303         stats.finishIteratingHistoryLocked();
    304 
    305         for (int j = 0; j < parsers.length; j++) {
    306             parsers[j].onParsingDone();
    307         }
    308     }
    309 }
    310