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