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