1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settings.fuelgauge.batteryusage; 17 18 import android.content.ContentValues; 19 import android.database.Cursor; 20 import android.os.BatteryConsumer; 21 import android.util.Log; 22 23 import java.time.Duration; 24 25 /** A container class to carry data from {@link ContentValues}. */ 26 public class BatteryHistEntry { 27 private static final boolean DEBUG = false; 28 private static final String TAG = "BatteryHistEntry"; 29 30 /** Keys for accessing {@link ContentValues} or {@link Cursor}. */ 31 public static final String KEY_UID = "uid"; 32 public static final String KEY_USER_ID = "userId"; 33 public static final String KEY_APP_LABEL = "appLabel"; 34 public static final String KEY_PACKAGE_NAME = "packageName"; 35 public static final String KEY_IS_HIDDEN = "isHidden"; 36 // Device booting elapsed time from SystemClock.elapsedRealtime(). 37 public static final String KEY_BOOT_TIMESTAMP = "bootTimestamp"; 38 public static final String KEY_TIMESTAMP = "timestamp"; 39 public static final String KEY_ZONE_ID = "zoneId"; 40 public static final String KEY_TOTAL_POWER = "totalPower"; 41 public static final String KEY_CONSUME_POWER = "consumePower"; 42 public static final String KEY_PERCENT_OF_TOTAL = "percentOfTotal"; 43 public static final String KEY_FOREGROUND_USAGE_TIME = "foregroundUsageTimeInMs"; 44 public static final String KEY_BACKGROUND_USAGE_TIME = "backgroundUsageTimeInMs"; 45 public static final String KEY_DRAIN_TYPE = "drainType"; 46 public static final String KEY_CONSUMER_TYPE = "consumerType"; 47 public static final String KEY_BATTERY_LEVEL = "batteryLevel"; 48 public static final String KEY_BATTERY_STATUS = "batteryStatus"; 49 public static final String KEY_BATTERY_HEALTH = "batteryHealth"; 50 51 public final long mUid; 52 public final long mUserId; 53 public final String mAppLabel; 54 public final String mPackageName; 55 // Whether the data is represented as system component or not? 56 public final boolean mIsHidden; 57 // Records the timestamp relative information. 58 public final long mBootTimestamp; 59 public final long mTimestamp; 60 public final String mZoneId; 61 // Records the battery usage relative information. 62 public final double mTotalPower; 63 public final double mConsumePower; 64 public final double mPercentOfTotal; 65 public final long mForegroundUsageTimeInMs; 66 public final long mBackgroundUsageTimeInMs; 67 @BatteryConsumer.PowerComponent 68 public final int mDrainType; 69 @ConvertUtils.ConsumerType 70 public final int mConsumerType; 71 // Records the battery intent relative information. 72 public final int mBatteryLevel; 73 public final int mBatteryStatus; 74 public final int mBatteryHealth; 75 76 private String mKey = null; 77 private boolean mIsValidEntry = true; 78 BatteryHistEntry(ContentValues values)79 public BatteryHistEntry(ContentValues values) { 80 mUid = getLong(values, KEY_UID); 81 mUserId = getLong(values, KEY_USER_ID); 82 mAppLabel = getString(values, KEY_APP_LABEL); 83 mPackageName = getString(values, KEY_PACKAGE_NAME); 84 mIsHidden = getBoolean(values, KEY_IS_HIDDEN); 85 mBootTimestamp = getLong(values, KEY_BOOT_TIMESTAMP); 86 mTimestamp = getLong(values, KEY_TIMESTAMP); 87 mZoneId = getString(values, KEY_ZONE_ID); 88 mTotalPower = getDouble(values, KEY_TOTAL_POWER); 89 mConsumePower = getDouble(values, KEY_CONSUME_POWER); 90 mPercentOfTotal = getDouble(values, KEY_PERCENT_OF_TOTAL); 91 mForegroundUsageTimeInMs = getLong(values, KEY_FOREGROUND_USAGE_TIME); 92 mBackgroundUsageTimeInMs = getLong(values, KEY_BACKGROUND_USAGE_TIME); 93 mDrainType = getInteger(values, KEY_DRAIN_TYPE); 94 mConsumerType = getInteger(values, KEY_CONSUMER_TYPE); 95 mBatteryLevel = getInteger(values, KEY_BATTERY_LEVEL); 96 mBatteryStatus = getInteger(values, KEY_BATTERY_STATUS); 97 mBatteryHealth = getInteger(values, KEY_BATTERY_HEALTH); 98 } 99 BatteryHistEntry(Cursor cursor)100 public BatteryHistEntry(Cursor cursor) { 101 mUid = getLong(cursor, KEY_UID); 102 mUserId = getLong(cursor, KEY_USER_ID); 103 mAppLabel = getString(cursor, KEY_APP_LABEL); 104 mPackageName = getString(cursor, KEY_PACKAGE_NAME); 105 mIsHidden = getBoolean(cursor, KEY_IS_HIDDEN); 106 mBootTimestamp = getLong(cursor, KEY_BOOT_TIMESTAMP); 107 mTimestamp = getLong(cursor, KEY_TIMESTAMP); 108 mZoneId = getString(cursor, KEY_ZONE_ID); 109 mTotalPower = getDouble(cursor, KEY_TOTAL_POWER); 110 mConsumePower = getDouble(cursor, KEY_CONSUME_POWER); 111 mPercentOfTotal = getDouble(cursor, KEY_PERCENT_OF_TOTAL); 112 mForegroundUsageTimeInMs = getLong(cursor, KEY_FOREGROUND_USAGE_TIME); 113 mBackgroundUsageTimeInMs = getLong(cursor, KEY_BACKGROUND_USAGE_TIME); 114 mDrainType = getInteger(cursor, KEY_DRAIN_TYPE); 115 mConsumerType = getInteger(cursor, KEY_CONSUMER_TYPE); 116 mBatteryLevel = getInteger(cursor, KEY_BATTERY_LEVEL); 117 mBatteryStatus = getInteger(cursor, KEY_BATTERY_STATUS); 118 mBatteryHealth = getInteger(cursor, KEY_BATTERY_HEALTH); 119 } 120 BatteryHistEntry( BatteryHistEntry fromEntry, long bootTimestamp, long timestamp, double totalPower, double consumePower, long foregroundUsageTimeInMs, long backgroundUsageTimeInMs, int batteryLevel)121 private BatteryHistEntry( 122 BatteryHistEntry fromEntry, 123 long bootTimestamp, 124 long timestamp, 125 double totalPower, 126 double consumePower, 127 long foregroundUsageTimeInMs, 128 long backgroundUsageTimeInMs, 129 int batteryLevel) { 130 mUid = fromEntry.mUid; 131 mUserId = fromEntry.mUserId; 132 mAppLabel = fromEntry.mAppLabel; 133 mPackageName = fromEntry.mPackageName; 134 mIsHidden = fromEntry.mIsHidden; 135 mBootTimestamp = bootTimestamp; 136 mTimestamp = timestamp; 137 mZoneId = fromEntry.mZoneId; 138 mTotalPower = totalPower; 139 mConsumePower = consumePower; 140 mPercentOfTotal = fromEntry.mPercentOfTotal; 141 mForegroundUsageTimeInMs = foregroundUsageTimeInMs; 142 mBackgroundUsageTimeInMs = backgroundUsageTimeInMs; 143 mDrainType = fromEntry.mDrainType; 144 mConsumerType = fromEntry.mConsumerType; 145 mBatteryLevel = batteryLevel; 146 mBatteryStatus = fromEntry.mBatteryStatus; 147 mBatteryHealth = fromEntry.mBatteryHealth; 148 } 149 150 /** Whether this {@link BatteryHistEntry} is valid or not? */ isValidEntry()151 public boolean isValidEntry() { 152 return mIsValidEntry; 153 } 154 155 /** Whether this {@link BatteryHistEntry} is user consumer or not. */ isUserEntry()156 public boolean isUserEntry() { 157 return mConsumerType == ConvertUtils.CONSUMER_TYPE_USER_BATTERY; 158 } 159 160 /** Whether this {@link BatteryHistEntry} is app consumer or not. */ isAppEntry()161 public boolean isAppEntry() { 162 return mConsumerType == ConvertUtils.CONSUMER_TYPE_UID_BATTERY; 163 } 164 165 /** Whether this {@link BatteryHistEntry} is system consumer or not. */ isSystemEntry()166 public boolean isSystemEntry() { 167 return mConsumerType == ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY; 168 } 169 170 /** Gets an identifier to represent this {@link BatteryHistEntry}. */ getKey()171 public String getKey() { 172 if (mKey == null) { 173 switch (mConsumerType) { 174 case ConvertUtils.CONSUMER_TYPE_UID_BATTERY: 175 mKey = Long.toString(mUid); 176 break; 177 case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY: 178 mKey = "S|" + mDrainType; 179 break; 180 case ConvertUtils.CONSUMER_TYPE_USER_BATTERY: 181 mKey = "U|" + mUserId; 182 break; 183 } 184 } 185 return mKey; 186 } 187 188 @Override toString()189 public String toString() { 190 final String recordAtDateTime = 191 ConvertUtils.utcToLocalTime(/*context=*/ null, mTimestamp); 192 final StringBuilder builder = new StringBuilder() 193 .append("\nBatteryHistEntry{") 194 .append(String.format("\n\tpackage=%s|label=%s|uid=%d|userId=%d|isHidden=%b", 195 mPackageName, mAppLabel, mUid, mUserId, mIsHidden)) 196 .append(String.format("\n\ttimestamp=%s|zoneId=%s|bootTimestamp=%d", 197 recordAtDateTime, mZoneId, Duration.ofMillis(mBootTimestamp).getSeconds())) 198 .append(String.format("\n\tusage=%f|total=%f|consume=%f|elapsedTime=%d|%d", 199 mPercentOfTotal, mTotalPower, mConsumePower, 200 Duration.ofMillis(mForegroundUsageTimeInMs).getSeconds(), 201 Duration.ofMillis(mBackgroundUsageTimeInMs).getSeconds())) 202 .append(String.format("\n\tdrainType=%d|consumerType=%d", 203 mDrainType, mConsumerType)) 204 .append(String.format("\n\tbattery=%d|status=%d|health=%d\n}", 205 mBatteryLevel, mBatteryStatus, mBatteryHealth)); 206 return builder.toString(); 207 } 208 getInteger(ContentValues values, String key)209 private int getInteger(ContentValues values, String key) { 210 if (values != null && values.containsKey(key)) { 211 return values.getAsInteger(key); 212 } 213 mIsValidEntry = false; 214 return 0; 215 } 216 getInteger(Cursor cursor, String key)217 private int getInteger(Cursor cursor, String key) { 218 final int columnIndex = cursor.getColumnIndex(key); 219 if (columnIndex >= 0) { 220 return cursor.getInt(columnIndex); 221 } 222 mIsValidEntry = false; 223 return 0; 224 } 225 getLong(ContentValues values, String key)226 private long getLong(ContentValues values, String key) { 227 if (values != null && values.containsKey(key)) { 228 return values.getAsLong(key); 229 } 230 mIsValidEntry = false; 231 return 0L; 232 } 233 getLong(Cursor cursor, String key)234 private long getLong(Cursor cursor, String key) { 235 final int columnIndex = cursor.getColumnIndex(key); 236 if (columnIndex >= 0) { 237 return cursor.getLong(columnIndex); 238 } 239 mIsValidEntry = false; 240 return 0L; 241 } 242 getDouble(ContentValues values, String key)243 private double getDouble(ContentValues values, String key) { 244 if (values != null && values.containsKey(key)) { 245 return values.getAsDouble(key); 246 } 247 mIsValidEntry = false; 248 return 0f; 249 } 250 getDouble(Cursor cursor, String key)251 private double getDouble(Cursor cursor, String key) { 252 final int columnIndex = cursor.getColumnIndex(key); 253 if (columnIndex >= 0) { 254 return cursor.getDouble(columnIndex); 255 } 256 mIsValidEntry = false; 257 return 0f; 258 } 259 getString(ContentValues values, String key)260 private String getString(ContentValues values, String key) { 261 if (values != null && values.containsKey(key)) { 262 return values.getAsString(key); 263 } 264 mIsValidEntry = false; 265 return null; 266 } 267 getString(Cursor cursor, String key)268 private String getString(Cursor cursor, String key) { 269 final int columnIndex = cursor.getColumnIndex(key); 270 if (columnIndex >= 0) { 271 return cursor.getString(columnIndex); 272 } 273 mIsValidEntry = false; 274 return null; 275 } 276 getBoolean(ContentValues values, String key)277 private boolean getBoolean(ContentValues values, String key) { 278 if (values != null && values.containsKey(key)) { 279 return values.getAsBoolean(key); 280 } 281 mIsValidEntry = false; 282 return false; 283 } 284 getBoolean(Cursor cursor, String key)285 private boolean getBoolean(Cursor cursor, String key) { 286 final int columnIndex = cursor.getColumnIndex(key); 287 if (columnIndex >= 0) { 288 // Use value == 1 to represent boolean value in the database. 289 return cursor.getInt(columnIndex) == 1; 290 } 291 mIsValidEntry = false; 292 return false; 293 } 294 295 /** Creates new {@link BatteryHistEntry} from interpolation. */ interpolate( long slotTimestamp, long upperTimestamp, double ratio, BatteryHistEntry lowerHistEntry, BatteryHistEntry upperHistEntry)296 public static BatteryHistEntry interpolate( 297 long slotTimestamp, 298 long upperTimestamp, 299 double ratio, 300 BatteryHistEntry lowerHistEntry, 301 BatteryHistEntry upperHistEntry) { 302 final double totalPower = interpolate( 303 lowerHistEntry == null ? 0 : lowerHistEntry.mTotalPower, 304 upperHistEntry.mTotalPower, 305 ratio); 306 final double consumePower = interpolate( 307 lowerHistEntry == null ? 0 : lowerHistEntry.mConsumePower, 308 upperHistEntry.mConsumePower, 309 ratio); 310 final double foregroundUsageTimeInMs = interpolate( 311 lowerHistEntry == null ? 0 : lowerHistEntry.mForegroundUsageTimeInMs, 312 upperHistEntry.mForegroundUsageTimeInMs, 313 ratio); 314 final double backgroundUsageTimeInMs = interpolate( 315 lowerHistEntry == null ? 0 : lowerHistEntry.mBackgroundUsageTimeInMs, 316 upperHistEntry.mBackgroundUsageTimeInMs, 317 ratio); 318 // Checks whether there is any abnoaml cases! 319 if (upperHistEntry.mConsumePower < consumePower 320 || upperHistEntry.mForegroundUsageTimeInMs < foregroundUsageTimeInMs 321 || upperHistEntry.mBackgroundUsageTimeInMs < backgroundUsageTimeInMs) { 322 if (DEBUG) { 323 Log.w(TAG, String.format( 324 "abnormal interpolation:\nupper:%s\nlower:%s", 325 upperHistEntry, lowerHistEntry)); 326 } 327 } 328 final double batteryLevel = 329 lowerHistEntry == null 330 ? upperHistEntry.mBatteryLevel 331 : interpolate( 332 lowerHistEntry.mBatteryLevel, 333 upperHistEntry.mBatteryLevel, 334 ratio); 335 return new BatteryHistEntry( 336 upperHistEntry, 337 /*bootTimestamp=*/ upperHistEntry.mBootTimestamp 338 - (upperTimestamp - slotTimestamp), 339 /*timestamp=*/ slotTimestamp, 340 totalPower, 341 consumePower, 342 Math.round(foregroundUsageTimeInMs), 343 Math.round(backgroundUsageTimeInMs), 344 (int) Math.round(batteryLevel)); 345 } 346 interpolate(double v1, double v2, double ratio)347 private static double interpolate(double v1, double v2, double ratio) { 348 return v1 + ratio * (v2 - v1); 349 } 350 } 351