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