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 package com.android.settings.fuelgauge.batteryusage;
17 
18 import android.annotation.IntDef;
19 import android.app.usage.IUsageStatsManager;
20 import android.app.usage.UsageEvents.Event;
21 import android.app.usage.UsageStatsManager;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.pm.PackageManager;
25 import android.database.Cursor;
26 import android.os.BatteryUsageStats;
27 import android.os.Build;
28 import android.os.LocaleList;
29 import android.os.UserHandle;
30 import android.text.TextUtils;
31 import android.text.format.DateFormat;
32 import android.util.Base64;
33 import android.util.Log;
34 
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 import androidx.annotation.VisibleForTesting;
38 
39 import com.android.settings.fuelgauge.BatteryUtils;
40 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
41 import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
42 import com.android.settings.fuelgauge.batteryusage.db.BatteryUsageSlotEntity;
43 
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Locale;
49 import java.util.Map;
50 import java.util.Set;
51 import java.util.TimeZone;
52 
53 /** A utility class to convert data into another types. */
54 public final class ConvertUtils {
55     private static final String TAG = "ConvertUtils";
56 
57     /** A fake package name to represent no BatteryEntry data. */
58     public static final String FAKE_PACKAGE_NAME = "fake_package";
59 
60     @IntDef(
61             prefix = {"CONSUMER_TYPE"},
62             value = {
63                 CONSUMER_TYPE_UNKNOWN,
64                 CONSUMER_TYPE_UID_BATTERY,
65                 CONSUMER_TYPE_USER_BATTERY,
66                 CONSUMER_TYPE_SYSTEM_BATTERY,
67             })
68     @Retention(RetentionPolicy.SOURCE)
69     public @interface ConsumerType {}
70 
71     public static final int CONSUMER_TYPE_UNKNOWN = 0;
72     public static final int CONSUMER_TYPE_UID_BATTERY = 1;
73     public static final int CONSUMER_TYPE_USER_BATTERY = 2;
74     public static final int CONSUMER_TYPE_SYSTEM_BATTERY = 3;
75 
76     public static final int DEFAULT_USAGE_SOURCE = UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY;
77     public static final int EMPTY_USAGE_SOURCE = -1;
78 
79     @VisibleForTesting static int sUsageSource = EMPTY_USAGE_SOURCE;
80 
ConvertUtils()81     private ConvertUtils() {}
82 
83     /** Whether {@code consumerType} is app consumer or not. */
isUidConsumer(final int consumerType)84     public static boolean isUidConsumer(final int consumerType) {
85         return consumerType == CONSUMER_TYPE_UID_BATTERY;
86     }
87 
88     /** Whether {@code consumerType} is user consumer or not. */
isUserConsumer(final int consumerType)89     public static boolean isUserConsumer(final int consumerType) {
90         return consumerType == CONSUMER_TYPE_USER_BATTERY;
91     }
92 
93     /** Whether {@code consumerType} is system consumer or not. */
isSystemConsumer(final int consumerType)94     public static boolean isSystemConsumer(final int consumerType) {
95         return consumerType == CONSUMER_TYPE_SYSTEM_BATTERY;
96     }
97 
98     /** Converts {@link BatteryEntry} to {@link ContentValues} */
convertBatteryEntryToContentValues( final BatteryEntry entry, final BatteryUsageStats batteryUsageStats, final int batteryLevel, final int batteryStatus, final int batteryHealth, final long bootTimestamp, final long timestamp, final boolean isFullChargeStart)99     public static ContentValues convertBatteryEntryToContentValues(
100             final BatteryEntry entry,
101             final BatteryUsageStats batteryUsageStats,
102             final int batteryLevel,
103             final int batteryStatus,
104             final int batteryHealth,
105             final long bootTimestamp,
106             final long timestamp,
107             final boolean isFullChargeStart) {
108         final ContentValues values = new ContentValues();
109         if (entry != null && batteryUsageStats != null) {
110             values.put(BatteryHistEntry.KEY_UID, Long.valueOf(entry.getUid()));
111             values.put(
112                     BatteryHistEntry.KEY_USER_ID,
113                     Long.valueOf(UserHandle.getUserId(entry.getUid())));
114             final String packageName = entry.getDefaultPackageName();
115             values.put(BatteryHistEntry.KEY_PACKAGE_NAME, packageName != null ? packageName : "");
116             values.put(
117                     BatteryHistEntry.KEY_CONSUMER_TYPE, Integer.valueOf(entry.getConsumerType()));
118         } else {
119             values.put(BatteryHistEntry.KEY_PACKAGE_NAME, FAKE_PACKAGE_NAME);
120         }
121         values.put(BatteryHistEntry.KEY_TIMESTAMP, Long.valueOf(timestamp));
122         values.put(
123                 BatteryHistEntry.KEY_IS_FULL_CHARGE_CYCLE_START,
124                 Boolean.valueOf(isFullChargeStart));
125         final BatteryInformation batteryInformation =
126                 constructBatteryInformation(
127                         entry,
128                         batteryUsageStats,
129                         batteryLevel,
130                         batteryStatus,
131                         batteryHealth,
132                         bootTimestamp);
133         values.put(
134                 BatteryHistEntry.KEY_BATTERY_INFORMATION,
135                 convertBatteryInformationToString(batteryInformation));
136         // Save the BatteryInformation unencoded string into database for debugging.
137         if (Build.TYPE.equals("userdebug")) {
138             values.put(
139                     BatteryHistEntry.KEY_BATTERY_INFORMATION_DEBUG, batteryInformation.toString());
140         }
141         return values;
142     }
143 
144     /** Converts {@link AppUsageEvent} to {@link ContentValues} */
convertAppUsageEventToContentValues(final AppUsageEvent event)145     public static ContentValues convertAppUsageEventToContentValues(final AppUsageEvent event) {
146         final ContentValues values = new ContentValues();
147         values.put(AppUsageEventEntity.KEY_UID, event.getUid());
148         values.put(AppUsageEventEntity.KEY_USER_ID, event.getUserId());
149         values.put(AppUsageEventEntity.KEY_TIMESTAMP, event.getTimestamp());
150         values.put(AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE, event.getType().getNumber());
151         values.put(AppUsageEventEntity.KEY_PACKAGE_NAME, event.getPackageName());
152         values.put(AppUsageEventEntity.KEY_INSTANCE_ID, event.getInstanceId());
153         values.put(AppUsageEventEntity.KEY_TASK_ROOT_PACKAGE_NAME, event.getTaskRootPackageName());
154         return values;
155     }
156 
157     /** Converts {@link BatteryEvent} to {@link ContentValues} */
convertBatteryEventToContentValues(final BatteryEvent event)158     public static ContentValues convertBatteryEventToContentValues(final BatteryEvent event) {
159         final ContentValues values = new ContentValues();
160         values.put(BatteryEventEntity.KEY_TIMESTAMP, event.getTimestamp());
161         values.put(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE, event.getType().getNumber());
162         values.put(BatteryEventEntity.KEY_BATTERY_LEVEL, event.getBatteryLevel());
163         return values;
164     }
165 
166     /** Converts {@link BatteryUsageSlot} to {@link ContentValues} */
convertBatteryUsageSlotToContentValues( final BatteryUsageSlot batteryUsageSlot)167     public static ContentValues convertBatteryUsageSlotToContentValues(
168             final BatteryUsageSlot batteryUsageSlot) {
169         final ContentValues values = new ContentValues(2);
170         values.put(BatteryUsageSlotEntity.KEY_TIMESTAMP, batteryUsageSlot.getStartTimestamp());
171         values.put(
172                 BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT,
173                 Base64.encodeToString(batteryUsageSlot.toByteArray(), Base64.DEFAULT));
174         return values;
175     }
176 
177     /** Gets the encoded string from {@link BatteryInformation} instance. */
convertBatteryInformationToString( final BatteryInformation batteryInformation)178     public static String convertBatteryInformationToString(
179             final BatteryInformation batteryInformation) {
180         return Base64.encodeToString(batteryInformation.toByteArray(), Base64.DEFAULT);
181     }
182 
183     /** Gets the {@link BatteryInformation} instance from {@link ContentValues}. */
getBatteryInformation( final ContentValues values, final String key)184     public static BatteryInformation getBatteryInformation(
185             final ContentValues values, final String key) {
186         final BatteryInformation defaultInstance = BatteryInformation.getDefaultInstance();
187         if (values != null && values.containsKey(key)) {
188             return BatteryUtils.parseProtoFromString(values.getAsString(key), defaultInstance);
189         }
190         return defaultInstance;
191     }
192 
193     /** Gets the {@link BatteryInformation} instance from {@link Cursor}. */
getBatteryInformation(final Cursor cursor, final String key)194     public static BatteryInformation getBatteryInformation(final Cursor cursor, final String key) {
195         final BatteryInformation defaultInstance = BatteryInformation.getDefaultInstance();
196         final int columnIndex = cursor.getColumnIndex(key);
197         if (columnIndex >= 0) {
198             return BatteryUtils.parseProtoFromString(
199                     cursor.getString(columnIndex), defaultInstance);
200         }
201         return defaultInstance;
202     }
203 
204     /** Gets the encoded string from {@link BatteryReattribute} instance. */
205     @NonNull
encodeBatteryReattribute( @onNull BatteryReattribute batteryReattribute)206     public static String encodeBatteryReattribute(
207             @NonNull BatteryReattribute batteryReattribute) {
208         return Base64.encodeToString(batteryReattribute.toByteArray(), Base64.DEFAULT);
209     }
210 
211     /** Gets the decoded {@link BatteryReattribute} instance from string. */
212     @NonNull
decodeBatteryReattribute(@onNull String content)213     public static BatteryReattribute decodeBatteryReattribute(@NonNull String content) {
214         return BatteryUtils.parseProtoFromString(
215                 content, BatteryReattribute.getDefaultInstance());
216     }
217 
218     /** Converts to {@link BatteryHistEntry} */
convertToBatteryHistEntry( BatteryEntry entry, BatteryUsageStats batteryUsageStats)219     public static BatteryHistEntry convertToBatteryHistEntry(
220             BatteryEntry entry, BatteryUsageStats batteryUsageStats) {
221         return new BatteryHistEntry(
222                 convertBatteryEntryToContentValues(
223                         entry,
224                         batteryUsageStats,
225                         /* batteryLevel= */ 0,
226                         /* batteryStatus= */ 0,
227                         /* batteryHealth= */ 0,
228                         /* bootTimestamp= */ 0,
229                         /* timestamp= */ 0,
230                         /* isFullChargeStart= */ false));
231     }
232 
233     /** Converts from {@link Event} to {@link AppUsageEvent} */
234     @Nullable
convertToAppUsageEvent( Context context, IUsageStatsManager usageStatsManager, final Event event, final long userId)235     public static AppUsageEvent convertToAppUsageEvent(
236             Context context,
237             IUsageStatsManager usageStatsManager,
238             final Event event,
239             final long userId) {
240         final String packageName = event.getPackageName();
241         if (packageName == null) {
242             // See b/190609174: Event package names should never be null, but sometimes they are.
243             // Note that system events like device shutting down should still come with the android
244             // package name.
245             Log.w(
246                     TAG,
247                     String.format(
248                             "Ignoring a usage event with null package name (timestamp=%d, type=%d)",
249                             event.getTimeStamp(), event.getEventType()));
250             return null;
251         }
252 
253         final AppUsageEvent.Builder appUsageEventBuilder = AppUsageEvent.newBuilder();
254         appUsageEventBuilder
255                 .setTimestamp(event.getTimeStamp())
256                 .setType(getAppUsageEventType(event.getEventType()))
257                 .setPackageName(packageName)
258                 .setUserId(userId);
259 
260         final String taskRootPackageName = getTaskRootPackageName(event);
261         if (taskRootPackageName != null) {
262             appUsageEventBuilder.setTaskRootPackageName(taskRootPackageName);
263         }
264 
265         final String effectivePackageName =
266                 getEffectivePackageName(
267                         context, usageStatsManager, packageName, taskRootPackageName);
268         try {
269             final long uid =
270                     context.getPackageManager()
271                             .getPackageUidAsUser(effectivePackageName, (int) userId);
272             appUsageEventBuilder.setUid(uid);
273         } catch (PackageManager.NameNotFoundException e) {
274             Log.w(
275                     TAG,
276                     String.format(
277                             "Fail to get uid for package %s of user %d)",
278                             event.getPackageName(), userId));
279             return null;
280         }
281 
282         try {
283             appUsageEventBuilder.setInstanceId(event.getInstanceId());
284         } catch (NoClassDefFoundError | NoSuchMethodError e) {
285             Log.w(TAG, "UsageEvent instance ID API error");
286         }
287 
288         return appUsageEventBuilder.build();
289     }
290 
291     /** Converts from {@link Cursor} to {@link AppUsageEvent} */
convertToAppUsageEvent(final Cursor cursor)292     public static AppUsageEvent convertToAppUsageEvent(final Cursor cursor) {
293         final AppUsageEvent.Builder eventBuilder = AppUsageEvent.newBuilder();
294         eventBuilder.setTimestamp(getLongFromCursor(cursor, AppUsageEventEntity.KEY_TIMESTAMP));
295         eventBuilder.setType(
296                 AppUsageEventType.forNumber(
297                         getIntegerFromCursor(
298                                 cursor, AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE)));
299         eventBuilder.setPackageName(
300                 getStringFromCursor(cursor, AppUsageEventEntity.KEY_PACKAGE_NAME));
301         eventBuilder.setInstanceId(
302                 getIntegerFromCursor(cursor, AppUsageEventEntity.KEY_INSTANCE_ID));
303         eventBuilder.setTaskRootPackageName(
304                 getStringFromCursor(cursor, AppUsageEventEntity.KEY_TASK_ROOT_PACKAGE_NAME));
305         eventBuilder.setUserId(getLongFromCursor(cursor, AppUsageEventEntity.KEY_USER_ID));
306         eventBuilder.setUid(getLongFromCursor(cursor, AppUsageEventEntity.KEY_UID));
307         return eventBuilder.build();
308     }
309 
310     /** Converts from {@link BatteryEventType} to {@link BatteryEvent} */
convertToBatteryEvent( long timestamp, BatteryEventType type, int batteryLevel)311     public static BatteryEvent convertToBatteryEvent(
312             long timestamp, BatteryEventType type, int batteryLevel) {
313         final BatteryEvent.Builder eventBuilder = BatteryEvent.newBuilder();
314         eventBuilder.setTimestamp(timestamp);
315         eventBuilder.setType(type);
316         eventBuilder.setBatteryLevel(batteryLevel);
317         return eventBuilder.build();
318     }
319 
320     /** Converts from {@link Cursor} to {@link BatteryEvent} */
convertToBatteryEvent(final Cursor cursor)321     public static BatteryEvent convertToBatteryEvent(final Cursor cursor) {
322         final BatteryEvent.Builder eventBuilder = BatteryEvent.newBuilder();
323         eventBuilder.setTimestamp(getLongFromCursor(cursor, BatteryEventEntity.KEY_TIMESTAMP));
324         eventBuilder.setType(
325                 BatteryEventType.forNumber(
326                         getIntegerFromCursor(cursor, BatteryEventEntity.KEY_BATTERY_EVENT_TYPE)));
327         eventBuilder.setBatteryLevel(
328                 getIntegerFromCursor(cursor, BatteryEventEntity.KEY_BATTERY_LEVEL));
329         return eventBuilder.build();
330     }
331 
332     /** Converts from {@link BatteryLevelData} to {@link List<BatteryEvent>} */
convertToBatteryEventList( final BatteryLevelData batteryLevelData)333     public static List<BatteryEvent> convertToBatteryEventList(
334             final BatteryLevelData batteryLevelData) {
335         final List<BatteryEvent> batteryEventList = new ArrayList<>();
336         final List<BatteryLevelData.PeriodBatteryLevelData> levelDataList =
337                 batteryLevelData.getHourlyBatteryLevelsPerDay();
338         final int dailyDataSize = levelDataList.size();
339         for (int dailyIndex = 0; dailyIndex < dailyDataSize; dailyIndex++) {
340             final BatteryLevelData.PeriodBatteryLevelData oneDayData =
341                     levelDataList.get(dailyIndex);
342             final int hourDataSize = oneDayData.getLevels().size();
343             for (int hourIndex = 0; hourIndex < hourDataSize; hourIndex++) {
344                 // For timestamp data on adjacent days, the last data (24:00) of the previous day is
345                 // equal to the first data (00:00) of the next day, so skip sending EVEN_HOUR event.
346                 if (dailyIndex < dailyDataSize - 1 && hourIndex == hourDataSize - 1) {
347                     continue;
348                 }
349                 batteryEventList.add(
350                         convertToBatteryEvent(
351                                 oneDayData.getTimestamps().get(hourIndex),
352                                 BatteryEventType.EVEN_HOUR,
353                                 oneDayData.getLevels().get(hourIndex)));
354             }
355         }
356         return batteryEventList;
357     }
358 
359     /** Converts from {@link Cursor} to {@link BatteryUsageSlot} */
convertToBatteryUsageSlot(final Cursor cursor)360     public static BatteryUsageSlot convertToBatteryUsageSlot(final Cursor cursor) {
361         final BatteryUsageSlot defaultInstance = BatteryUsageSlot.getDefaultInstance();
362         final int columnIndex =
363                 cursor.getColumnIndex(BatteryUsageSlotEntity.KEY_BATTERY_USAGE_SLOT);
364         return columnIndex < 0
365                 ? defaultInstance
366                 : BatteryUtils.parseProtoFromString(cursor.getString(columnIndex), defaultInstance);
367     }
368 
369     /** Converts from {@link Map<Long, BatteryDiffData>} to {@link List<BatteryUsageSlot>} */
convertToBatteryUsageSlotList( final Context context, final Map<Long, BatteryDiffData> batteryDiffDataMap, final boolean isAppOptimizationModeLogged)370     public static List<BatteryUsageSlot> convertToBatteryUsageSlotList(
371             final Context context,
372             final Map<Long, BatteryDiffData> batteryDiffDataMap,
373             final boolean isAppOptimizationModeLogged) {
374         List<BatteryUsageSlot> batteryUsageSlotList = new ArrayList<>();
375         final BatteryOptimizationModeCache optimizationModeCache =
376                 isAppOptimizationModeLogged ? new BatteryOptimizationModeCache(context) : null;
377         for (BatteryDiffData batteryDiffData : batteryDiffDataMap.values()) {
378             batteryUsageSlotList.add(
379                     convertToBatteryUsageSlot(batteryDiffData, optimizationModeCache));
380         }
381         return batteryUsageSlotList;
382     }
383 
384     /**
385      * Converts UTC timestamp to local time string for logging only, so use the US locale for better
386      * readability in debugging.
387      */
utcToLocalTimeForLogging(long timestamp)388     public static String utcToLocalTimeForLogging(long timestamp) {
389         final Locale locale = Locale.US;
390         final String pattern = DateFormat.getBestDateTimePattern(locale, "MMM dd,yyyy HH:mm:ss");
391         return DateFormat.format(pattern, timestamp).toString();
392     }
393 
394     /** Converts UTC timestamp to local time hour data. */
utcToLocalTimeHour( final Context context, final long timestamp, final boolean is24HourFormat, final boolean showMinute)395     public static String utcToLocalTimeHour(
396             final Context context,
397             final long timestamp,
398             final boolean is24HourFormat,
399             final boolean showMinute) {
400         final Locale locale = getLocale(context);
401         // e.g. for 12-hour format: 9 PM
402         // e.g. for 24-hour format: 09:00
403         final String skeleton = is24HourFormat ? "HHm" : (showMinute ? "hma" : "ha");
404         final String pattern = DateFormat.getBestDateTimePattern(locale, skeleton);
405         return DateFormat.format(pattern, timestamp).toString();
406     }
407 
408     /** Converts UTC timestamp to local time day of week data. */
utcToLocalTimeDayOfWeek( final Context context, final long timestamp, final boolean isAbbreviation)409     public static String utcToLocalTimeDayOfWeek(
410             final Context context, final long timestamp, final boolean isAbbreviation) {
411         final Locale locale = getLocale(context);
412         final String pattern =
413                 DateFormat.getBestDateTimePattern(locale, isAbbreviation ? "E" : "EEEE");
414         return DateFormat.format(pattern, timestamp).toString();
415     }
416 
417     @VisibleForTesting
getLocale(Context context)418     static Locale getLocale(Context context) {
419         if (context == null) {
420             return Locale.getDefault();
421         }
422         final LocaleList locales = context.getResources().getConfiguration().getLocales();
423         return locales != null && !locales.isEmpty() ? locales.get(0) : Locale.getDefault();
424     }
425 
426     /**
427      * Returns the package name the app usage should be attributed to.
428      *
429      * <ul>
430      *   <li>If {@link UsageStatsManager#getUsageSource()} returns {@link
431      *       UsageStatsManager#USAGE_SOURCE_CURRENT_ACTIVITY}, this method will return packageName.
432      *   <li>If {@link UsageStatsManager#getUsageSource()} returns {@link
433      *       UsageStatsManager#USAGE_SOURCE_TASK_ROOT_ACTIVITY}, this method will return
434      *       taskRootPackageName if it exists, or packageName otherwise.
435      * </ul>
436      */
437     @VisibleForTesting
getEffectivePackageName( Context context, IUsageStatsManager usageStatsManager, final String packageName, final String taskRootPackageName)438     static String getEffectivePackageName(
439             Context context,
440             IUsageStatsManager usageStatsManager,
441             final String packageName,
442             final String taskRootPackageName) {
443         final int usageSource = getUsageSource(context, usageStatsManager);
444         switch (usageSource) {
445             case UsageStatsManager.USAGE_SOURCE_TASK_ROOT_ACTIVITY:
446                 return !TextUtils.isEmpty(taskRootPackageName) ? taskRootPackageName : packageName;
447             case UsageStatsManager.USAGE_SOURCE_CURRENT_ACTIVITY:
448                 return packageName;
449             default:
450                 Log.e(TAG, "Unexpected usage source: " + usageSource);
451                 return packageName;
452         }
453     }
454 
455     /**
456      * Returns the package name of the task root when this event was reported when {@code event} is
457      * one of:
458      *
459      * <ul>
460      *   <li>{@link Event#ACTIVITY_RESUMED}
461      *   <li>{@link Event#ACTIVITY_STOPPED}
462      * </ul>
463      */
464     @Nullable
getTaskRootPackageName(Event event)465     private static String getTaskRootPackageName(Event event) {
466         int eventType = event.getEventType();
467         if (eventType != Event.ACTIVITY_RESUMED && eventType != Event.ACTIVITY_STOPPED) {
468             // Task root is only relevant for ACTIVITY_* events.
469             return null;
470         }
471 
472         try {
473             String taskRootPackageName = event.getTaskRootPackageName();
474             if (taskRootPackageName == null) {
475                 Log.w(
476                         TAG,
477                         String.format(
478                                 "Null task root in event with timestamp %d, type=%d, package %s",
479                                 event.getTimeStamp(),
480                                 event.getEventType(),
481                                 event.getPackageName()));
482             }
483             return taskRootPackageName;
484         } catch (NoSuchMethodError e) {
485             Log.w(TAG, "Failed to call Event#getTaskRootPackageName()");
486             return null;
487         }
488     }
489 
getUsageSource(Context context, IUsageStatsManager usageStatsManager)490     private static int getUsageSource(Context context, IUsageStatsManager usageStatsManager) {
491         if (sUsageSource == EMPTY_USAGE_SOURCE) {
492             sUsageSource = DatabaseUtils.getUsageSource(context, usageStatsManager);
493         }
494         return sUsageSource;
495     }
496 
getAppUsageEventType(final int eventType)497     private static AppUsageEventType getAppUsageEventType(final int eventType) {
498         switch (eventType) {
499             case Event.ACTIVITY_RESUMED:
500                 return AppUsageEventType.ACTIVITY_RESUMED;
501             case Event.ACTIVITY_STOPPED:
502                 return AppUsageEventType.ACTIVITY_STOPPED;
503             case Event.DEVICE_SHUTDOWN:
504                 return AppUsageEventType.DEVICE_SHUTDOWN;
505             default:
506                 return AppUsageEventType.UNKNOWN;
507         }
508     }
509 
510     @VisibleForTesting
convertToBatteryUsageDiff( final BatteryDiffEntry batteryDiffEntry, final @Nullable BatteryOptimizationModeCache optimizationModeCache)511     static BatteryUsageDiff convertToBatteryUsageDiff(
512             final BatteryDiffEntry batteryDiffEntry,
513             final @Nullable BatteryOptimizationModeCache optimizationModeCache) {
514         BatteryUsageDiff.Builder builder =
515                 BatteryUsageDiff.newBuilder()
516                         .setUid(batteryDiffEntry.mUid)
517                         .setUserId(batteryDiffEntry.mUserId)
518                         .setIsHidden(batteryDiffEntry.mIsHidden)
519                         .setComponentId(batteryDiffEntry.mComponentId)
520                         .setConsumerType(batteryDiffEntry.mConsumerType)
521                         .setConsumePower(batteryDiffEntry.mConsumePower)
522                         .setForegroundUsageConsumePower(
523                                 batteryDiffEntry.mForegroundUsageConsumePower)
524                         .setBackgroundUsageConsumePower(
525                                 batteryDiffEntry.mBackgroundUsageConsumePower)
526                         .setForegroundServiceUsageConsumePower(
527                                 batteryDiffEntry.mForegroundServiceUsageConsumePower)
528                         .setCachedUsageConsumePower(batteryDiffEntry.mCachedUsageConsumePower)
529                         .setForegroundUsageTime(batteryDiffEntry.mForegroundUsageTimeInMs)
530                         .setForegroundServiceUsageTime(
531                                 batteryDiffEntry.mForegroundServiceUsageTimeInMs)
532                         .setBackgroundUsageTime(batteryDiffEntry.mBackgroundUsageTimeInMs)
533                         .setScreenOnTime(batteryDiffEntry.mScreenOnTimeInMs);
534         if (batteryDiffEntry.mKey != null) {
535             builder.setKey(batteryDiffEntry.mKey);
536         }
537         if (batteryDiffEntry.mLegacyPackageName != null) {
538             builder.setPackageName(batteryDiffEntry.mLegacyPackageName);
539         }
540         if (batteryDiffEntry.mLegacyLabel != null) {
541             builder.setLabel(batteryDiffEntry.mLegacyLabel);
542         }
543         // Log the battery optimization mode of AppEntry while converting to batteryUsageSlot.
544         if (optimizationModeCache != null && !batteryDiffEntry.isSystemEntry()) {
545             builder.setAppOptimizationMode(
546                     optimizationModeCache.getBatteryOptimizeMode(
547                             (int) batteryDiffEntry.mUid, batteryDiffEntry.getPackageName()));
548         }
549         return builder.build();
550     }
551 
convertToBatteryUsageSlot( final BatteryDiffData batteryDiffData, final @Nullable BatteryOptimizationModeCache optimizationModeCache)552     private static BatteryUsageSlot convertToBatteryUsageSlot(
553             final BatteryDiffData batteryDiffData,
554             final @Nullable BatteryOptimizationModeCache optimizationModeCache) {
555         if (batteryDiffData == null) {
556             return BatteryUsageSlot.getDefaultInstance();
557         }
558         final BatteryUsageSlot.Builder builder =
559                 BatteryUsageSlot.newBuilder()
560                         .setStartTimestamp(batteryDiffData.getStartTimestamp())
561                         .setEndTimestamp(batteryDiffData.getEndTimestamp())
562                         .setStartBatteryLevel(batteryDiffData.getStartBatteryLevel())
563                         .setEndBatteryLevel(batteryDiffData.getEndBatteryLevel())
564                         .setScreenOnTime(batteryDiffData.getScreenOnTime());
565         for (BatteryDiffEntry batteryDiffEntry : batteryDiffData.getAppDiffEntryList()) {
566             builder.addAppUsage(convertToBatteryUsageDiff(batteryDiffEntry, optimizationModeCache));
567         }
568         for (BatteryDiffEntry batteryDiffEntry : batteryDiffData.getSystemDiffEntryList()) {
569             builder.addSystemUsage(
570                     convertToBatteryUsageDiff(batteryDiffEntry, /* optimizationModeCache= */ null));
571         }
572         return builder.build();
573     }
574 
convertToBatteryDiffEntry( Context context, final BatteryUsageDiff batteryUsageDiff)575     private static BatteryDiffEntry convertToBatteryDiffEntry(
576             Context context, final BatteryUsageDiff batteryUsageDiff) {
577         return new BatteryDiffEntry(
578                 context,
579                 batteryUsageDiff.getUid(),
580                 batteryUsageDiff.getUserId(),
581                 batteryUsageDiff.getKey(),
582                 batteryUsageDiff.getIsHidden(),
583                 batteryUsageDiff.getComponentId(),
584                 batteryUsageDiff.getPackageName(),
585                 batteryUsageDiff.getLabel(),
586                 batteryUsageDiff.getConsumerType(),
587                 batteryUsageDiff.getForegroundUsageTime(),
588                 batteryUsageDiff.getForegroundServiceUsageTime(),
589                 batteryUsageDiff.getBackgroundUsageTime(),
590                 batteryUsageDiff.getScreenOnTime(),
591                 batteryUsageDiff.getConsumePower(),
592                 batteryUsageDiff.getForegroundUsageConsumePower(),
593                 batteryUsageDiff.getForegroundServiceUsageConsumePower(),
594                 batteryUsageDiff.getBackgroundUsageConsumePower(),
595                 batteryUsageDiff.getCachedUsageConsumePower());
596     }
597 
convertToBatteryDiffData( Context context, final BatteryUsageSlot batteryUsageSlot, @NonNull final Set<String> systemAppsPackageNames, @NonNull final Set<Integer> systemAppsUids)598     static BatteryDiffData convertToBatteryDiffData(
599             Context context,
600             final BatteryUsageSlot batteryUsageSlot,
601             @NonNull final Set<String> systemAppsPackageNames,
602             @NonNull final Set<Integer> systemAppsUids) {
603         final List<BatteryDiffEntry> appDiffEntries = new ArrayList<>();
604         final List<BatteryDiffEntry> systemDiffEntries = new ArrayList<>();
605         for (BatteryUsageDiff batteryUsageDiff : batteryUsageSlot.getAppUsageList()) {
606             appDiffEntries.add(convertToBatteryDiffEntry(context, batteryUsageDiff));
607         }
608         for (BatteryUsageDiff batteryUsageDiff : batteryUsageSlot.getSystemUsageList()) {
609             systemDiffEntries.add(convertToBatteryDiffEntry(context, batteryUsageDiff));
610         }
611         return new BatteryDiffData(
612                 context,
613                 batteryUsageSlot.getStartTimestamp(),
614                 batteryUsageSlot.getEndTimestamp(),
615                 batteryUsageSlot.getStartBatteryLevel(),
616                 batteryUsageSlot.getEndBatteryLevel(),
617                 batteryUsageSlot.getScreenOnTime(),
618                 appDiffEntries,
619                 systemDiffEntries,
620                 systemAppsPackageNames,
621                 systemAppsUids,
622                 /* isAccumulated= */ false);
623     }
624 
constructBatteryInformation( final BatteryEntry entry, final BatteryUsageStats batteryUsageStats, final int batteryLevel, final int batteryStatus, final int batteryHealth, final long bootTimestamp)625     private static BatteryInformation constructBatteryInformation(
626             final BatteryEntry entry,
627             final BatteryUsageStats batteryUsageStats,
628             final int batteryLevel,
629             final int batteryStatus,
630             final int batteryHealth,
631             final long bootTimestamp) {
632         final DeviceBatteryState deviceBatteryState =
633                 DeviceBatteryState.newBuilder()
634                         .setBatteryLevel(batteryLevel)
635                         .setBatteryStatus(batteryStatus)
636                         .setBatteryHealth(batteryHealth)
637                         .build();
638         final BatteryInformation.Builder batteryInformationBuilder =
639                 BatteryInformation.newBuilder()
640                         .setDeviceBatteryState(deviceBatteryState)
641                         .setBootTimestamp(bootTimestamp)
642                         .setZoneId(TimeZone.getDefault().getID());
643         if (entry != null && batteryUsageStats != null) {
644             batteryInformationBuilder
645                     .setIsHidden(entry.isHidden())
646                     .setAppLabel(entry.getLabel() != null ? entry.getLabel() : "")
647                     .setTotalPower(batteryUsageStats.getConsumedPower())
648                     .setConsumePower(entry.getConsumedPower())
649                     .setForegroundUsageConsumePower(entry.getConsumedPowerInForeground())
650                     .setForegroundServiceUsageConsumePower(
651                             entry.getConsumedPowerInForegroundService())
652                     .setBackgroundUsageConsumePower(entry.getConsumedPowerInBackground())
653                     .setCachedUsageConsumePower(entry.getConsumedPowerInCached())
654                     .setPercentOfTotal(entry.mPercent)
655                     .setDrainType(entry.getPowerComponentId())
656                     .setForegroundUsageTimeInMs(entry.getTimeInForegroundMs())
657                     .setForegroundServiceUsageTimeInMs(entry.getTimeInForegroundServiceMs())
658                     .setBackgroundUsageTimeInMs(entry.getTimeInBackgroundMs());
659         }
660 
661         return batteryInformationBuilder.build();
662     }
663 
getIntegerFromCursor(final Cursor cursor, final String key)664     private static int getIntegerFromCursor(final Cursor cursor, final String key) {
665         final int columnIndex = cursor.getColumnIndex(key);
666         if (columnIndex >= 0) {
667             return cursor.getInt(columnIndex);
668         }
669         return 0;
670     }
671 
getLongFromCursor(final Cursor cursor, final String key)672     private static long getLongFromCursor(final Cursor cursor, final String key) {
673         final int columnIndex = cursor.getColumnIndex(key);
674         if (columnIndex >= 0) {
675             return cursor.getLong(columnIndex);
676         }
677         return 0L;
678     }
679 
getStringFromCursor(final Cursor cursor, final String key)680     private static String getStringFromCursor(final Cursor cursor, final String key) {
681         final int columnIndex = cursor.getColumnIndex(key);
682         if (columnIndex >= 0) {
683             return cursor.getString(columnIndex);
684         }
685         return "";
686     }
687 }
688