1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.fuelgauge.batteryusage;
18 
19 import android.app.usage.UsageEvents;
20 import android.content.Context;
21 import android.os.AsyncTask;
22 import android.os.BatteryUsageStats;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.util.Log;
26 
27 import androidx.annotation.VisibleForTesting;
28 
29 import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action;
30 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
31 import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils;
32 import com.android.settings.overlay.FeatureFactory;
33 
34 import java.util.List;
35 import java.util.Map;
36 import java.util.function.Supplier;
37 
38 /** Load battery usage data in the background. */
39 public final class BatteryUsageDataLoader {
40     private static final String TAG = "BatteryUsageDataLoader";
41 
42     // For testing only.
43     @VisibleForTesting static Supplier<List<BatteryEntry>> sFakeBatteryEntryListSupplier;
44     @VisibleForTesting static Supplier<Map<Long, UsageEvents>> sFakeAppUsageEventsSupplier;
45     @VisibleForTesting static Supplier<List<AppUsageEvent>> sFakeUsageEventsListSupplier;
46 
BatteryUsageDataLoader()47     private BatteryUsageDataLoader() {}
48 
enqueueWork(final Context context, final boolean isFullChargeStart)49     static void enqueueWork(final Context context, final boolean isFullChargeStart) {
50         AsyncTask.execute(
51                 () -> {
52                     Log.d(TAG, "loadUsageDataSafely() in the AsyncTask");
53                     loadUsageDataSafely(context.getApplicationContext(), isFullChargeStart);
54                 });
55     }
56 
57     @VisibleForTesting
loadBatteryStatsData(final Context context, final boolean isFullChargeStart)58     static void loadBatteryStatsData(final Context context, final boolean isFullChargeStart) {
59         BatteryUsageLogUtils.writeLog(context, Action.FETCH_USAGE_DATA, "");
60         final long currentTime = System.currentTimeMillis();
61         final BatteryUsageStats batteryUsageStats = DataProcessor.getBatteryUsageStats(context);
62         final List<BatteryEntry> batteryEntryList =
63                 sFakeBatteryEntryListSupplier != null
64                         ? sFakeBatteryEntryListSupplier.get()
65                         : DataProcessor.generateBatteryEntryListFromBatteryUsageStats(
66                                 context, batteryUsageStats);
67         if (batteryEntryList == null || batteryEntryList.isEmpty()) {
68             Log.w(TAG, "getBatteryEntryList() returns null or empty content");
69         }
70         final long elapsedTime = System.currentTimeMillis() - currentTime;
71         Log.d(TAG, String.format("getBatteryUsageStats() in %d/ms", elapsedTime));
72         if (isFullChargeStart) {
73             DatabaseUtils.recordDateTime(context, DatabaseUtils.KEY_LAST_LOAD_FULL_CHARGE_TIME);
74             DatabaseUtils.sendBatteryEventData(
75                     context,
76                     ConvertUtils.convertToBatteryEvent(
77                             currentTime, BatteryEventType.FULL_CHARGED, 100));
78             DatabaseUtils.removeDismissedPowerAnomalyKeys(context);
79         }
80 
81         // Uploads the BatteryEntry data into database.
82         DatabaseUtils.sendBatteryEntryData(
83                 context, currentTime, batteryEntryList, batteryUsageStats, isFullChargeStart);
84         DataProcessor.closeBatteryUsageStats(batteryUsageStats);
85     }
86 
87     @VisibleForTesting
loadAppUsageData(final Context context, final UserIdsSeries userIdsSeries)88     static void loadAppUsageData(final Context context, final UserIdsSeries userIdsSeries) {
89         final long start = System.currentTimeMillis();
90         final Map<Long, UsageEvents> appUsageEvents =
91                 sFakeAppUsageEventsSupplier != null
92                         ? sFakeAppUsageEventsSupplier.get()
93                         : DataProcessor.getAppUsageEvents(context, userIdsSeries);
94         if (appUsageEvents == null) {
95             Log.w(TAG, "loadAppUsageData() returns null");
96             return;
97         }
98         final List<AppUsageEvent> appUsageEventList =
99                 sFakeUsageEventsListSupplier != null
100                         ? sFakeUsageEventsListSupplier.get()
101                         : DataProcessor.generateAppUsageEventListFromUsageEvents(
102                                 context, appUsageEvents);
103         if (appUsageEventList == null || appUsageEventList.isEmpty()) {
104             Log.w(TAG, "loadAppUsageData() returns null or empty content");
105             return;
106         }
107         final long elapsedTime = System.currentTimeMillis() - start;
108         Log.d(
109                 TAG,
110                 String.format(
111                         "loadAppUsageData() size=%d in %d/ms",
112                         appUsageEventList.size(), elapsedTime));
113         // Uploads the AppUsageEvent data into database.
114         DatabaseUtils.sendAppUsageEventData(context, appUsageEventList);
115     }
116 
preprocessBatteryUsageSlots( final Context context, final UserIdsSeries userIdsSeries)117     private static void preprocessBatteryUsageSlots(
118             final Context context, final UserIdsSeries userIdsSeries) {
119         final long start = System.currentTimeMillis();
120         final Handler handler = new Handler(Looper.getMainLooper());
121         final BatteryLevelData batteryLevelData =
122                 DataProcessManager.getBatteryLevelData(
123                         context,
124                         handler,
125                         userIdsSeries,
126                         /* isFromPeriodJob= */ true,
127                         batteryDiffDataMap -> {
128                             final PowerUsageFeatureProvider featureProvider =
129                                     FeatureFactory.getFeatureFactory()
130                                             .getPowerUsageFeatureProvider();
131                             DatabaseUtils.sendBatteryUsageSlotData(
132                                     context,
133                                     ConvertUtils.convertToBatteryUsageSlotList(
134                                             context,
135                                             batteryDiffDataMap,
136                                             featureProvider.isAppOptimizationModeLogged()));
137                             if (batteryDiffDataMap.values().stream()
138                                     .anyMatch(
139                                             data ->
140                                                     data != null
141                                                             && (!data.getSystemDiffEntryList()
142                                                                             .isEmpty()
143                                                                     || !data.getAppDiffEntryList()
144                                                                             .isEmpty()))) {
145                                 featureProvider.detectPowerAnomaly(
146                                         context,
147                                         /* displayDrain= */ 0,
148                                         DetectRequestSourceType.TYPE_DATA_LOADER);
149                             }
150                         });
151         if (batteryLevelData == null) {
152             Log.d(TAG, "preprocessBatteryUsageSlots() no new battery usage data.");
153             return;
154         }
155 
156         DatabaseUtils.sendBatteryEventData(
157                 context, ConvertUtils.convertToBatteryEventList(batteryLevelData));
158         Log.d(
159                 TAG,
160                 String.format(
161                         "preprocessBatteryUsageSlots() batteryLevelData=%s in %d/ms",
162                         batteryLevelData, System.currentTimeMillis() - start));
163     }
164 
loadUsageDataSafely( final Context context, final boolean isFullChargeStart)165     private static void loadUsageDataSafely(
166             final Context context, final boolean isFullChargeStart) {
167         try {
168             final long start = System.currentTimeMillis();
169             loadBatteryStatsData(context, isFullChargeStart);
170             AppOptModeSharedPreferencesUtils.resetExpiredAppOptModeBeforeTimestamp(
171                     context, System.currentTimeMillis());
172             if (!isFullChargeStart) {
173                 // No app usage data or battery diff data at this time.
174                 final UserIdsSeries userIdsSeries =
175                         new UserIdsSeries(context, /* isNonUIRequest= */ true);
176                 if (!userIdsSeries.isCurrentUserLocked()) {
177                     loadAppUsageData(context, userIdsSeries);
178                     preprocessBatteryUsageSlots(context, userIdsSeries);
179                 }
180             }
181             Log.d(
182                     TAG,
183                     String.format(
184                             "loadUsageDataSafely() in %d/ms", System.currentTimeMillis() - start));
185         } catch (RuntimeException e) {
186             Log.e(TAG, "loadUsageData:", e);
187         }
188     }
189 }
190