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