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