1 /*
2  * Copyright (C) 2023 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.server.display;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.content.pm.PackageManagerInternal;
23 import android.provider.DeviceConfig;
24 import android.provider.DeviceConfigInterface;
25 import android.util.ArrayMap;
26 import android.util.SparseArray;
27 
28 import com.android.internal.R;
29 import com.android.internal.annotations.GuardedBy;
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.internal.os.BackgroundThread;
32 import com.android.server.LocalServices;
33 import com.android.server.pm.UserManagerInternal;
34 
35 import java.io.PrintWriter;
36 import java.util.Arrays;
37 import java.util.Map;
38 
39 final class SmallAreaDetectionController {
nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds)40     private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds);
nativeSetSmallAreaDetectionThreshold(int uid, float threshold)41     private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold);
42 
43     // TODO(b/281720315): Move this to DeviceConfig once server side ready.
44     private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST =
45             "small_area_detection_allowlist";
46 
47     private final Object mLock = new Object();
48     private final Context mContext;
49     private final PackageManagerInternal mPackageManager;
50     private final UserManagerInternal mUserManager;
51     @GuardedBy("mLock")
52     private final Map<String, Float> mAllowPkgMap = new ArrayMap<>();
53     // TODO(b/298722189): Update allowlist when user changes
54     @GuardedBy("mLock")
55     private int[] mUserIds;
56 
create(@onNull Context context)57     static SmallAreaDetectionController create(@NonNull Context context) {
58         final SmallAreaDetectionController controller =
59                 new SmallAreaDetectionController(context, DeviceConfigInterface.REAL);
60         final String property = DeviceConfigInterface.REAL.getProperty(
61                 DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_SMALL_AREA_DETECTION_ALLOWLIST);
62         controller.updateAllowlist(property);
63         return controller;
64     }
65 
66     @VisibleForTesting
SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig)67     SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
68         mContext = context;
69         mPackageManager = LocalServices.getService(PackageManagerInternal.class);
70         mUserManager = LocalServices.getService(UserManagerInternal.class);
71         deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
72                 BackgroundThread.getExecutor(),
73                 new SmallAreaDetectionController.OnPropertiesChangedListener());
74         mPackageManager.getPackageList(new PackageReceiver());
75     }
76 
77     @VisibleForTesting
updateAllowlist(@ullable String property)78     void updateAllowlist(@Nullable String property) {
79         synchronized (mLock) {
80             mAllowPkgMap.clear();
81             if (property != null) {
82                 final String[] mapStrings = property.split(",");
83                 for (String mapString : mapStrings) putToAllowlist(mapString);
84             } else {
85                 final String[] defaultMapStrings = mContext.getResources()
86                         .getStringArray(R.array.config_smallAreaDetectionAllowlist);
87                 for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);
88             }
89             updateSmallAreaDetection();
90         }
91     }
92 
93     @GuardedBy("mLock")
putToAllowlist(String rowData)94     private void putToAllowlist(String rowData) {
95         // Data format: package:threshold - e.g. "com.abc.music:0.05"
96         final String[] items = rowData.split(":");
97         if (items.length == 2) {
98             try {
99                 final String pkg = items[0];
100                 final float threshold = Float.valueOf(items[1]);
101                 mAllowPkgMap.put(pkg, threshold);
102             } catch (Exception e) {
103                 // Just skip if items[1] - the threshold is not parsable number
104             }
105         }
106     }
107 
108     @GuardedBy("mLock")
updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold)109     private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) {
110         for (int i = 0; i < mUserIds.length; i++) {
111             final int userId = mUserIds[i];
112             final int uid = mPackageManager.getPackageUid(pkg, 0, userId);
113             if (uid > 0) list.put(uid, threshold);
114         }
115     }
116 
117     @GuardedBy("mLock")
updateSmallAreaDetection()118     private void updateSmallAreaDetection() {
119         if (mAllowPkgMap.isEmpty()) return;
120 
121         mUserIds = mUserManager.getUserIds();
122 
123         final SparseArray<Float> uidThresholdList = new SparseArray<>();
124         for (String pkg : mAllowPkgMap.keySet()) {
125             final float threshold = mAllowPkgMap.get(pkg);
126             updateUidListForAllUsers(uidThresholdList, pkg, threshold);
127         }
128 
129         final int[] uids = new int[uidThresholdList.size()];
130         final float[] thresholds = new float[uidThresholdList.size()];
131         for (int i = 0; i < uidThresholdList.size();  i++) {
132             uids[i] = uidThresholdList.keyAt(i);
133             thresholds[i] = uidThresholdList.valueAt(i);
134         }
135         updateSmallAreaDetection(uids, thresholds);
136     }
137 
138     @VisibleForTesting
updateSmallAreaDetection(int[] uids, float[] thresholds)139     void updateSmallAreaDetection(int[] uids, float[] thresholds) {
140         nativeUpdateSmallAreaDetection(uids, thresholds);
141     }
142 
setSmallAreaDetectionThreshold(int uid, float threshold)143     void setSmallAreaDetectionThreshold(int uid, float threshold) {
144         nativeSetSmallAreaDetectionThreshold(uid, threshold);
145     }
146 
dump(PrintWriter pw)147     void dump(PrintWriter pw) {
148         pw.println("Small area detection allowlist");
149         pw.println("  Packages:");
150         synchronized (mLock) {
151             for (String pkg : mAllowPkgMap.keySet()) {
152                 pw.println("    " + pkg + " threshold = " + mAllowPkgMap.get(pkg));
153             }
154             pw.println("  mUserIds=" + Arrays.toString(mUserIds));
155         }
156     }
157 
158     private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener {
onPropertiesChanged(@onNull DeviceConfig.Properties properties)159         public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
160             if (properties.getKeyset().contains(KEY_SMALL_AREA_DETECTION_ALLOWLIST)) {
161                 updateAllowlist(
162                         properties.getString(KEY_SMALL_AREA_DETECTION_ALLOWLIST, null /*default*/));
163             }
164         }
165     }
166 
167     private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
168         @Override
onPackageAdded(@onNull String packageName, int uid)169         public void onPackageAdded(@NonNull String packageName, int uid) {
170             synchronized (mLock) {
171                 if (mAllowPkgMap.containsKey(packageName)) {
172                     setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName));
173                 }
174             }
175         }
176     }
177 }
178