• 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.pm.ApplicationInfo;
26 import android.content.pm.IPackageManager;
27 import android.os.Build;
28 import android.os.IDeviceIdleController;
29 import android.os.ParcelFileDescriptor;
30 import android.os.RemoteException;
31 import android.os.ServiceManager;
32 import android.os.UserHandle;
33 import android.util.ArraySet;
34 import android.util.Log;
35 
36 import androidx.annotation.VisibleForTesting;
37 
38 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
39 
40 import java.io.IOException;
41 import java.nio.charset.StandardCharsets;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.List;
45 
46 /** An implementation to backup and restore battery configurations. */
47 public final class BatteryBackupHelper implements BackupHelper {
48     /** An inditifier for {@link BackupHelper}. */
49     public static final String TAG = "BatteryBackupHelper";
50     private static final String DEVICE_IDLE_SERVICE = "deviceidle";
51     private static final boolean DEBUG = Build.TYPE.equals("userdebug");
52 
53     static final String DELIMITER = ",";
54     static final String DELIMITER_MODE = ":";
55     static final String KEY_FULL_POWER_LIST = "full_power_list";
56     static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list";
57 
58     @VisibleForTesting
59     ArraySet<ApplicationInfo> mTestApplicationInfoList = null;
60 
61     @VisibleForTesting
62     PowerAllowlistBackend mPowerAllowlistBackend;
63     @VisibleForTesting
64     IDeviceIdleController mIDeviceIdleController;
65     @VisibleForTesting
66     IPackageManager mIPackageManager;
67     @VisibleForTesting
68     BatteryOptimizeUtils mBatteryOptimizeUtils;
69 
70     private final Context mContext;
71 
BatteryBackupHelper(Context context)72     public BatteryBackupHelper(Context context) {
73         mContext = context.getApplicationContext();
74     }
75 
76     @Override
performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState)77     public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
78             ParcelFileDescriptor newState) {
79         if (!isOwner() || data == null) {
80             Log.w(TAG, "ignore performBackup() for non-owner or empty data");
81             return;
82         }
83         final List<String> allowlistedApps = backupFullPowerList(data);
84         if (allowlistedApps != null) {
85             backupOptimizationMode(data, allowlistedApps);
86         }
87     }
88 
89     @Override
restoreEntity(BackupDataInputStream data)90     public void restoreEntity(BackupDataInputStream data) {
91         if (!isOwner() || data == null || data.size() == 0) {
92             Log.w(TAG, "ignore restoreEntity() for non-owner or empty data");
93             return;
94         }
95         if (KEY_OPTIMIZATION_LIST.equals(data.getKey())) {
96             final int dataSize = data.size();
97             final byte[] dataBytes = new byte[dataSize];
98             try {
99                 data.read(dataBytes, 0 /*offset*/, dataSize);
100             } catch (IOException e) {
101                 Log.e(TAG, "failed to load BackupDataInputStream", e);
102                 return;
103             }
104             restoreOptimizationMode(dataBytes);
105         }
106     }
107 
108     @Override
writeNewStateDescription(ParcelFileDescriptor newState)109     public void writeNewStateDescription(ParcelFileDescriptor newState) {
110     }
111 
backupFullPowerList(BackupDataOutput data)112     private List<String> backupFullPowerList(BackupDataOutput data) {
113         final long timestamp = System.currentTimeMillis();
114         String[] allowlistedApps;
115         try {
116             allowlistedApps = getIDeviceIdleController().getFullPowerWhitelist();
117         } catch (RemoteException e) {
118             Log.e(TAG, "backupFullPowerList() failed", e);
119             return null;
120         }
121         // Ignores unexpected emptty result case.
122         if (allowlistedApps == null || allowlistedApps.length == 0) {
123             Log.w(TAG, "no data found in the getFullPowerList()");
124             return new ArrayList<>();
125         }
126 
127         final String allowedApps = String.join(DELIMITER, allowlistedApps);
128         writeBackupData(data, KEY_FULL_POWER_LIST, allowedApps);
129         Log.d(TAG, String.format("backup getFullPowerList() size=%d in %d/ms",
130                 allowlistedApps.length, (System.currentTimeMillis() - timestamp)));
131         return Arrays.asList(allowlistedApps);
132     }
133 
134     @VisibleForTesting
backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps)135     void backupOptimizationMode(BackupDataOutput data, List<String> allowlistedApps) {
136         final long timestamp = System.currentTimeMillis();
137         final ArraySet<ApplicationInfo> applications = getInstalledApplications();
138         if (applications == null || applications.isEmpty()) {
139             Log.w(TAG, "no data found in the getInstalledApplications()");
140             return;
141         }
142         int backupCount = 0;
143         final StringBuilder builder = new StringBuilder();
144         final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
145         // Converts application into the AppUsageState.
146         for (ApplicationInfo info : applications) {
147             final int mode = appOps.checkOpNoThrow(
148                     AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName);
149             @BatteryOptimizeUtils.OptimizationMode
150             final int optimizationMode = BatteryOptimizeUtils.getAppOptimizationMode(
151                     mode, allowlistedApps.contains(info.packageName));
152             // Ignores default optimized/unknown state or system/default apps.
153             if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED
154                     || optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN
155                     || isSystemOrDefaultApp(info.packageName)) {
156                 continue;
157             }
158             final String packageOptimizeMode =
159                     info.packageName + DELIMITER_MODE + optimizationMode;
160             builder.append(packageOptimizeMode + DELIMITER);
161             debugLog(packageOptimizeMode);
162             backupCount++;
163         }
164 
165         writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString());
166         Log.d(TAG, String.format("backup getInstalledApplications():%d count=%d in %d/ms",
167                 applications.size(), backupCount, (System.currentTimeMillis() - timestamp)));
168     }
169 
170     @VisibleForTesting
restoreOptimizationMode(byte[] dataBytes)171     void restoreOptimizationMode(byte[] dataBytes) {
172         final long timestamp = System.currentTimeMillis();
173         final String dataContent = new String(dataBytes, StandardCharsets.UTF_8);
174         if (dataContent == null || dataContent.isEmpty()) {
175             Log.w(TAG, "no data found in the restoreOptimizationMode()");
176             return;
177         }
178         final String[] appConfigurations = dataContent.split(BatteryBackupHelper.DELIMITER);
179         if (appConfigurations == null || appConfigurations.length == 0) {
180             Log.w(TAG, "no data found from the split() processing");
181             return;
182         }
183         int restoreCount = 0;
184         for (int index = 0; index < appConfigurations.length; index++) {
185             final String[] results = appConfigurations[index]
186                     .split(BatteryBackupHelper.DELIMITER_MODE);
187             // Example format: com.android.systemui:2 we should have length=2
188             if (results == null || results.length != 2) {
189                 Log.w(TAG, "invalid raw data found:" + appConfigurations[index]);
190                 continue;
191             }
192             final String packageName = results[0];
193             // Ignores system/default apps.
194             if (isSystemOrDefaultApp(packageName)) {
195                 Log.w(TAG, "ignore from isSystemOrDefaultApp():" + packageName);
196                 continue;
197             }
198             @BatteryOptimizeUtils.OptimizationMode
199             int optimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;
200             try {
201                 optimizationMode = Integer.parseInt(results[1]);
202             } catch (NumberFormatException e) {
203                 Log.e(TAG, "failed to parse the optimization mode: "
204                         + appConfigurations[index], e);
205                 continue;
206             }
207             restoreOptimizationMode(packageName, optimizationMode);
208             restoreCount++;
209         }
210         Log.d(TAG, String.format("restoreOptimizationMode() count=%d in %d/ms",
211                 restoreCount, (System.currentTimeMillis() - timestamp)));
212     }
213 
restoreOptimizationMode( String packageName, @BatteryOptimizeUtils.OptimizationMode int mode)214     private void restoreOptimizationMode(
215             String packageName, @BatteryOptimizeUtils.OptimizationMode int mode) {
216         final int uid = BatteryUtils.getInstance(mContext).getPackageUid(packageName);
217         if (uid == BatteryUtils.UID_NULL) {
218             return;
219         }
220         final BatteryOptimizeUtils batteryOptimizeUtils =
221                 mBatteryOptimizeUtils != null
222                         ? mBatteryOptimizeUtils /*testing only*/
223                         : new BatteryOptimizeUtils(mContext, uid, packageName);
224         batteryOptimizeUtils.setAppUsageState(mode);
225         Log.d(TAG, String.format("restore:%s mode=%d", packageName, mode));
226     }
227 
228     // Provides an opportunity to inject mock IDeviceIdleController for testing.
getIDeviceIdleController()229     private IDeviceIdleController getIDeviceIdleController() {
230         if (mIDeviceIdleController != null) {
231             return mIDeviceIdleController;
232         }
233         mIDeviceIdleController = IDeviceIdleController.Stub.asInterface(
234                 ServiceManager.getService(DEVICE_IDLE_SERVICE));
235         return mIDeviceIdleController;
236     }
237 
getIPackageManager()238     private IPackageManager getIPackageManager() {
239         if (mIPackageManager != null) {
240             return mIPackageManager;
241         }
242         mIPackageManager = AppGlobals.getPackageManager();
243         return mIPackageManager;
244     }
245 
getPowerAllowlistBackend()246     private PowerAllowlistBackend getPowerAllowlistBackend() {
247         if (mPowerAllowlistBackend != null) {
248             return mPowerAllowlistBackend;
249         }
250         mPowerAllowlistBackend = PowerAllowlistBackend.getInstance(mContext);
251         return mPowerAllowlistBackend;
252     }
253 
isSystemOrDefaultApp(String packageName)254     private boolean isSystemOrDefaultApp(String packageName) {
255         final PowerAllowlistBackend powerAllowlistBackend = getPowerAllowlistBackend();
256         return powerAllowlistBackend.isSysAllowlisted(packageName)
257                 || powerAllowlistBackend.isDefaultActiveApp(packageName);
258     }
259 
getInstalledApplications()260     private ArraySet<ApplicationInfo> getInstalledApplications() {
261         if (mTestApplicationInfoList != null) {
262             return mTestApplicationInfoList;
263         }
264         return BatteryOptimizeUtils.getInstalledApplications(mContext, getIPackageManager());
265     }
266 
debugLog(String debugContent)267     private void debugLog(String debugContent) {
268         if (DEBUG) Log.d(TAG, debugContent);
269     }
270 
writeBackupData( BackupDataOutput data, String dataKey, String dataContent)271     private static void writeBackupData(
272             BackupDataOutput data, String dataKey, String dataContent) {
273         final byte[] dataContentBytes = dataContent.getBytes();
274         try {
275             data.writeEntityHeader(dataKey, dataContentBytes.length);
276             data.writeEntityData(dataContentBytes, dataContentBytes.length);
277         } catch (IOException e) {
278             Log.e(TAG, "writeBackupData() is failed for " + dataKey, e);
279         }
280     }
281 
isOwner()282     private static boolean isOwner() {
283         return UserHandle.myUserId() == UserHandle.USER_OWNER;
284     }
285 }
286