1 /*
2  * Copyright (C) 2021 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;
18 
19 import android.annotation.IntDef;
20 import android.app.AppOpsManager;
21 import android.content.Context;
22 import android.os.AsyncTask;
23 import android.util.Log;
24 
25 import androidx.annotation.VisibleForTesting;
26 
27 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
28 
29 import java.lang.annotation.Retention;
30 import java.lang.annotation.RetentionPolicy;
31 
32 /** A utility class for application usage operation. */
33 public class BatteryOptimizeUtils {
34     private static final String TAG = "BatteryOptimizeUtils";
35     private static final String UNKNOWN_PACKAGE = "unknown";
36 
37     @VisibleForTesting AppOpsManager mAppOpsManager;
38     @VisibleForTesting BatteryUtils mBatteryUtils;
39     @VisibleForTesting PowerAllowlistBackend mPowerAllowListBackend;
40     @VisibleForTesting int mMode;
41     @VisibleForTesting boolean mAllowListed;
42 
43     private final String mPackageName;
44     private final int mUid;
45 
46     // Optimization modes.
47     static final int MODE_UNKNOWN = 0;
48     static final int MODE_RESTRICTED = 1;
49     static final int MODE_UNRESTRICTED = 2;
50     static final int MODE_OPTIMIZED = 3;
51 
52     @IntDef(prefix = {"MODE_"}, value = {
53         MODE_UNKNOWN,
54         MODE_RESTRICTED,
55         MODE_UNRESTRICTED,
56         MODE_OPTIMIZED,
57     })
58     @Retention(RetentionPolicy.SOURCE)
59     static @interface OptimizationMode {}
60 
BatteryOptimizeUtils(Context context, int uid, String packageName)61     public BatteryOptimizeUtils(Context context, int uid, String packageName) {
62         mUid = uid;
63         mPackageName = packageName;
64         mAppOpsManager = context.getSystemService(AppOpsManager.class);
65         mBatteryUtils = BatteryUtils.getInstance(context);
66         mPowerAllowListBackend = PowerAllowlistBackend.getInstance(context);
67         mMode = mAppOpsManager
68                 .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName);
69         mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName);
70     }
71 
72     /** Gets the {@link OptimizationMode} based on mode and allowed list. */
73     @OptimizationMode
getAppOptimizationMode(int mode, boolean isAllowListed)74     public static int getAppOptimizationMode(int mode, boolean isAllowListed) {
75         if (!isAllowListed && mode == AppOpsManager.MODE_IGNORED) {
76             return MODE_RESTRICTED;
77         } else if (isAllowListed && mode == AppOpsManager.MODE_ALLOWED) {
78             return MODE_UNRESTRICTED;
79         } else if (!isAllowListed && mode == AppOpsManager.MODE_ALLOWED) {
80             return MODE_OPTIMIZED;
81         } else {
82             return MODE_UNKNOWN;
83         }
84     }
85 
86     /** Gets the {@link OptimizationMode} for associated app. */
87     @OptimizationMode
getAppOptimizationMode()88     public int getAppOptimizationMode() {
89         refreshState();
90         return getAppOptimizationMode(mMode, mAllowListed);
91     }
92 
93     /** Sets the {@link OptimizationMode} for associated app. */
setAppUsageState(@ptimizationMode int mode)94     public void setAppUsageState(@OptimizationMode int mode) {
95         if (getAppOptimizationMode(mMode, mAllowListed) == mode) {
96             Log.w(TAG, "set the same optimization mode for: " + mPackageName);
97             return;
98         }
99 
100         AsyncTask.execute(() -> {
101             switch (mode) {
102                 case MODE_RESTRICTED:
103                     setAppOptimizationMode(AppOpsManager.MODE_IGNORED, /* allowListed */ false);
104                     break;
105                 case MODE_UNRESTRICTED:
106                     setAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ true);
107                     break;
108                 case MODE_OPTIMIZED:
109                     setAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false);
110                     break;
111                 default:
112                     Log.d(TAG, "set unknown app optimization mode.");
113             }
114         });
115     }
116 
117     /**
118      * Return {@code true} if package name is valid (can get an uid).
119      */
isValidPackageName()120     public boolean isValidPackageName() {
121         return mBatteryUtils.getPackageUid(mPackageName) != BatteryUtils.UID_NULL;
122     }
123 
124     /**
125      * Return {@code true} if this package is system or default active app.
126      */
isSystemOrDefaultApp()127     public boolean isSystemOrDefaultApp() {
128         mPowerAllowListBackend.refreshList();
129 
130         return mPowerAllowListBackend.isSysAllowlisted(mPackageName)
131                 || mPowerAllowListBackend.isDefaultActiveApp(mPackageName);
132     }
133 
getPackageName()134     String getPackageName() {
135         return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName;
136     }
137 
setAppOptimizationMode(int appStandbyMode, boolean allowListed)138     private void setAppOptimizationMode(int appStandbyMode, boolean allowListed) {
139         try {
140             mBatteryUtils.setForceAppStandby(mUid, mPackageName, appStandbyMode);
141             if (allowListed) {
142                 mPowerAllowListBackend.addApp(mPackageName);
143             } else {
144                 mPowerAllowListBackend.removeApp(mPackageName);
145             }
146         } catch (Exception e) {
147             Log.e(TAG, "set OPTIMIZED failed for " + mPackageName, e);
148         }
149     }
150 
refreshState()151     private void refreshState() {
152         mPowerAllowListBackend.refreshList();
153         mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName);
154         mMode = mAppOpsManager
155                 .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName);
156         Log.d(TAG, String.format("refresh %s state, allowlisted = %s, mode = %d",
157                 mPackageName,
158                 mAllowListed,
159                 mMode));
160     }
161 }
162