• 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.overlay.FeatureFactory;
42 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
43 
44 import java.io.IOException;
45 import java.io.PrintWriter;
46 import java.nio.charset.StandardCharsets;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.List;
50 
51 /** An implementation to backup and restore battery configurations. */
52 public final class BatteryBackupHelper implements BackupHelper {
53     /** An inditifier for {@link BackupHelper}. */
54     public static final String TAG = "BatteryBackupHelper";
55     // Definition for the device build information.
56     public static final String KEY_BUILD_BRAND = "device_build_brand";
57     public static final String KEY_BUILD_PRODUCT = "device_build_product";
58     public static final String KEY_BUILD_MANUFACTURER = "device_build_manufacture";
59     public static final String KEY_BUILD_FINGERPRINT = "device_build_fingerprint";
60     // Customized fields for device extra information.
61     public static final String KEY_BUILD_METADATA_1 = "device_build_metadata_1";
62     public static final String KEY_BUILD_METADATA_2 = "device_build_metadata_2";
63 
64     private static final String DEVICE_IDLE_SERVICE = "deviceidle";
65     private static final String BATTERY_OPTIMIZE_BACKUP_FILE_NAME =
66             "battery_optimize_backup_historical_logs";
67     private static final int DEVICE_BUILD_INFO_SIZE = 6;
68 
69     static final String DELIMITER = ",";
70     static final String DELIMITER_MODE = ":";
71     static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list";
72 
73     @VisibleForTesting
74     ArraySet<ApplicationInfo> mTestApplicationInfoList = null;
75 
76     @VisibleForTesting
77     PowerAllowlistBackend mPowerAllowlistBackend;
78     @VisibleForTesting
79     IDeviceIdleController mIDeviceIdleController;
80     @VisibleForTesting
81     IPackageManager mIPackageManager;
82     @VisibleForTesting
83     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(ParcelFileDescriptor oldState, BackupDataOutput data,
99             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 = FeatureFactory.getFactory(mContext)
115                 .getPowerUsageFeatureProvider(mContext);
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     }
154 
getFullPowerList()155     private List<String> getFullPowerList() {
156         final long timestamp = System.currentTimeMillis();
157         String[] allowlistedApps;
158         try {
159             allowlistedApps = getIDeviceIdleController().getFullPowerWhitelist();
160         } catch (RemoteException e) {
161             Log.e(TAG, "backupFullPowerList() failed", e);
162             return null;
163         }
164         // Ignores unexpected empty result case.
165         if (allowlistedApps == null || allowlistedApps.length == 0) {
166             Log.w(TAG, "no data found in the getFullPowerList()");
167             return new ArrayList<>();
168         }
169         Log.d(TAG, String.format("getFullPowerList() size=%d in %d/ms",
170                 allowlistedApps.length, (System.currentTimeMillis() - timestamp)));
171         return Arrays.asList(allowlistedApps);
172     }
173 
174     @VisibleForTesting
backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps)175     void backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps) {
176         final long timestamp = System.currentTimeMillis();
177         final ArraySet<ApplicationInfo> applications = getInstalledApplications();
178         if (applications == null || applications.isEmpty()) {
179             Log.w(TAG, "no data found in the getInstalledApplications()");
180             return;
181         }
182         int backupCount = 0;
183         final StringBuilder builder = new StringBuilder();
184         final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
185         final SharedPreferences sharedPreferences = getSharedPreferences(mContext);
186         // Converts application into the AppUsageState.
187         for (ApplicationInfo info : applications) {
188             final int mode = BatteryOptimizeUtils.getMode(appOps, info.uid, info.packageName);
189             @BatteryOptimizeUtils.OptimizationMode
190             final int optimizationMode = BatteryOptimizeUtils.getAppOptimizationMode(
191                     mode, allowlistedApps.contains(info.packageName));
192             // Ignores default optimized/unknown state or system/default apps.
193             if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED
194                     || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN
195                     || isSystemOrDefaultApp(info.packageName, info.uid)) {
196                 continue;
197             }
198             final String packageOptimizeMode =
199                     info.packageName + DELIMITER_MODE + optimizationMode;
200             builder.append(packageOptimizeMode + DELIMITER);
201             Log.d(TAG, "backupOptimizationMode: " + packageOptimizeMode);
202             BatteryOptimizeLogUtils.writeLog(
203                     sharedPreferences, Action.BACKUP, info.packageName,
204                     /* actionDescription */ "mode: " + optimizationMode);
205             backupCount++;
206         }
207 
208         writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString());
209         Log.d(TAG, String.format("backup getInstalledApplications():%d count=%d in %d/ms",
210                 applications.size(), backupCount, (System.currentTimeMillis() - timestamp)));
211     }
212 
213     @VisibleForTesting
restoreOptimizationMode(byte[] dataBytes)214     int restoreOptimizationMode(byte[] dataBytes) {
215         final long timestamp = System.currentTimeMillis();
216         final String dataContent = new String(dataBytes, StandardCharsets.UTF_8);
217         if (dataContent == null || dataContent.isEmpty()) {
218             Log.w(TAG, "no data found in the restoreOptimizationMode()");
219             return 0;
220         }
221         final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER);
222         if (appConfigurations == null || appConfigurations.length == 0) {
223             Log.w(TAG, "no data found from the split() processing");
224             return 0;
225         }
226         int restoreCount = 0;
227         for (int index = 0; index < appConfigurations.length; index++) {
228             final String[] results = appConfigurations[index]
229                     .split(BatteryBackupHelper.DELIMITER_MODE);
230             // Example format: com.android.systemui:2 we should have length=2
231             if (results == null || results.length != 2) {
232                 Log.w(TAG, "invalid raw data found:" + appConfigurations[index]);
233                 continue;
234             }
235             final String packageName = results[0];
236             final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
237             // Ignores system/default apps.
238             if (isSystemOrDefaultApp(packageName, uid)) {
239                 Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName);
240                 continue;
241             }
242             @BatteryOptimizeUtils.OptimizationMode
243             int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;
244             try {
245                 optimizationMode = Integer.parseInt(results[1]);
246             } catch (NumberFormatException e) {
247                 Log.e(TAG, "failed to parse the optimization mode: "
248                         + appConfigurations[index], e);
249                 continue;
250             }
251             restoreOptimizationMode(packageName, optimizationMode);
252             restoreCount++;
253         }
254         Log.d(TAG, String.format("restoreOptimizationMode() count=%d in %d/ms",
255                 restoreCount, (System.currentTimeMillis() - timestamp)));
256         return restoreCount;
257     }
258 
performRestoreIfNeeded()259     private void performRestoreIfNeeded() {
260         if (mOptimizationModeBytes == null || mOptimizationModeBytes.length == 0) {
261             return;
262         }
263         final PowerUsageFeatureProvider provider = FeatureFactory.getFactory(mContext)
264                 .getPowerUsageFeatureProvider(mContext);
265         if (!provider.isValidToRestoreOptimizationMode(mDeviceBuildInfoMap)) {
266             return;
267         }
268         // Start to restore the app optimization mode data.
269         final int restoreCount = restoreOptimizationMode(mOptimizationModeBytes);
270         if (restoreCount > 0) {
271             BatterySettingsMigrateChecker.verifyOptimizationModes(mContext);
272         }
273         mOptimizationModeBytes = null; // clear data
274     }
275 
276     /** Dump the app optimization mode backup history data. */
dumpHistoricalData(Context context, PrintWriter writer)277     public static void dumpHistoricalData(Context context, PrintWriter writer) {
278         BatteryOptimizeLogUtils.printBatteryOptimizeHistoricalLog(
279                 getSharedPreferences(context), writer);
280     }
281 
isOwner()282     static boolean isOwner() {
283         return UserHandle.myUserId() == UserHandle.USER_SYSTEM;
284     }
285 
newBatteryOptimizeUtils( Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils)286     static BatteryOptimizeUtils newBatteryOptimizeUtils(
287             Context context, String packageName, BatteryOptimizeUtils testOptimizeUtils) {
288         final int uid = BatteryUtils.getInstance(context).getPackageUid(packageName);
289         if (uid == BatteryUtils.UID_NULL) {
290             return null;
291         }
292         final BatteryOptimizeUtils batteryOptimizeUtils =
293                 testOptimizeUtils != null
294                         ? testOptimizeUtils /*testing only*/
295                         : new BatteryOptimizeUtils(context, uid, packageName);
296         return batteryOptimizeUtils;
297     }
298 
299     @VisibleForTesting
getSharedPreferences(Context context)300     static SharedPreferences getSharedPreferences(Context context) {
301         return context.getSharedPreferences(
302                 BATTERY_OPTIMIZE_BACKUP_FILE_NAME, Context.MODE_PRIVATE);
303     }
304 
restoreOptimizationMode( String packageName, @BatteryOptimizeUtils.OptimizationMode int mode)305     private void restoreOptimizationMode(
306             String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) {
307         final BatteryOptimizeUtils batteryOptimizeUtils =
308                 newBatteryOptimizeUtils(mContext, packageName, mBatteryOptimizeUtils);
309         if (batteryOptimizeUtils == null) {
310             return;
311         }
312         batteryOptimizeUtils.setAppUsageState(
313                 mode, BatteryOptimizeHistoricalLogEntry.Action.RESTORE);
314         Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode));
315     }
316 
317     // Provides an opportunity to inject mock IDeviceIdleController for testing.
getIDeviceIdleController()318     private IDeviceIdleController getIDeviceIdleController() {
319         if (mIDeviceIdleController != null) {
320             return mIDeviceIdleController;
321         }
322         mIDeviceIdleController = IDeviceIdleController.Stub.asInterface(
323                 ServiceManager.getService(DEVICE_IDLE_SERVICE));
324         return mIDeviceIdleController;
325     }
326 
getIPackageManager()327     private IPackageManager getIPackageManager() {
328         if (mIPackageManager != null) {
329             return mIPackageManager;
330         }
331         mIPackageManager = AppGlobals.getPackageManager();
332         return mIPackageManager;
333     }
334 
getPowerAllowlistBackend()335     private PowerAllowlistBackend getPowerAllowlistBackend() {
336         if (mPowerAllowlistBackend != null) {
337             return mPowerAllowlistBackend;
338         }
339         mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext);
340         return mPowerAllowlistBackend;
341     }
342 
isSystemOrDefaultApp(String packageName, int uid)343     private boolean isSystemOrDefaultApp(String packageName, int uid) {
344         return BatteryOptimizeUtils.isSystemOrDefaultApp(
345                 getPowerAllowlistBackend(), packageName, uid);
346     }
347 
getInstalledApplications()348     private ArraySet<ApplicationInfo> getInstalledApplications() {
349         if (mTestApplicationInfoList != null) {
350             return mTestApplicationInfoList;
351         }
352         return BatteryOptimizeUtils.getInstalledApplications(mContext, getIPackageManager());
353     }
354 
restoreBackupData(String dataKey, BackupDataInputStream data)355     private void restoreBackupData(String dataKey, BackupDataInputStream data) {
356         final byte[] dataBytes = getBackupData(dataKey, data);
357         if (dataBytes == null || dataBytes.length == 0) {
358             return;
359         }
360         final String dataContent = new String(dataBytes, StandardCharsets.UTF_8);
361         mDeviceBuildInfoMap.put(dataKey, dataContent);
362         Log.d(TAG, String.format("restore:%s:%s", dataKey, dataContent));
363     }
364 
getBackupData(String dataKey, BackupDataInputStream data)365     private static byte[] getBackupData(String dataKey, BackupDataInputStream data) {
366         final int dataSize = data.size();
367         final byte[] dataBytes = new byte[dataSize];
368         try {
369             data.read(dataBytes, 0 /*offset*/, dataSize);
370         } catch (IOException e) {
371             Log.e(TAG, "failed to getBackupData() " + dataKey, e);
372             return null;
373         }
374         return dataBytes;
375     }
376 
writeBackupData( BackupDataOutput data, String dataKey, String dataContent)377     private static void writeBackupData(
378             BackupDataOutput data, String dataKey, String dataContent) {
379         if (dataContent == null || dataContent.isEmpty()) {
380             return;
381         }
382         final byte[] dataContentBytes = dataContent.getBytes();
383         try {
384             data.writeEntityHeader(dataKey, dataContentBytes.length);
385             data.writeEntityData(dataContentBytes, dataContentBytes.length);
386         } catch (IOException e) {
387             Log.e(TAG, "writeBackupData() is failed for " + dataKey, e);
388         }
389         Log.d(TAG, String.format("backup:%s:%s", dataKey, dataContent));
390     }
391 }
392