• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16 
17 package com.android.settings.fuelgauge;
18 
19 import android.app.AppGlobals;
20 import android.app.AppOpsManager;
21 import android.app.backup.BackupDataInputStream;
22 import android.app.backup.BackupDataOutput;
23 import android.app.backup.BackupHelper;
24 import android.content.Context;
25 import android.content.SharedPreferences;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.IPackageManager;
28 import android.os.Build;
29 import android.os.IDeviceIdleController;
30 import android.os.ParcelFileDescriptor;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.UserHandle;
34 import android.util.ArrayMap;
35 import android.util.ArraySet;
36 import android.util.Log;
37 
38 import androidx.annotation.VisibleForTesting;
39 
40 import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
41 import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils;
42 import com.android.settings.fuelgauge.batteryusage.AppOptimizationModeEvent;
43 import com.android.settings.overlay.FeatureFactory;
44 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
45 
46 import java.io.IOException;
47 import java.io.PrintWriter;
48 import java.nio.charset.StandardCharsets;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.List;
52 import java.util.Map;
53 import java.util.stream.Collectors;
54 
55 /** An implementation to backup and restore battery configurations. */
56 public final class BatteryBackupHelper implements BackupHelper {
57     /** An inditifier for {@link BackupHelper}. */
58     public static final String TAG = "BatteryBackupHelper";
59 
60     // Definition for the device build information.
61     public static final String KEY_BUILD_BRAND = "device_build_brand";
62     public static final String KEY_BUILD_PRODUCT = "device_build_product";
63     public static final String KEY_BUILD_MANUFACTURER = "device_build_manufacture";
64     public static final String KEY_BUILD_FINGERPRINT = "device_build_fingerprint";
65     // Customized fields for device extra information.
66     public static final String KEY_BUILD_METADATA_1 = "device_build_metadata_1";
67     public static final String KEY_BUILD_METADATA_2 = "device_build_metadata_2";
68 
69     private static final String DEVICE_IDLE_SERVICE = "deviceidle";
70     private static final String BATTERY_OPTIMIZE_BACKUP_FILE_NAME =
71             "battery_optimize_backup_historical_logs";
72     private static final int DEVICE_BUILD_INFO_SIZE = 6;
73 
74     static final String DELIMITER = ",";
75     static final String DELIMITER_MODE = ":";
76     static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list";
77 
78     @VisibleForTesting ArraySet<ApplicationInfo> mTestApplicationInfoList = null;
79 
80     @VisibleForTesting PowerAllowlistBackend mPowerAllowlistBackend;
81     @VisibleForTesting IDeviceIdleController mIDeviceIdleController;
82     @VisibleForTesting IPackageManager mIPackageManager;
83     @VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils;
84 
85     private byte[] mOptimizationModeBytes;
86     private boolean mVerifyMigrateConfiguration = false;
87 
88     private final Context mContext;
89     // Device information map from the restoreEntity() method.
90     private final ArrayMap<String, String> mDeviceBuildInfoMap =
91             new ArrayMap<>(DEVICE_BUILD_INFO_SIZE);
92 
BatteryBackupHelper(Context context)93     public BatteryBackupHelper(Context context) {
94         mContext = context.getApplicationContext();
95     }
96 
97     @Override
performBackup( ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)98     public void performBackup(
99             ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) {
100         if (!isOwner() || data == null) {
101             Log.w(TAG, "ignore performBackup() for non-owner or empty data");
102             return;
103         }
104         final List<String> allowlistedApps = getFullPowerList();
105         if (allowlistedApps == null) {
106             return;
107         }
108 
109         writeBackupData(data, KEY_BUILD_BRAND, Build.BRAND);
110         writeBackupData(data, KEY_BUILD_PRODUCT, Build.PRODUCT);
111         writeBackupData(data, KEY_BUILD_MANUFACTURER, Build.MANUFACTURER);
112         writeBackupData(data, KEY_BUILD_FINGERPRINT, Build.FINGERPRINT);
113         // Add customized device build metadata fields.
114         final PowerUsageFeatureProvider provider =
115                 FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
116         writeBackupData(data, KEY_BUILD_METADATA_1, provider.getBuildMetadata1(mContext));
117         writeBackupData(data, KEY_BUILD_METADATA_2, provider.getBuildMetadata2(mContext));
118 
119         backupOptimizationMode(data, allowlistedApps);
120     }
121 
122     @Override
restoreEntity(BackupDataInputStream data)123     public void restoreEntity(BackupDataInputStream data) {
124         // Ensure we only verify the migrate configuration one time.
125         if (!mVerifyMigrateConfiguration) {
126             mVerifyMigrateConfiguration = true;
127             BatterySettingsMigrateChecker.verifySaverConfiguration(mContext);
128         }
129         if (!isOwner() || data == null || data.size() == 0) {
130             Log.w(TAG, "ignore restoreEntity() for non-owner or empty data");
131             return;
132         }
133         final String dataKey = data.getKey();
134         switch (dataKey) {
135             case KEY_BUILD_BRAND:
136             case KEY_BUILD_PRODUCT:
137             case KEY_BUILD_MANUFACTURER:
138             case KEY_BUILD_FINGERPRINT:
139             case KEY_BUILD_METADATA_1:
140             case KEY_BUILD_METADATA_2:
141                 restoreBackupData(dataKey, data);
142                 break;
143             case KEY_OPTIMIZATION_LIST:
144                 // Hold the optimization mode data until all conditions are matched.
145                 mOptimizationModeBytes = getBackupData(dataKey, data);
146                 break;
147         }
148         performRestoreIfNeeded();
149     }
150 
151     @Override
writeNewStateDescription(ParcelFileDescriptor newState)152     public void writeNewStateDescription(ParcelFileDescriptor newState) {}
153 
getFullPowerList()154     private List<String> getFullPowerList() {
155         final long timestamp = System.currentTimeMillis();
156         String[] allowlistedApps;
157         try {
158             allowlistedApps = getIDeviceIdleController().getFullPowerWhitelist();
159         } catch (RemoteException e) {
160             Log.e(TAG, "backupFullPowerList() failed", e);
161             return null;
162         }
163         // Ignores unexpected empty result case.
164         if (allowlistedApps == null || allowlistedApps.length == 0) {
165             Log.w(TAG, "no data found in the getFullPowerList()");
166             return new ArrayList<>();
167         }
168         Log.d(
169                 TAG,
170                 String.format(
171                         "getFullPowerList() size=%d in %d/ms",
172                         allowlistedApps.length, (System.currentTimeMillis() - timestamp)));
173         return Arrays.asList(allowlistedApps);
174     }
175 
176     @VisibleForTesting
backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps)177     void backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps) {
178         final long timestamp = System.currentTimeMillis();
179         final ArraySet<ApplicationInfo> applications = getInstalledApplications();
180         if (applications == null || applications.isEmpty()) {
181             Log.w(TAG, "no data found in the getInstalledApplications()");
182             return;
183         }
184         int backupCount = 0;
185         final StringBuilder builder = new StringBuilder();
186         final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
187         final SharedPreferences sharedPreferences = getSharedPreferences(mContext);
188         final Map<Integer, AppOptimizationModeEvent> appOptModeMap =
189                 AppOptModeSharedPreferencesUtils.getAllEvents(mContext).stream()
190                         .collect(Collectors.toMap(AppOptimizationModeEvent::getUid, e -> e));
191         // Converts application into the AppUsageState.
192         for (ApplicationInfo info : applications) {
193             final int mode = BatteryOptimizeUtils.getMode(appOps, info.uid, info.packageName);
194             @BatteryOptimizeUtils.OptimizationMode
195             final int optimizationMode =
196                     appOptModeMap.containsKey(info.uid)
197                             ? (int) appOptModeMap.get(info.uid).getResetOptimizationMode()
198                             : BatteryOptimizeUtils.getAppOptimizationMode(
199                                     mode,
200                                     allowlistedApps.contains(info.packageName),
201                                     /* ignoreUnknownMode= */ false);
202             // Ignores default optimized/unknown state or system/default apps.
203             if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED
204                     || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN
205                     || isSystemOrDefaultApp(info.packageName, info.uid)) {
206                 continue;
207             }
208             final String packageOptimizeMode = info.packageName + DELIMITER_MODE + optimizationMode;
209             builder.append(packageOptimizeMode + DELIMITER);
210             Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode);
211             BatteryOptimizeLogUtils.writeLog(
212                     sharedPreferences,
213                     Action.BACKUP,
214                     info.packageName,
215                     /* actionDescription */ "mode: " + optimizationMode);
216             backupCount++;
217         }
218 
219         writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString());
220         Log.d(
221                 TAG,
222                 String.format(
223                         "backup getInstalledApplications():%d count=%d in %d/ms",
224                         applications.size(),
225                         backupCount,
226                         (System.currentTimeMillis() - timestamp)));
227     }
228 
229     @VisibleForTesting
restoreOptimizationMode(byte[] dataBytes)230     int restoreOptimizationMode(byte[] dataBytes) {
231         final long timestamp = System.currentTimeMillis();
232         final String dataContent = new String(dataBytes, StandardCharsets.UTF_8);
233         if (dataContent == null || dataContent.isEmpty()) {
234             Log.w(TAG, "no data found in the restoreOptimizationMode()");
235             return 0;
236         }
237         final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER);
238         if (appConfigurations == null || appConfigurations.length == 0) {
239             Log.w(TAG, "no data found from the split() processing");
240             return 0;
241         }
242         int restoreCount = 0;
243         for (int index = 0; index < appConfigurations.length; index++) {
244             final String[] results =
245                     appConfigurations[index].split(BatteryBackupHelper.DELIMITER_MODE);
246             // Example format: com.android.systemui:2 we should have length=2
247             if (results == null || results.length != 2) {
248                 Log.w(TAG, "invalid raw data found:" + appConfigurations[index]);
249                 continue;
250             }
251             final String packageName = results[0];
252             final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
253             // Ignores system/default apps.
254             if (isSystemOrDefaultApp(packageName, uid)) {
255                 Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName);
256                 continue;
257             }
258             @BatteryOptimizeUtils.OptimizationMode
259             int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;
260             try {
261                 optimizationMode = Integer.parseInt(results[1]);
262             } catch (NumberFormatException e) {
263                 Log.e(TAG, "failed to parse the optimization mode: " + appConfigurations[index], e);
264                 continue;
265             }
266             restoreOptimizationMode(packageName, optimizationMode);
267             restoreCount++;
268         }
269         Log.d(
270                 TAG,
271                 String.format(
272                         "restoreOptimizationMode() count=%d in %d/ms",
273                         restoreCount, (System.currentTimeMillis() - timestamp)));
274         return restoreCount;
275     }
276 
performRestoreIfNeeded()277     private void performRestoreIfNeeded() {
278         if (mOptimizationModeBytes == null || mOptimizationModeBytes.length == 0) {
279             return;
280         }
281         final PowerUsageFeatureProvider provider =
282                 FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
283         if (!provider.isValidToRestoreOptimizationMode(mDeviceBuildInfoMap)) {
284             return;
285         }
286         // Start to restore the app optimization mode data.
287         final int restoreCount = restoreOptimizationMode(mOptimizationModeBytes);
288         if (restoreCount > 0) {
289             BatterySettingsMigrateChecker.verifyBatteryOptimizeModes(mContext);
290         }
291         mOptimizationModeBytes = null; // clear data
292     }
293 
294     /** Dump the app optimization mode backup history data. */
dumpHistoricalData(Context context, PrintWriter writer)295     public static void dumpHistoricalData(Context context, PrintWriter writer) {
296         BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(
297                 getSharedPreferences(context), writer);
298     }
299 
isOwner()300     static boolean isOwner() {
301         return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
302     }
303 
newBatteryOptimizeUtils( Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils)304     static BatteryOptimizeUtils newBatteryOptimizeUtils(
305             Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils) {
306         final int uid = BatteryUtils.getInstance(context).getPackageUid(packageName);
307         if (uid == BatteryUtils.UID_NULL) {
308             return null;
309         }
310         final BatteryOptimizeUtils batteryOptimizeUtils =
311                 testOptimizeUtils != null
312                         ? testOptimizeUtils /*testing only*/
313                         : new BatteryOptimizeUtils(context, uid, packageName);
314         return batteryOptimizeUtils;
315     }
316 
317     @VisibleForTesting
getSharedPreferences(Context context)318     static SharedPreferences getSharedPreferences(Context context) {
319         return context.getSharedPreferences(
320                 BATTERY_OPTIMIZE_BACKUP_FILE_NAME, Context.MODE_PRIVATE);
321     }
322 
restoreOptimizationMode( String packageName, @BatteryOptimizeUtils.OptimizationMode int mode)323     private void restoreOptimizationMode(
324             String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) {
325         final BatteryOptimizeUtils batteryOptimizeUtils =
326                 newBatteryOptimizeUtils(mContext, packageName, mBatteryOptimizeUtils);
327         if (batteryOptimizeUtils == null) {
328             return;
329         }
330         batteryOptimizeUtils.setAppUsageState(
331                 mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE);
332         Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode));
333     }
334 
335     // Provides an opportunity to inject mock IDeviceIdleController for testing.
getIDeviceIdleController()336     private IDeviceIdleController getIDeviceIdleController() {
337         if (mIDeviceIdleController != null) {
338             return mIDeviceIdleController;
339         }
340         mIDeviceIdleController =
341                 IDeviceIdleController.Stub.asInterface(
342                         ServiceManager.getService(DEVICE_IDLE_SERVICE));
343         return mIDeviceIdleController;
344     }
345 
getIPackageManager()346     private IPackageManager getIPackageManager() {
347         if (mIPackageManager != null) {
348             return mIPackageManager;
349         }
350         mIPackageManager = AppGlobals.getPackageManager();
351         return mIPackageManager;
352     }
353 
getPowerAllowlistBackend()354     private PowerAllowlistBackend getPowerAllowlistBackend() {
355         if (mPowerAllowlistBackend != null) {
356             return mPowerAllowlistBackend;
357         }
358         mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext);
359         return mPowerAllowlistBackend;
360     }
361 
isSystemOrDefaultApp(String packageName, int uid)362     private boolean isSystemOrDefaultApp(String packageName, int uid) {
363         return BatteryOptimizeUtils.isSystemOrDefaultApp(
364                 mContext, getPowerAllowlistBackend(), packageName, uid);
365     }
366 
getInstalledApplications()367     private ArraySet<ApplicationInfo> getInstalledApplications() {
368         if (mTestApplicationInfoList != null) {
369             return mTestApplicationInfoList;
370         }
371         return BatteryOptimizeUtils.getInstalledApplications(mContext, getIPackageManager());
372     }
373 
restoreBackupData(String dataKey, BackupDataInputStream data)374     private void restoreBackupData(String dataKey, BackupDataInputStream data) {
375         final byte[] dataBytes = getBackupData(dataKey, data);
376         if (dataBytes == null || dataBytes.length == 0) {
377             return;
378         }
379         final String dataContent = new String(dataBytes, StandardCharsets.UTF_8);
380         mDeviceBuildInfoMap.put(dataKey, dataContent);
381         Log.d(TAG, String.format("restore:%s:%s", dataKey, dataContent));
382     }
383 
getBackupData(String dataKey, BackupDataInputStream data)384     private static byte[] getBackupData(String dataKey, BackupDataInputStream data) {
385         final int dataSize = data.size();
386         final byte[] dataBytes = new byte[dataSize];
387         try {
388             data.read(dataBytes, 0 /*offset*/, dataSize);
389         } catch (IOException e) {
390             Log.e(TAG, "failed to getBackupData() " + dataKey, e);
391             return null;
392         }
393         return dataBytes;
394     }
395 
writeBackupData(BackupDataOutput data, String dataKey, String dataContent)396     private static void writeBackupData(BackupDataOutput data, String dataKey, String dataContent) {
397         if (dataContent == null || dataContent.isEmpty()) {
398             return;
399         }
400         final byte[] dataContentBytes = dataContent.getBytes();
401         try {
402             data.writeEntityHeader(dataKey, dataContentBytes.length);
403             data.writeEntityData(dataContentBytes, dataContentBytes.length);
404         } catch (IOException e) {
405             Log.e(TAG, "writeBackupData() is failed for " + dataKey, e);
406         }
407         Log.d(TAG, String.format("backup:%s:%s", dataKey, dataContent));
408     }
409 }
410