• 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 import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTimeForLogging;
19 
20 import android.app.usage.IUsageStatsManager;
21 import android.app.usage.UsageStatsManager;
22 import android.content.ContentResolver;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.SharedPreferences;
27 import android.content.pm.PackageManager;
28 import android.database.Cursor;
29 import android.net.Uri;
30 import android.os.AsyncTask;
31 import android.os.BatteryManager;
32 import android.os.BatteryUsageStats;
33 import android.os.Handler;
34 import android.os.Looper;
35 import android.os.RemoteException;
36 import android.os.SystemClock;
37 import android.os.UserManager;
38 import android.util.ArrayMap;
39 import android.util.ArraySet;
40 import android.util.Log;
41 
42 import androidx.annotation.VisibleForTesting;
43 
44 import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
45 import com.android.settings.fuelgauge.BatteryUtils;
46 import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
47 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
48 import com.android.settingslib.fuelgauge.BatteryStatus;
49 
50 import java.io.PrintWriter;
51 import java.time.Clock;
52 import java.time.Duration;
53 import java.util.ArrayList;
54 import java.util.Calendar;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.function.Function;
59 import java.util.function.Supplier;
60 import java.util.stream.Collectors;
61 
62 /** A utility class to operate battery usage database. */
63 public final class DatabaseUtils {
64     private static final String TAG = "DatabaseUtils";
65     private static final String SHARED_PREFS_FILE = "battery_usage_shared_prefs";
66 
67     /** Clear memory threshold for device booting phase. **/
68     private static final long CLEAR_MEMORY_THRESHOLD_MS = Duration.ofMinutes(5).toMillis();
69     private static final long CLEAR_MEMORY_DELAYED_MS = Duration.ofSeconds(2).toMillis();
70     private static final long INVALID_TIMESTAMP = 0L;
71 
72     static final int DATA_RETENTION_INTERVAL_DAY = 9;
73     static final String KEY_LAST_LOAD_FULL_CHARGE_TIME = "last_load_full_charge_time";
74     static final String KEY_LAST_UPLOAD_FULL_CHARGE_TIME = "last_upload_full_charge_time";
75     static final String KEY_LAST_USAGE_SOURCE = "last_usage_source";
76     static final String KEY_DISMISSED_POWER_ANOMALY_KEYS = "dismissed_power_anomaly_keys";
77 
78     /** An authority name of the battery content provider. */
79     public static final String AUTHORITY = "com.android.settings.battery.usage.provider";
80     /** A table name for app usage events. */
81     public static final String APP_USAGE_EVENT_TABLE = "AppUsageEvent";
82     /** A table name for battery events. */
83     public static final String BATTERY_EVENT_TABLE = "BatteryEvent";
84     /** A table name for battery usage history. */
85     public static final String BATTERY_STATE_TABLE = "BatteryState";
86     /** A table name for battery usage slot. */
87     public static final String BATTERY_USAGE_SLOT_TABLE = "BatteryUsageSlot";
88     /** A path name for last full charge time query. */
89     public static final String LAST_FULL_CHARGE_TIMESTAMP_PATH = "lastFullChargeTimestamp";
90     /** A path name for querying the latest record timestamp in battery state table. */
91     public static final String BATTERY_STATE_LATEST_TIMESTAMP_PATH = "batteryStateLatestTimestamp";
92     /** A path name for app usage latest timestamp query. */
93     public static final String APP_USAGE_LATEST_TIMESTAMP_PATH = "appUsageLatestTimestamp";
94     /** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/
95     public static final String QUERY_KEY_TIMESTAMP = "timestamp";
96     /** Key for query parameter userid used in APP_USAGE_EVENT_URI **/
97     public static final String QUERY_KEY_USERID = "userid";
98     /** Key for query parameter battery event type used in BATTERY_EVENT_URI **/
99     public static final String QUERY_BATTERY_EVENT_TYPE = "batteryEventType";
100 
101     public static final long INVALID_USER_ID = Integer.MIN_VALUE;
102     /**
103      * The buffer hours to query app usage events that may have begun or ended out of the final
104      * desired time frame.
105      */
106     public static final long USAGE_QUERY_BUFFER_HOURS = Duration.ofHours(3).toMillis();
107 
108     /** A content URI to access app usage events data. */
109     public static final Uri APP_USAGE_EVENT_URI =
110             new Uri.Builder()
111                     .scheme(ContentResolver.SCHEME_CONTENT)
112                     .authority(AUTHORITY)
113                     .appendPath(APP_USAGE_EVENT_TABLE)
114                     .build();
115     /** A content URI to access battery events data. */
116     public static final Uri BATTERY_EVENT_URI =
117             new Uri.Builder()
118                     .scheme(ContentResolver.SCHEME_CONTENT)
119                     .authority(AUTHORITY)
120                     .appendPath(BATTERY_EVENT_TABLE)
121                     .build();
122     /** A content URI to access battery usage states data. */
123     public static final Uri BATTERY_CONTENT_URI =
124             new Uri.Builder()
125                     .scheme(ContentResolver.SCHEME_CONTENT)
126                     .authority(AUTHORITY)
127                     .appendPath(BATTERY_STATE_TABLE)
128                     .build();
129     /** A content URI to access battery usage slots data. */
130     public static final Uri BATTERY_USAGE_SLOT_URI =
131             new Uri.Builder()
132                     .scheme(ContentResolver.SCHEME_CONTENT)
133                     .authority(AUTHORITY)
134                     .appendPath(BATTERY_USAGE_SLOT_TABLE)
135                     .build();
136 
137     // For testing only.
138     @VisibleForTesting
139     static Supplier<Cursor> sFakeSupplier;
140 
DatabaseUtils()141     private DatabaseUtils() {
142     }
143 
144     /** Returns true if current user is a work profile user. */
isWorkProfile(Context context)145     public static boolean isWorkProfile(Context context) {
146         final UserManager userManager = context.getSystemService(UserManager.class);
147         return userManager.isManagedProfile();
148     }
149 
150     /** Returns the latest timestamp current user data in app usage event table. */
getAppUsageStartTimestampOfUser( Context context, final long userId, final long earliestTimestamp)151     public static long getAppUsageStartTimestampOfUser(
152             Context context, final long userId, final long earliestTimestamp) {
153         final long startTime = System.currentTimeMillis();
154         // Builds the content uri everytime to avoid cache.
155         final Uri appUsageLatestTimestampUri =
156                 new Uri.Builder()
157                         .scheme(ContentResolver.SCHEME_CONTENT)
158                         .authority(AUTHORITY)
159                         .appendPath(APP_USAGE_LATEST_TIMESTAMP_PATH)
160                         .appendQueryParameter(
161                                 QUERY_KEY_USERID, Long.toString(userId))
162                         .build();
163         final long latestTimestamp = loadLongFromContentProvider(
164                 context, appUsageLatestTimestampUri, /*defaultValue=*/ INVALID_TIMESTAMP);
165         final String latestTimestampString = utcToLocalTimeForLogging(latestTimestamp);
166         Log.d(TAG, String.format(
167                 "getAppUsageStartTimestampOfUser() userId=%d latestTimestamp=%s in %d/ms",
168                 userId, latestTimestampString, (System.currentTimeMillis() - startTime)));
169         // Use (latestTimestamp + 1) here to avoid loading the events of the latestTimestamp
170         // repeatedly.
171         return Math.max(latestTimestamp + 1, earliestTimestamp);
172     }
173 
174     /** Returns the current user data in app usage event table. */
getAppUsageEventForUsers( Context context, final Calendar calendar, final List<Integer> userIds, final long rawStartTimestamp)175     public static List<AppUsageEvent> getAppUsageEventForUsers(
176             Context context,
177             final Calendar calendar,
178             final List<Integer> userIds,
179             final long rawStartTimestamp) {
180         final long startTime = System.currentTimeMillis();
181         final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
182         // Query a longer time period and then trim to the original time period in order to make
183         // sure the app usage calculation near the boundaries is correct.
184         final long queryTimestamp =
185                 Math.max(rawStartTimestamp, sixDaysAgoTimestamp) - USAGE_QUERY_BUFFER_HOURS;
186         Log.d(TAG, "sixDaysAgoTimestamp: " + utcToLocalTimeForLogging(sixDaysAgoTimestamp));
187         final String queryUserIdString = userIds.stream()
188                 .map(userId -> String.valueOf(userId))
189                 .collect(Collectors.joining(","));
190         // Builds the content uri everytime to avoid cache.
191         final Uri appUsageEventUri =
192                 new Uri.Builder()
193                         .scheme(ContentResolver.SCHEME_CONTENT)
194                         .authority(AUTHORITY)
195                         .appendPath(APP_USAGE_EVENT_TABLE)
196                         .appendQueryParameter(
197                                 QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
198                         .appendQueryParameter(QUERY_KEY_USERID, queryUserIdString)
199                         .build();
200 
201         final List<AppUsageEvent> appUsageEventList = loadListFromContentProvider(
202                 context, appUsageEventUri, ConvertUtils::convertToAppUsageEvent);
203         Log.d(TAG, String.format("getAppUsageEventForUser userId=%s size=%d in %d/ms",
204                 queryUserIdString, appUsageEventList.size(),
205                 (System.currentTimeMillis() - startTime)));
206         return appUsageEventList;
207     }
208 
209     /** Returns the battery event data since the query timestamp in battery event table. */
getBatteryEvents( Context context, final Calendar calendar, final long rawStartTimestamp, final List<BatteryEventType> queryBatteryEventTypes)210     public static List<BatteryEvent> getBatteryEvents(
211             Context context,
212             final Calendar calendar,
213             final long rawStartTimestamp,
214             final List<BatteryEventType> queryBatteryEventTypes) {
215         final long startTime = System.currentTimeMillis();
216         final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
217         final long queryTimestamp = Math.max(rawStartTimestamp, sixDaysAgoTimestamp);
218         Log.d(TAG, "getBatteryEvents for timestamp: " + queryTimestamp);
219         final String queryBatteryEventTypesString = queryBatteryEventTypes.stream()
220                 .map(type -> String.valueOf(type.getNumber()))
221                 .collect(Collectors.joining(","));
222         // Builds the content uri everytime to avoid cache.
223         final Uri batteryEventUri =
224                 new Uri.Builder()
225                         .scheme(ContentResolver.SCHEME_CONTENT)
226                         .authority(AUTHORITY)
227                         .appendPath(BATTERY_EVENT_TABLE)
228                         .appendQueryParameter(
229                                 QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
230                         .appendQueryParameter(
231                                 QUERY_BATTERY_EVENT_TYPE, queryBatteryEventTypesString)
232                         .build();
233 
234         final List<BatteryEvent> batteryEventList = loadListFromContentProvider(
235                 context, batteryEventUri, ConvertUtils::convertToBatteryEvent);
236         Log.d(TAG, String.format("getBatteryEvents size=%d in %d/ms", batteryEventList.size(),
237                 (System.currentTimeMillis() - startTime)));
238         return batteryEventList;
239     }
240 
241     /**
242      * Returns the battery usage slot data after {@code rawStartTimestamp} in battery event table.
243      */
getBatteryUsageSlots( Context context, final Calendar calendar, final long rawStartTimestamp)244     public static List<BatteryUsageSlot> getBatteryUsageSlots(
245             Context context,
246             final Calendar calendar,
247             final long rawStartTimestamp) {
248         final long startTime = System.currentTimeMillis();
249         final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
250         final long queryTimestamp = Math.max(rawStartTimestamp, sixDaysAgoTimestamp);
251         Log.d(TAG, "getBatteryUsageSlots for timestamp: " + queryTimestamp);
252         // Builds the content uri everytime to avoid cache.
253         final Uri batteryUsageSlotUri =
254                 new Uri.Builder()
255                         .scheme(ContentResolver.SCHEME_CONTENT)
256                         .authority(AUTHORITY)
257                         .appendPath(BATTERY_USAGE_SLOT_TABLE)
258                         .appendQueryParameter(
259                                 QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
260                         .build();
261 
262         final List<BatteryUsageSlot> batteryUsageSlotList = loadListFromContentProvider(
263                 context, batteryUsageSlotUri, ConvertUtils::convertToBatteryUsageSlot);
264         Log.d(TAG, String.format("getBatteryUsageSlots size=%d in %d/ms",
265                 batteryUsageSlotList.size(), (System.currentTimeMillis() - startTime)));
266         return batteryUsageSlotList;
267     }
268 
269     /** Returns the last full charge time. */
getLastFullChargeTime(Context context)270     public static long getLastFullChargeTime(Context context) {
271         final long startTime = System.currentTimeMillis();
272         // Builds the content uri everytime to avoid cache.
273         final Uri lastFullChargeTimeUri =
274                 new Uri.Builder()
275                         .scheme(ContentResolver.SCHEME_CONTENT)
276                         .authority(AUTHORITY)
277                         .appendPath(LAST_FULL_CHARGE_TIMESTAMP_PATH)
278                         .build();
279         final long lastFullChargeTime = loadLongFromContentProvider(
280                 context, lastFullChargeTimeUri, /*defaultValue=*/ INVALID_TIMESTAMP);
281         final String lastFullChargeTimeString = utcToLocalTimeForLogging(lastFullChargeTime);
282         Log.d(TAG, String.format(
283                 "getLastFullChargeTime() lastFullChargeTime=%s in %d/ms",
284                 lastFullChargeTimeString, (System.currentTimeMillis() - startTime)));
285         return lastFullChargeTime;
286     }
287 
288     /** Returns the first battery state timestamp no later than the {@code queryTimestamp}. */
289     @VisibleForTesting
getBatteryStateLatestTimestampBeforeQueryTimestamp( Context context, final long queryTimestamp)290     static long getBatteryStateLatestTimestampBeforeQueryTimestamp(
291             Context context, final long queryTimestamp) {
292         final long startTime = System.currentTimeMillis();
293         // Builds the content uri everytime to avoid cache.
294         final Uri batteryStateLatestTimestampUri =
295                 new Uri.Builder()
296                         .scheme(ContentResolver.SCHEME_CONTENT)
297                         .authority(AUTHORITY)
298                         .appendPath(BATTERY_STATE_LATEST_TIMESTAMP_PATH)
299                         .appendQueryParameter(
300                                 QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
301                         .build();
302         final long batteryStateLatestTimestamp = loadLongFromContentProvider(
303                 context, batteryStateLatestTimestampUri, /*defaultValue=*/ INVALID_TIMESTAMP);
304         final String batteryStateLatestTimestampString =
305                 utcToLocalTimeForLogging(batteryStateLatestTimestamp);
306         Log.d(TAG, String.format(
307                 "getBatteryStateLatestTimestamp() batteryStateLatestTimestamp=%s in %d/ms",
308                 batteryStateLatestTimestampString, (System.currentTimeMillis() - startTime)));
309         return batteryStateLatestTimestamp;
310     }
311 
312     /** Returns the battery history map after the given timestamp. */
313     @VisibleForTesting
getHistoryMapSinceQueryTimestamp( Context context, final long queryTimestamp)314     static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceQueryTimestamp(
315             Context context, final long queryTimestamp) {
316         final long startTime = System.currentTimeMillis();
317         // Builds the content uri everytime to avoid cache.
318         final Uri batteryStateUri =
319                 new Uri.Builder()
320                         .scheme(ContentResolver.SCHEME_CONTENT)
321                         .authority(AUTHORITY)
322                         .appendPath(BATTERY_STATE_TABLE)
323                         .appendQueryParameter(
324                                 QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
325                         .build();
326 
327         final List<BatteryHistEntry> batteryHistEntryList = loadListFromContentProvider(
328                 context, batteryStateUri, cursor -> new BatteryHistEntry(cursor));
329         final Map<Long, Map<String, BatteryHistEntry>> resultMap = new ArrayMap();
330         for (final BatteryHistEntry entry : batteryHistEntryList) {
331             final long timestamp = entry.mTimestamp;
332             final String key = entry.getKey();
333             Map batteryHistEntryMap = resultMap.get(timestamp);
334             // Creates new one if there is no corresponding map.
335             if (batteryHistEntryMap == null) {
336                 batteryHistEntryMap = new ArrayMap();
337                 resultMap.put(timestamp, batteryHistEntryMap);
338             }
339             batteryHistEntryMap.put(key, entry);
340         }
341 
342         if (resultMap == null || resultMap.isEmpty()) {
343             Log.d(TAG, "getBatteryHistoryMap() returns empty or null");
344         } else {
345             Log.d(TAG, String.format("getBatteryHistoryMap() size=%d in %d/ms",
346                     resultMap.size(), (System.currentTimeMillis() - startTime)));
347         }
348         return resultMap;
349     }
350 
351     /**
352      * Returns the battery history map since the latest record no later than the given timestamp.
353      * If there is no record before the given timestamp or the given timestamp is before last full
354      * charge time, returns the history map since last full charge time.
355      */
356     public static Map<Long, Map<String, BatteryHistEntry>>
getHistoryMapSinceLatestRecordBeforeQueryTimestamp(Context context, Calendar calendar, final long queryTimestamp, final long lastFullChargeTime)357             getHistoryMapSinceLatestRecordBeforeQueryTimestamp(Context context, Calendar calendar,
358                     final long queryTimestamp, final long lastFullChargeTime) {
359         final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
360         Log.d(TAG, "sixDaysAgoTimestamp: " + utcToLocalTimeForLogging(sixDaysAgoTimestamp));
361         final long batteryStateLatestTimestamp =
362                 queryTimestamp == 0L ? 0L : getBatteryStateLatestTimestampBeforeQueryTimestamp(
363                         context, queryTimestamp);
364         final long maxTimestamp = Math.max(Math.max(
365                 sixDaysAgoTimestamp, lastFullChargeTime), batteryStateLatestTimestamp);
366         return getHistoryMapSinceQueryTimestamp(context, maxTimestamp);
367     }
368 
369     /** Returns the history map since last full charge time. */
getHistoryMapSinceLastFullCharge( Context context, Calendar calendar)370     public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
371             Context context, Calendar calendar) {
372         final long lastFullChargeTime = getLastFullChargeTime(context);
373         return getHistoryMapSinceLatestRecordBeforeQueryTimestamp(
374                 context, calendar, 0, lastFullChargeTime);
375     }
376 
377     /** Clears all data in the battery usage database. */
clearAll(Context context)378     public static void clearAll(Context context) {
379         AsyncTask.execute(() -> {
380             try {
381                 final BatteryStateDatabase database = BatteryStateDatabase
382                         .getInstance(context.getApplicationContext());
383                 database.appUsageEventDao().clearAll();
384                 database.batteryEventDao().clearAll();
385                 database.batteryStateDao().clearAll();
386                 database.batteryUsageSlotDao().clearAll();
387             } catch (RuntimeException e) {
388                 Log.e(TAG, "clearAll() failed", e);
389             }
390         });
391     }
392 
393     /** Clears all out-of-date data in the battery usage database. */
clearExpiredDataIfNeeded(Context context)394     public static void clearExpiredDataIfNeeded(Context context) {
395         AsyncTask.execute(() -> {
396             try {
397                 final BatteryStateDatabase database = BatteryStateDatabase
398                         .getInstance(context.getApplicationContext());
399                 final long earliestTimestamp = Clock.systemUTC().millis()
400                         - Duration.ofDays(DATA_RETENTION_INTERVAL_DAY).toMillis();
401                 database.appUsageEventDao().clearAllBefore(earliestTimestamp);
402                 database.batteryEventDao().clearAllBefore(earliestTimestamp);
403                 database.batteryStateDao().clearAllBefore(earliestTimestamp);
404                 database.batteryUsageSlotDao().clearAllBefore(earliestTimestamp);
405             } catch (RuntimeException e) {
406                 Log.e(TAG, "clearAllBefore() failed", e);
407             }
408         });
409     }
410 
411     /** Returns the timestamp for 00:00 6 days before the calendar date. */
getTimestampSixDaysAgo(Calendar calendar)412     public static long getTimestampSixDaysAgo(Calendar calendar) {
413         Calendar startCalendar =
414                 calendar == null ? Calendar.getInstance() : (Calendar) calendar.clone();
415         startCalendar.add(Calendar.DAY_OF_YEAR, -6);
416         startCalendar.set(Calendar.HOUR_OF_DAY, 0);
417         startCalendar.set(Calendar.MINUTE, 0);
418         startCalendar.set(Calendar.SECOND, 0);
419         startCalendar.set(Calendar.MILLISECOND, 0);
420         return startCalendar.getTimeInMillis();
421     }
422 
423     /** Returns the context with profile parent identity when current user is work profile. */
getParentContext(Context context)424     public static Context getParentContext(Context context) {
425         if (isWorkProfile(context)) {
426             try {
427                 return context.createPackageContextAsUser(
428                         /*packageName=*/ context.getPackageName(),
429                         /*flags=*/ 0,
430                         /*user=*/ context.getSystemService(UserManager.class)
431                                 .getProfileParent(context.getUser()));
432             } catch (PackageManager.NameNotFoundException e) {
433                 Log.e(TAG, "context.createPackageContextAsUser() fail:", e);
434                 return null;
435             }
436         }
437         return context;
438     }
439 
sendAppUsageEventData( final Context context, final List<AppUsageEvent> appUsageEventList)440     static List<ContentValues> sendAppUsageEventData(
441             final Context context, final List<AppUsageEvent> appUsageEventList) {
442         final long startTime = System.currentTimeMillis();
443         // Creates the ContentValues list to insert them into provider.
444         final List<ContentValues> valuesList = new ArrayList<>();
445         appUsageEventList.stream()
446                 .filter(appUsageEvent -> appUsageEvent.hasUid())
447                 .forEach(appUsageEvent -> valuesList.add(
448                         ConvertUtils.convertAppUsageEventToContentValues(appUsageEvent)));
449         int size = 0;
450         final ContentResolver resolver = context.getContentResolver();
451         // Inserts all ContentValues into battery provider.
452         if (!valuesList.isEmpty()) {
453             final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
454             valuesList.toArray(valuesArray);
455             try {
456                 size = resolver.bulkInsert(APP_USAGE_EVENT_URI, valuesArray);
457                 resolver.notifyChange(APP_USAGE_EVENT_URI, /*observer=*/ null);
458                 Log.d(TAG, "insert() app usage events data into database");
459             } catch (Exception e) {
460                 Log.e(TAG, "bulkInsert() app usage data into database error:", e);
461             }
462         }
463         Log.d(TAG, String.format("sendAppUsageEventData() size=%d in %d/ms",
464                 size, (System.currentTimeMillis() - startTime)));
465         clearMemory();
466         return valuesList;
467     }
468 
sendBatteryEventData( final Context context, final BatteryEvent batteryEvent)469     static ContentValues sendBatteryEventData(
470             final Context context, final BatteryEvent batteryEvent) {
471         final long startTime = System.currentTimeMillis();
472         ContentValues contentValues = ConvertUtils.convertBatteryEventToContentValues(batteryEvent);
473         final ContentResolver resolver = context.getContentResolver();
474         try {
475             resolver.insert(BATTERY_EVENT_URI, contentValues);
476             Log.d(TAG, "insert() battery event data into database: " + batteryEvent.toString());
477         } catch (Exception e) {
478             Log.e(TAG, "insert() battery event data into database error:", e);
479         }
480         Log.d(TAG, String.format("sendBatteryEventData() in %d/ms",
481                 (System.currentTimeMillis() - startTime)));
482         clearMemory();
483         return contentValues;
484     }
485 
sendBatteryEventData( final Context context, final List<BatteryEvent> batteryEventList)486     static List<ContentValues> sendBatteryEventData(
487             final Context context, final List<BatteryEvent> batteryEventList) {
488         final long startTime = System.currentTimeMillis();
489         // Creates the ContentValues list to insert them into provider.
490         final List<ContentValues> valuesList = new ArrayList<>();
491         batteryEventList.stream()
492                 .forEach(batteryEvent -> valuesList.add(
493                         ConvertUtils.convertBatteryEventToContentValues(batteryEvent)));
494         int size = 0;
495         final ContentResolver resolver = context.getContentResolver();
496         // Inserts all ContentValues into battery provider.
497         if (!valuesList.isEmpty()) {
498             final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
499             valuesList.toArray(valuesArray);
500             try {
501                 size = resolver.bulkInsert(BATTERY_EVENT_URI, valuesArray);
502                 resolver.notifyChange(BATTERY_EVENT_URI, /*observer=*/ null);
503                 Log.d(TAG, "insert() battery event data into database");
504             } catch (Exception e) {
505                 Log.e(TAG, "bulkInsert() battery event data into database error:", e);
506             }
507         }
508         Log.d(TAG, String.format("sendBatteryEventData() size=%d in %d/ms",
509                 size, (System.currentTimeMillis() - startTime)));
510         clearMemory();
511         return valuesList;
512     }
513 
sendBatteryUsageSlotData( final Context context, final List<BatteryUsageSlot> batteryUsageSlotList)514     static List<ContentValues> sendBatteryUsageSlotData(
515             final Context context, final List<BatteryUsageSlot> batteryUsageSlotList) {
516         final long startTime = System.currentTimeMillis();
517         // Creates the ContentValues list to insert them into provider.
518         final List<ContentValues> valuesList = new ArrayList<>();
519         batteryUsageSlotList.stream()
520                 .forEach(batteryUsageSlot -> valuesList.add(
521                         ConvertUtils.convertBatteryUsageSlotToContentValues(batteryUsageSlot)));
522         int size = 0;
523         final ContentResolver resolver = context.getContentResolver();
524         // Inserts all ContentValues into battery provider.
525         if (!valuesList.isEmpty()) {
526             final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
527             valuesList.toArray(valuesArray);
528             try {
529                 size = resolver.bulkInsert(BATTERY_USAGE_SLOT_URI, valuesArray);
530                 resolver.notifyChange(BATTERY_USAGE_SLOT_URI, /*observer=*/ null);
531                 Log.d(TAG, "insert() battery usage slots data into database");
532             } catch (Exception e) {
533                 Log.e(TAG, "bulkInsert() battery usage slots data into database error:", e);
534             }
535         }
536         Log.d(TAG, String.format("sendBatteryUsageSlotData() size=%d in %d/ms",
537                 size, (System.currentTimeMillis() - startTime)));
538         clearMemory();
539         return valuesList;
540     }
541 
sendBatteryEntryData( final Context context, final long snapshotTimestamp, final List<BatteryEntry> batteryEntryList, final BatteryUsageStats batteryUsageStats, final boolean isFullChargeStart)542     static List<ContentValues> sendBatteryEntryData(
543             final Context context,
544             final long snapshotTimestamp,
545             final List<BatteryEntry> batteryEntryList,
546             final BatteryUsageStats batteryUsageStats,
547             final boolean isFullChargeStart) {
548         final long startTime = System.currentTimeMillis();
549         final Intent intent = BatteryUtils.getBatteryIntent(context);
550         if (intent == null) {
551             Log.e(TAG, "sendBatteryEntryData(): cannot fetch battery intent");
552             clearMemory();
553             return null;
554         }
555         final int batteryLevel = BatteryStatus.getBatteryLevel(intent);
556         final int batteryStatus = intent.getIntExtra(
557                 BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
558         final int batteryHealth = intent.getIntExtra(
559                 BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
560         // We should use the same timestamp for each data snapshot.
561         final long snapshotBootTimestamp = SystemClock.elapsedRealtime();
562 
563         // Creates the ContentValues list to insert them into provider.
564         final List<ContentValues> valuesList = new ArrayList<>();
565         if (batteryEntryList != null) {
566             batteryEntryList.stream()
567                     .filter(entry -> {
568                         final long foregroundMs = entry.getTimeInForegroundMs();
569                         final long backgroundMs = entry.getTimeInBackgroundMs();
570                         if (entry.getConsumedPower() == 0
571                                 && (foregroundMs != 0
572                                 || backgroundMs != 0)) {
573                             Log.w(TAG, String.format(
574                                     "no consumed power but has running time for %s time=%d|%d",
575                                     entry.getLabel(), foregroundMs, backgroundMs));
576                         }
577                         return entry.getConsumedPower() != 0
578                                 || foregroundMs != 0
579                                 || backgroundMs != 0;
580                     })
581                     .forEach(entry -> valuesList.add(
582                             ConvertUtils.convertBatteryEntryToContentValues(
583                                     entry,
584                                     batteryUsageStats,
585                                     batteryLevel,
586                                     batteryStatus,
587                                     batteryHealth,
588                                     snapshotBootTimestamp,
589                                     snapshotTimestamp,
590                                     isFullChargeStart)));
591         }
592 
593         int size = 1;
594         final ContentResolver resolver = context.getContentResolver();
595         String errorMessage = "";
596         // Inserts all ContentValues into battery provider.
597         if (!valuesList.isEmpty()) {
598             final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
599             valuesList.toArray(valuesArray);
600             try {
601                 size = resolver.bulkInsert(BATTERY_CONTENT_URI, valuesArray);
602                 Log.d(TAG, "insert() battery states data into database with isFullChargeStart:"
603                         + isFullChargeStart);
604             } catch (Exception e) {
605                 Log.e(TAG, "bulkInsert() data into database error:", e);
606             }
607         } else {
608             // Inserts one fake data into battery provider.
609             final ContentValues contentValues =
610                     ConvertUtils.convertBatteryEntryToContentValues(
611                             /*entry=*/ null,
612                             /*batteryUsageStats=*/ null,
613                             batteryLevel,
614                             batteryStatus,
615                             batteryHealth,
616                             snapshotBootTimestamp,
617                             snapshotTimestamp,
618                             isFullChargeStart);
619             try {
620                 resolver.insert(BATTERY_CONTENT_URI, contentValues);
621                 Log.d(TAG, "insert() data into database with isFullChargeStart:"
622                         + isFullChargeStart);
623 
624             } catch (Exception e) {
625                 Log.e(TAG, "insert() data into database error:", e);
626             }
627             valuesList.add(contentValues);
628         }
629         resolver.notifyChange(BATTERY_CONTENT_URI, /*observer=*/ null);
630         BatteryUsageLogUtils.writeLog(
631                 context,
632                 Action.INSERT_USAGE_DATA,
633                 "size=" + size + " " + errorMessage);
634         Log.d(TAG, String.format("sendBatteryEntryData() size=%d in %d/ms",
635                 size, (System.currentTimeMillis() - startTime)));
636         if (isFullChargeStart) {
637             recordDateTime(context, KEY_LAST_UPLOAD_FULL_CHARGE_TIME);
638         }
639         clearMemory();
640         return valuesList;
641     }
642 
643     /** Dump all required data into {@link PrintWriter}. */
dump(Context context, PrintWriter writer)644     public static void dump(Context context, PrintWriter writer) {
645         writeString(context, writer, "BatteryLevelChanged",
646                 Intent.ACTION_BATTERY_LEVEL_CHANGED);
647         writeString(context, writer, "BatteryPlugging",
648                 BatteryUsageBroadcastReceiver.ACTION_BATTERY_PLUGGING);
649         writeString(context, writer, "BatteryUnplugging",
650                 BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING);
651         writeString(context, writer, "ClearBatteryCacheData",
652                 BatteryUsageBroadcastReceiver.ACTION_CLEAR_BATTERY_CACHE_DATA);
653         writeString(context, writer, "LastLoadFullChargeTime",
654                 KEY_LAST_LOAD_FULL_CHARGE_TIME);
655         writeString(context, writer, "LastUploadFullChargeTime",
656                 KEY_LAST_UPLOAD_FULL_CHARGE_TIME);
657         writeString(context, writer, "DismissedPowerAnomalyKeys",
658                 KEY_DISMISSED_POWER_ANOMALY_KEYS);
659     }
660 
getSharedPreferences(Context context)661     static SharedPreferences getSharedPreferences(Context context) {
662         return context.getApplicationContext().getSharedPreferences(
663                 SHARED_PREFS_FILE, Context.MODE_PRIVATE);
664     }
665 
removeUsageSource(Context context)666     static void removeUsageSource(Context context) {
667         final SharedPreferences sharedPreferences = getSharedPreferences(context);
668         if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) {
669             sharedPreferences.edit().remove(KEY_LAST_USAGE_SOURCE).apply();
670         }
671     }
672 
673     /**
674      * Returns what App Usage Observers will consider the source of usage for an activity.
675      *
676      * @see UsageStatsManager#getUsageSource()
677      */
getUsageSource(Context context, IUsageStatsManager usageStatsManager)678     static int getUsageSource(Context context, IUsageStatsManager usageStatsManager) {
679         final SharedPreferences sharedPreferences = getSharedPreferences(context);
680         if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) {
681             return sharedPreferences
682                     .getInt(KEY_LAST_USAGE_SOURCE, ConvertUtils.DEFAULT_USAGE_SOURCE);
683         }
684         int usageSource = ConvertUtils.DEFAULT_USAGE_SOURCE;
685 
686         try {
687             usageSource = usageStatsManager.getUsageSource();
688         } catch (RemoteException e) {
689             Log.e(TAG, "Failed to getUsageSource", e);
690         }
691         if (sharedPreferences != null) {
692             sharedPreferences.edit().putInt(KEY_LAST_USAGE_SOURCE, usageSource).apply();
693         }
694         return usageSource;
695     }
696 
removeDismissedPowerAnomalyKeys(Context context)697     static void removeDismissedPowerAnomalyKeys(Context context) {
698         final SharedPreferences sharedPreferences = getSharedPreferences(context);
699         if (sharedPreferences != null
700                 && sharedPreferences.contains(KEY_DISMISSED_POWER_ANOMALY_KEYS)) {
701             sharedPreferences.edit().remove(KEY_DISMISSED_POWER_ANOMALY_KEYS).apply();
702         }
703     }
704 
getDismissedPowerAnomalyKeys(Context context)705     static Set<String> getDismissedPowerAnomalyKeys(Context context) {
706         final SharedPreferences sharedPreferences = getSharedPreferences(context);
707         return sharedPreferences != null
708                 ? sharedPreferences.getStringSet(KEY_DISMISSED_POWER_ANOMALY_KEYS, new ArraySet<>())
709                 : new ArraySet<>();
710     }
711 
setDismissedPowerAnomalyKeys(Context context, String dismissedPowerAnomalyKey)712     static void setDismissedPowerAnomalyKeys(Context context, String dismissedPowerAnomalyKey) {
713         final SharedPreferences sharedPreferences = getSharedPreferences(context);
714         if (sharedPreferences != null) {
715             final Set<String> dismissedPowerAnomalyKeys = getDismissedPowerAnomalyKeys(context);
716             dismissedPowerAnomalyKeys.add(dismissedPowerAnomalyKey);
717             sharedPreferences.edit()
718                     .putStringSet(KEY_DISMISSED_POWER_ANOMALY_KEYS, dismissedPowerAnomalyKeys)
719                     .apply();
720         }
721     }
722 
recordDateTime(Context context, String preferenceKey)723     static void recordDateTime(Context context, String preferenceKey) {
724         final SharedPreferences sharedPreferences = getSharedPreferences(context);
725         if (sharedPreferences != null) {
726             final String currentTime = utcToLocalTimeForLogging(System.currentTimeMillis());
727             sharedPreferences.edit().putString(preferenceKey, currentTime).apply();
728         }
729     }
730 
731     @VisibleForTesting
loadFromContentProvider( Context context, Uri uri, T defaultValue, Function<Cursor, T> cursorReader)732     static <T> T loadFromContentProvider(
733             Context context, Uri uri, T defaultValue, Function<Cursor, T> cursorReader) {
734         // Transfer work profile to user profile. Please see b/297036263.
735         context = getParentContext(context);
736         if (context == null) {
737             return defaultValue;
738         }
739         try (Cursor cursor = sFakeSupplier != null ? sFakeSupplier.get() :
740                 context.getContentResolver().query(uri, null, null, null)) {
741             return (cursor == null || cursor.getCount() == 0)
742                     ? defaultValue : cursorReader.apply(cursor);
743         }
744     }
745 
loadLongFromContentProvider( Context context, Uri uri, final long defaultValue)746     private static long loadLongFromContentProvider(
747             Context context, Uri uri, final long defaultValue) {
748         return loadFromContentProvider(context, uri, defaultValue,
749                 cursor -> cursor.moveToFirst() ? cursor.getLong(/*columnIndex=*/ 0) : defaultValue);
750     }
751 
loadListFromContentProvider( Context context, Uri uri, Function<Cursor, E> converter)752     private static <E> List<E> loadListFromContentProvider(
753             Context context, Uri uri, Function<Cursor, E> converter) {
754         return loadFromContentProvider(context, uri, new ArrayList<>(),
755                 cursor -> {
756                     final List<E> list = new ArrayList<>();
757                     while (cursor.moveToNext()) {
758                         list.add(converter.apply(cursor));
759                     }
760                     return list;
761                 });
762     }
763 
writeString( Context context, PrintWriter writer, String prefix, String key)764     private static void writeString(
765             Context context, PrintWriter writer, String prefix, String key) {
766         final SharedPreferences sharedPreferences = getSharedPreferences(context);
767         if (sharedPreferences != null) {
768             final String content = sharedPreferences.getString(key, "");
769             writer.println(String.format("\t\t%s: %s", prefix, content));
770         }
771     }
772 
clearMemory()773     private static void clearMemory() {
774         if (SystemClock.uptimeMillis() > CLEAR_MEMORY_THRESHOLD_MS) {
775             return;
776         }
777         final Handler mainHandler = new Handler(Looper.getMainLooper());
778         mainHandler.postDelayed(() -> {
779             System.gc();
780             System.runFinalization();
781             System.gc();
782             Log.w(TAG, "invoke clearMemory()");
783         }, CLEAR_MEMORY_DELAYED_MS);
784     }
785 }
786