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