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 17 package com.android.settings.fuelgauge.batteryusage; 18 19 import android.content.ContentProvider; 20 import android.content.ContentValues; 21 import android.content.UriMatcher; 22 import android.database.Cursor; 23 import android.net.Uri; 24 import android.text.TextUtils; 25 import android.util.Log; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 import androidx.annotation.VisibleForTesting; 30 31 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventDao; 32 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity; 33 import com.android.settings.fuelgauge.batteryusage.db.BatteryEventDao; 34 import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity; 35 import com.android.settings.fuelgauge.batteryusage.db.BatteryState; 36 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao; 37 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase; 38 import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotDao; 39 import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotEntity; 40 import com.android.settingslib.fuelgauge.BatteryUtils; 41 42 import java.time.Clock; 43 import java.time.Duration; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.List; 47 48 /** {@link ContentProvider} class to fetch battery usage data. */ 49 public class BatteryUsageContentProvider extends ContentProvider { 50 private static final String TAG = "BatteryUsageContentProvider"; 51 52 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 53 public static final Duration QUERY_DURATION_HOURS = Duration.ofDays(6); 54 55 /** Codes */ 56 private static final int BATTERY_STATE_CODE = 1; 57 58 private static final int APP_USAGE_LATEST_TIMESTAMP_CODE = 2; 59 private static final int APP_USAGE_EVENT_CODE = 3; 60 private static final int BATTERY_EVENT_CODE = 4; 61 private static final int LAST_FULL_CHARGE_TIMESTAMP_CODE = 5; 62 private static final int BATTERY_STATE_LATEST_TIMESTAMP_CODE = 6; 63 private static final int BATTERY_USAGE_SLOT_CODE = 7; 64 65 private static final List<Integer> ALL_BATTERY_EVENT_TYPES = 66 Arrays.stream(BatteryEventType.values()).map(type -> type.getNumber()).toList(); 67 private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 68 69 static { sUriMatcher.addURI( DatabaseUtils.AUTHORITY, DatabaseUtils.BATTERY_STATE_TABLE, BATTERY_STATE_CODE)70 sUriMatcher.addURI( 71 DatabaseUtils.AUTHORITY, 72 /* path= */ DatabaseUtils.BATTERY_STATE_TABLE, 73 /* code= */ BATTERY_STATE_CODE); sUriMatcher.addURI( DatabaseUtils.AUTHORITY, DatabaseUtils.APP_USAGE_LATEST_TIMESTAMP_PATH, APP_USAGE_LATEST_TIMESTAMP_CODE)74 sUriMatcher.addURI( 75 DatabaseUtils.AUTHORITY, 76 /* path= */ DatabaseUtils.APP_USAGE_LATEST_TIMESTAMP_PATH, 77 /* code= */ APP_USAGE_LATEST_TIMESTAMP_CODE); sUriMatcher.addURI( DatabaseUtils.AUTHORITY, DatabaseUtils.APP_USAGE_EVENT_TABLE, APP_USAGE_EVENT_CODE)78 sUriMatcher.addURI( 79 DatabaseUtils.AUTHORITY, 80 /* path= */ DatabaseUtils.APP_USAGE_EVENT_TABLE, 81 /* code= */ APP_USAGE_EVENT_CODE); sUriMatcher.addURI( DatabaseUtils.AUTHORITY, DatabaseUtils.BATTERY_EVENT_TABLE, BATTERY_EVENT_CODE)82 sUriMatcher.addURI( 83 DatabaseUtils.AUTHORITY, 84 /* path= */ DatabaseUtils.BATTERY_EVENT_TABLE, 85 /* code= */ BATTERY_EVENT_CODE); sUriMatcher.addURI( DatabaseUtils.AUTHORITY, DatabaseUtils.LAST_FULL_CHARGE_TIMESTAMP_PATH, LAST_FULL_CHARGE_TIMESTAMP_CODE)86 sUriMatcher.addURI( 87 DatabaseUtils.AUTHORITY, 88 /* path= */ DatabaseUtils.LAST_FULL_CHARGE_TIMESTAMP_PATH, 89 /* code= */ LAST_FULL_CHARGE_TIMESTAMP_CODE); sUriMatcher.addURI( DatabaseUtils.AUTHORITY, DatabaseUtils.BATTERY_STATE_LATEST_TIMESTAMP_PATH, BATTERY_STATE_LATEST_TIMESTAMP_CODE)90 sUriMatcher.addURI( 91 DatabaseUtils.AUTHORITY, 92 /* path= */ DatabaseUtils.BATTERY_STATE_LATEST_TIMESTAMP_PATH, 93 /* code= */ BATTERY_STATE_LATEST_TIMESTAMP_CODE); sUriMatcher.addURI( DatabaseUtils.AUTHORITY, DatabaseUtils.BATTERY_USAGE_SLOT_TABLE, BATTERY_USAGE_SLOT_CODE)94 sUriMatcher.addURI( 95 DatabaseUtils.AUTHORITY, 96 /* path= */ DatabaseUtils.BATTERY_USAGE_SLOT_TABLE, 97 /* code= */ BATTERY_USAGE_SLOT_CODE); 98 } 99 100 private Clock mClock; 101 private BatteryStateDao mBatteryStateDao; 102 private AppUsageEventDao mAppUsageEventDao; 103 private BatteryEventDao mBatteryEventDao; 104 private BatteryUsageSlotDao mBatteryUsageSlotDao; 105 106 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) setClock(Clock clock)107 public void setClock(Clock clock) { 108 this.mClock = clock; 109 } 110 111 @Override onCreate()112 public boolean onCreate() { 113 if (BatteryUtils.isAdditionalProfile(getContext())) { 114 Log.w(TAG, "do not create provider for an additional profile"); 115 return false; 116 } 117 mClock = Clock.systemUTC(); 118 final BatteryStateDatabase database = BatteryStateDatabase.getInstance(getContext()); 119 mBatteryStateDao = database.batteryStateDao(); 120 mAppUsageEventDao = database.appUsageEventDao(); 121 mBatteryEventDao = database.batteryEventDao(); 122 mBatteryUsageSlotDao = database.batteryUsageSlotDao(); 123 Log.w(TAG, "create content provider from " + getCallingPackage()); 124 return true; 125 } 126 127 @Nullable 128 @Override query( @onNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1)129 public Cursor query( 130 @NonNull Uri uri, 131 @Nullable String[] strings, 132 @Nullable String s, 133 @Nullable String[] strings1, 134 @Nullable String s1) { 135 switch (sUriMatcher.match(uri)) { 136 case BATTERY_STATE_CODE: 137 return getBatteryStates(uri); 138 case APP_USAGE_EVENT_CODE: 139 return getAppUsageEvents(uri); 140 case APP_USAGE_LATEST_TIMESTAMP_CODE: 141 return getAppUsageLatestTimestamp(uri); 142 case BATTERY_EVENT_CODE: 143 return getBatteryEvents(uri); 144 case LAST_FULL_CHARGE_TIMESTAMP_CODE: 145 return getLastFullChargeTimestamp(uri); 146 case BATTERY_STATE_LATEST_TIMESTAMP_CODE: 147 return getBatteryStateLatestTimestamp(uri); 148 case BATTERY_USAGE_SLOT_CODE: 149 return getBatteryUsageSlots(uri); 150 default: 151 throw new IllegalArgumentException("unknown URI: " + uri); 152 } 153 } 154 155 @Nullable 156 @Override getType(@onNull Uri uri)157 public String getType(@NonNull Uri uri) { 158 return null; 159 } 160 161 @Nullable 162 @Override insert(@onNull Uri uri, @Nullable ContentValues contentValues)163 public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) { 164 try { 165 switch (sUriMatcher.match(uri)) { 166 case BATTERY_STATE_CODE: 167 mBatteryStateDao.insert(BatteryState.create(contentValues)); 168 break; 169 case APP_USAGE_EVENT_CODE: 170 mAppUsageEventDao.insert(AppUsageEventEntity.create(contentValues)); 171 break; 172 case BATTERY_EVENT_CODE: 173 mBatteryEventDao.insert(BatteryEventEntity.create(contentValues)); 174 break; 175 case BATTERY_USAGE_SLOT_CODE: 176 mBatteryUsageSlotDao.insert(BatteryUsageSlotEntity.create(contentValues)); 177 break; 178 default: 179 throw new IllegalArgumentException("unknown URI: " + uri); 180 } 181 } catch (RuntimeException e) { 182 if (e instanceof IllegalArgumentException) { 183 throw e; 184 } 185 Log.e(TAG, "insert() from:" + uri + " error:", e); 186 return null; 187 } 188 return uri; 189 } 190 191 @Override delete(@onNull Uri uri, @Nullable String s, @Nullable String[] strings)192 public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) { 193 throw new UnsupportedOperationException("unsupported!"); 194 } 195 196 @Override update( @onNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings)197 public int update( 198 @NonNull Uri uri, 199 @Nullable ContentValues contentValues, 200 @Nullable String s, 201 @Nullable String[] strings) { 202 throw new UnsupportedOperationException("unsupported!"); 203 } 204 getLastFullChargeTimestamp(Uri uri)205 private Cursor getLastFullChargeTimestamp(Uri uri) { 206 final long timestamp = mClock.millis(); 207 Cursor cursor = null; 208 try { 209 cursor = mBatteryEventDao.getLastFullChargeTimestamp(); 210 } catch (RuntimeException e) { 211 Log.e(TAG, "query() from:" + uri + " error:", e); 212 } 213 Log.d( 214 TAG, 215 String.format( 216 "getLastFullChargeTimestamp() in %d/ms", mClock.millis() - timestamp)); 217 return cursor; 218 } 219 getBatteryStateLatestTimestamp(Uri uri)220 private Cursor getBatteryStateLatestTimestamp(Uri uri) { 221 final long queryTimestamp = getQueryTimestamp(uri); 222 final long timestamp = mClock.millis(); 223 Cursor cursor = null; 224 try { 225 cursor = mBatteryStateDao.getLatestTimestampBefore(queryTimestamp); 226 } catch (RuntimeException e) { 227 Log.e(TAG, "query() from:" + uri + " error:", e); 228 } 229 Log.d( 230 TAG, 231 String.format( 232 "getBatteryStateLatestTimestamp() no later than %d in %d/ms", 233 queryTimestamp, mClock.millis() - timestamp)); 234 return cursor; 235 } 236 getBatteryStates(Uri uri)237 private Cursor getBatteryStates(Uri uri) { 238 final long queryTimestamp = getQueryTimestamp(uri); 239 final long timestamp = mClock.millis(); 240 Cursor cursor = null; 241 try { 242 cursor = mBatteryStateDao.getBatteryStatesAfter(queryTimestamp); 243 } catch (RuntimeException e) { 244 Log.e(TAG, "query() from:" + uri + " error:", e); 245 } 246 Log.d( 247 TAG, 248 String.format( 249 "getBatteryStates() after %d in %d/ms", 250 queryTimestamp, mClock.millis() - timestamp)); 251 return cursor; 252 } 253 getAppUsageEvents(Uri uri)254 private Cursor getAppUsageEvents(Uri uri) { 255 final List<Long> queryUserIds = getQueryUserIds(uri); 256 if (queryUserIds == null || queryUserIds.isEmpty()) { 257 return null; 258 } 259 final long queryTimestamp = getQueryTimestamp(uri); 260 final long timestamp = mClock.millis(); 261 Cursor cursor = null; 262 try { 263 cursor = mAppUsageEventDao.getAllForUsersAfter(queryUserIds, queryTimestamp); 264 } catch (RuntimeException e) { 265 Log.e(TAG, "query() from:" + uri + " error:", e); 266 } 267 Log.w(TAG, "getAppUsageEvents() in " + (mClock.millis() - timestamp) + "/ms"); 268 return cursor; 269 } 270 getAppUsageLatestTimestamp(Uri uri)271 private Cursor getAppUsageLatestTimestamp(Uri uri) { 272 final long queryUserId = getQueryUserId(uri); 273 if (queryUserId == DatabaseUtils.INVALID_USER_ID) { 274 return null; 275 } 276 final long timestamp = mClock.millis(); 277 Cursor cursor = null; 278 try { 279 cursor = mAppUsageEventDao.getLatestTimestampOfUser(queryUserId); 280 } catch (RuntimeException e) { 281 Log.e(TAG, "query() from:" + uri + " error:", e); 282 } 283 Log.d( 284 TAG, 285 String.format( 286 "getAppUsageLatestTimestamp() for user %d in %d/ms", 287 queryUserId, (mClock.millis() - timestamp))); 288 return cursor; 289 } 290 getBatteryEvents(Uri uri)291 private Cursor getBatteryEvents(Uri uri) { 292 List<Integer> queryBatteryEventTypes = getQueryBatteryEventTypes(uri); 293 if (queryBatteryEventTypes == null || queryBatteryEventTypes.isEmpty()) { 294 queryBatteryEventTypes = ALL_BATTERY_EVENT_TYPES; 295 } 296 final long queryTimestamp = getQueryTimestamp(uri); 297 final long timestamp = mClock.millis(); 298 Cursor cursor = null; 299 try { 300 cursor = mBatteryEventDao.getAllAfter(queryTimestamp, queryBatteryEventTypes); 301 } catch (RuntimeException e) { 302 Log.e(TAG, "query() from:" + uri + " error:", e); 303 } 304 Log.w(TAG, "getBatteryEvents() in " + (mClock.millis() - timestamp) + "/ms"); 305 return cursor; 306 } 307 getBatteryUsageSlots(Uri uri)308 private Cursor getBatteryUsageSlots(Uri uri) { 309 final long queryTimestamp = getQueryTimestamp(uri); 310 final long timestamp = mClock.millis(); 311 Cursor cursor = null; 312 try { 313 cursor = mBatteryUsageSlotDao.getAllAfter(queryTimestamp); 314 } catch (RuntimeException e) { 315 Log.e(TAG, "query() from:" + uri + " error:", e); 316 } 317 Log.w(TAG, "getBatteryUsageSlots() in " + (mClock.millis() - timestamp) + "/ms"); 318 return cursor; 319 } 320 getQueryBatteryEventTypes(Uri uri)321 private List<Integer> getQueryBatteryEventTypes(Uri uri) { 322 Log.d(TAG, "getQueryBatteryEventTypes from uri: " + uri); 323 final String batteryEventTypesParameter = 324 uri.getQueryParameter(DatabaseUtils.QUERY_BATTERY_EVENT_TYPE); 325 if (TextUtils.isEmpty(batteryEventTypesParameter)) { 326 return null; 327 } 328 try { 329 List<Integer> batteryEventTypes = new ArrayList<>(); 330 for (String typeString : batteryEventTypesParameter.split(",")) { 331 batteryEventTypes.add(Integer.parseInt(typeString.trim())); 332 } 333 return batteryEventTypes; 334 } catch (NumberFormatException e) { 335 Log.e(TAG, "invalid query value: " + batteryEventTypesParameter, e); 336 return null; 337 } 338 } 339 340 // If URI contains query parameter QUERY_KEY_USERID, use the value directly. 341 // Otherwise, return null. getQueryUserIds(Uri uri)342 private List<Long> getQueryUserIds(Uri uri) { 343 Log.d(TAG, "getQueryUserIds from uri: " + uri); 344 final String userIdsParameter = uri.getQueryParameter(DatabaseUtils.QUERY_KEY_USERID); 345 if (TextUtils.isEmpty(userIdsParameter)) { 346 return null; 347 } 348 try { 349 List<Long> userIds = new ArrayList<>(); 350 for (String idString : userIdsParameter.split(",")) { 351 userIds.add(Long.parseLong(idString.trim())); 352 } 353 return userIds; 354 } catch (NumberFormatException e) { 355 Log.e(TAG, "invalid query value: " + userIdsParameter, e); 356 return null; 357 } 358 } 359 360 // If URI contains query parameter QUERY_KEY_USERID, use the value directly. 361 // Otherwise, return INVALID_USER_ID. getQueryUserId(Uri uri)362 private long getQueryUserId(Uri uri) { 363 Log.d(TAG, "getQueryUserId from uri: " + uri); 364 return getQueryValueFromUri( 365 uri, DatabaseUtils.QUERY_KEY_USERID, DatabaseUtils.INVALID_USER_ID); 366 } 367 368 // If URI contains query parameter QUERY_KEY_TIMESTAMP, use the value directly. 369 // Otherwise, load the data for QUERY_DURATION_HOURS by default. getQueryTimestamp(Uri uri)370 private long getQueryTimestamp(Uri uri) { 371 Log.d(TAG, "getQueryTimestamp from uri: " + uri); 372 final long defaultTimestamp = mClock.millis() - QUERY_DURATION_HOURS.toMillis(); 373 return getQueryValueFromUri(uri, DatabaseUtils.QUERY_KEY_TIMESTAMP, defaultTimestamp); 374 } 375 getQueryValueFromUri(Uri uri, String key, long defaultValue)376 private long getQueryValueFromUri(Uri uri, String key, long defaultValue) { 377 final String value = uri.getQueryParameter(key); 378 if (TextUtils.isEmpty(value)) { 379 Log.w(TAG, "empty query value"); 380 return defaultValue; 381 } 382 383 try { 384 return Long.parseLong(value); 385 } catch (NumberFormatException e) { 386 Log.e(TAG, "invalid query value: " + value, e); 387 return defaultValue; 388 } 389 } 390 } 391