diff options
author | 2023-09-05 06:15:02 +0000 | |
---|---|---|
committer | 2023-09-05 06:15:02 +0000 | |
commit | 2fd6dc217581f8a9121a20a2f6b83026edecf0c7 (patch) | |
tree | 7d865c576e9deedfe99c94426453cd04b97a7308 | |
parent | 136b05654365bf872f03857742a6edabe641d209 (diff) | |
parent | 68ef56859f8d8c83ec18f323bb2ff5e003892973 (diff) |
VRR: Allowlist for small area detection am: 68ef56859f
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24476723
Change-Id: I86fda513ded0bea124c74e09c93962157132ee3a
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
10 files changed, 411 insertions, 2 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index fe6b897f3fc0..101857057514 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5380,6 +5380,13 @@ of known compatibility issues. --> <string-array name="config_highRefreshRateBlacklist"></string-array> + <!-- The list of packages to automatically opt in to refresh rate suppressing by small area + detection. Format of this array should be packageName:threshold and threshold value should + be between 0 to 1--> + <string-array name="config_smallAreaDetectionAllowlist" translatable="false"> + <!-- Add packages:threshold here --> + </string-array> + <!-- The list of packages to force slowJpegMode for Apps using Camera API1 --> <string-array name="config_forceSlowJpegModeList" translatable="false"> <!-- Add packages here --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index e3b873b4d3bd..852dc8cceb40 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4240,6 +4240,8 @@ <java-symbol type="array" name="config_highRefreshRateBlacklist" /> <java-symbol type="array" name="config_forceSlowJpegModeList" /> + <java-symbol type="array" name="config_smallAreaDetectionAllowlist" /> + <java-symbol type="layout" name="chooser_dialog" /> <java-symbol type="layout" name="chooser_dialog_item" /> <java-symbol type="drawable" name="chooser_dialog_background" /> diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 898a3c416083..6546f6e533c0 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -468,6 +468,8 @@ public final class DisplayManagerService extends SystemService { private SensorManager mSensorManager; private BrightnessTracker mBrightnessTracker; + private SmallAreaDetectionController mSmallAreaDetectionController; + // Whether minimal post processing is allowed by the user. @GuardedBy("mSyncRoot") @@ -731,6 +733,8 @@ public final class DisplayManagerService extends SystemService { filter.addAction(Intent.ACTION_DOCK_EVENT); mContext.registerReceiver(mIdleModeReceiver, filter); + + mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext); } @VisibleForTesting @@ -3046,6 +3050,9 @@ public final class DisplayManagerService extends SystemService { pw.println(); mDisplayModeDirector.dump(pw); mBrightnessSynchronizer.dump(pw); + if (mSmallAreaDetectionController != null) { + mSmallAreaDetectionController.dump(pw); + } } private static float[] getFloatArray(TypedArray array) { diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java new file mode 100644 index 000000000000..adaa5390cb9b --- /dev/null +++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.PackageManagerInternal; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; +import android.util.ArrayMap; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.BackgroundThread; +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Map; + +final class SmallAreaDetectionController { + private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds); + private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold); + + // TODO(b/281720315): Move this to DeviceConfig once server side ready. + private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST = + "small_area_detection_allowlist"; + + private final Object mLock = new Object(); + private final Context mContext; + private final PackageManagerInternal mPackageManager; + private final UserManagerInternal mUserManager; + @GuardedBy("mLock") + private final Map<String, Float> mAllowPkgMap = new ArrayMap<>(); + // TODO(b/298722189): Update allowlist when user changes + @GuardedBy("mLock") + private int[] mUserIds; + + static SmallAreaDetectionController create(@NonNull Context context) { + final SmallAreaDetectionController controller = + new SmallAreaDetectionController(context, DeviceConfigInterface.REAL); + final String property = DeviceConfigInterface.REAL.getProperty( + DeviceConfig.NAMESPACE_DISPLAY_MANAGER, KEY_SMALL_AREA_DETECTION_ALLOWLIST); + controller.updateAllowlist(property); + return controller; + } + + @VisibleForTesting + SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) { + mContext = context; + mPackageManager = LocalServices.getService(PackageManagerInternal.class); + mUserManager = LocalServices.getService(UserManagerInternal.class); + deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + BackgroundThread.getExecutor(), + new SmallAreaDetectionController.OnPropertiesChangedListener()); + mPackageManager.getPackageList(new PackageReceiver()); + } + + @VisibleForTesting + void updateAllowlist(@Nullable String property) { + synchronized (mLock) { + mAllowPkgMap.clear(); + if (property != null) { + final String[] mapStrings = property.split(","); + for (String mapString : mapStrings) putToAllowlist(mapString); + } else { + final String[] defaultMapStrings = mContext.getResources() + .getStringArray(R.array.config_smallAreaDetectionAllowlist); + for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString); + } + updateSmallAreaDetection(); + } + } + + @GuardedBy("mLock") + private void putToAllowlist(String rowData) { + // Data format: package:threshold - e.g. "com.abc.music:0.05" + final String[] items = rowData.split(":"); + if (items.length == 2) { + try { + final String pkg = items[0]; + final float threshold = Float.valueOf(items[1]); + mAllowPkgMap.put(pkg, threshold); + } catch (Exception e) { + // Just skip if items[1] - the threshold is not parsable number + } + } + } + + @GuardedBy("mLock") + private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) { + for (int i = 0; i < mUserIds.length; i++) { + final int userId = mUserIds[i]; + final int uid = mPackageManager.getPackageUid(pkg, 0, userId); + if (uid > 0) list.put(uid, threshold); + } + } + + @GuardedBy("mLock") + private void updateSmallAreaDetection() { + if (mAllowPkgMap.isEmpty()) return; + + mUserIds = mUserManager.getUserIds(); + + final SparseArray<Float> uidThresholdList = new SparseArray<>(); + for (String pkg : mAllowPkgMap.keySet()) { + final float threshold = mAllowPkgMap.get(pkg); + updateUidListForAllUsers(uidThresholdList, pkg, threshold); + } + + final int[] uids = new int[uidThresholdList.size()]; + final float[] thresholds = new float[uidThresholdList.size()]; + for (int i = 0; i < uidThresholdList.size(); i++) { + uids[i] = uidThresholdList.keyAt(i); + thresholds[i] = uidThresholdList.valueAt(i); + } + updateSmallAreaDetection(uids, thresholds); + } + + @VisibleForTesting + void updateSmallAreaDetection(int[] uids, float[] thresholds) { + nativeUpdateSmallAreaDetection(uids, thresholds); + } + + void setSmallAreaDetectionThreshold(int uid, float threshold) { + nativeSetSmallAreaDetectionThreshold(uid, threshold); + } + + void dump(PrintWriter pw) { + pw.println("Small area detection allowlist"); + pw.println(" Packages:"); + synchronized (mLock) { + for (String pkg : mAllowPkgMap.keySet()) { + pw.println(" " + pkg + " threshold = " + mAllowPkgMap.get(pkg)); + } + pw.println(" mUserIds=" + Arrays.toString(mUserIds)); + } + } + + private class OnPropertiesChangedListener implements DeviceConfig.OnPropertiesChangedListener { + public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { + if (properties.getKeyset().contains(KEY_SMALL_AREA_DETECTION_ALLOWLIST)) { + updateAllowlist( + properties.getString(KEY_SMALL_AREA_DETECTION_ALLOWLIST, null /*default*/)); + } + } + } + + private final class PackageReceiver implements PackageManagerInternal.PackageListObserver { + @Override + public void onPackageAdded(@NonNull String packageName, int uid) { + synchronized (mLock) { + if (mAllowPkgMap.containsKey(packageName)) { + setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName)); + } + } + } + } +} diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 6f2455e073a7..a0a7adb910fd 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -41,6 +41,7 @@ cc_library_static { "com_android_server_companion_virtual_InputController.cpp", "com_android_server_devicepolicy_CryptoTestHelper.cpp", "com_android_server_display_DisplayControl.cpp", + "com_android_server_display_SmallAreaDetectionController.cpp", "com_android_server_connectivity_Vpn.cpp", "com_android_server_gpu_GpuService.cpp", "com_android_server_HardwarePropertiesManagerService.cpp", diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp new file mode 100644 index 000000000000..b256f168f2af --- /dev/null +++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "SmallAreaDetectionController" + +#include <gui/SurfaceComposerClient.h> +#include <nativehelper/JNIHelp.h> +#include <nativehelper/ScopedPrimitiveArray.h> + +#include "jni.h" +#include "utils/Log.h" + +namespace android { +static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids, + jfloatArray jthresholds) { + if (juids == nullptr || jthresholds == nullptr) return; + + ScopedIntArrayRO uids(env, juids); + ScopedFloatArrayRO thresholds(env, jthresholds); + + if (uids.size() != thresholds.size()) { + ALOGE("uids size exceeds thresholds size!"); + return; + } + + std::vector<int32_t> uidVector; + std::vector<float> thresholdVector; + size_t size = uids.size(); + uidVector.reserve(size); + thresholdVector.reserve(size); + for (int i = 0; i < size; i++) { + uidVector.push_back(static_cast<int32_t>(uids[i])); + thresholdVector.push_back(static_cast<float>(thresholds[i])); + } + SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector); +} + +static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid, + jfloat threshold) { + SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold); +} + +static const JNINativeMethod gMethods[] = { + {"nativeUpdateSmallAreaDetection", "([I[F)V", (void*)nativeUpdateSmallAreaDetection}, + {"nativeSetSmallAreaDetectionThreshold", "(IF)V", + (void*)nativeSetSmallAreaDetectionThreshold}, +}; + +int register_android_server_display_smallAreaDetectionController(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/display/SmallAreaDetectionController", + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 290ad8de9547..63c3b4d1834c 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -66,6 +66,7 @@ int register_android_server_app_GameManagerService(JNIEnv* env); int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env); int register_com_android_server_display_DisplayControl(JNIEnv* env); int register_com_android_server_SystemClockTime(JNIEnv* env); +int register_android_server_display_smallAreaDetectionController(JNIEnv* env); }; using namespace android; @@ -124,5 +125,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_com_android_server_wm_TaskFpsCallbackController(env); register_com_android_server_display_DisplayControl(env); register_com_android_server_SystemClockTime(env); + register_android_server_display_smallAreaDetectionController(env); return JNI_VERSION_1_4; } diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index 4f555d9cc953..2397850dc745 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -34,6 +34,7 @@ android_test { "mockingservicestests-utils-mockito", "platform-compat-test-rules", "platform-test-annotations", + "service-permission.stubs.system_server", "services.core", "servicestests-utils", "testables", diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 50cf1696ba83..14586aef2c44 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -58,6 +58,7 @@ import android.compat.testing.PlatformCompatChangeRule; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.PackageManager; +import android.content.pm.PackageManagerInternal; import android.content.res.Resources; import android.graphics.Insets; import android.graphics.Rect; @@ -106,6 +107,7 @@ import com.android.server.display.DisplayManagerService.DeviceStateListener; import com.android.server.display.DisplayManagerService.SyncRoot; import com.android.server.input.InputManagerInternal; import com.android.server.lights.LightsManager; +import com.android.server.pm.UserManagerInternal; import com.android.server.sensors.SensorManagerInternal; import com.android.server.wm.WindowManagerInternal; @@ -254,10 +256,11 @@ public class DisplayManagerServiceTest { @Mock LocalDisplayAdapter.SurfaceControlProxy mSurfaceControlProxy; @Mock IBinder mMockDisplayToken; @Mock SensorManagerInternal mMockSensorManagerInternal; - @Mock SensorManager mSensorManager; - @Mock DisplayDeviceConfig mMockDisplayDeviceConfig; + @Mock PackageManagerInternal mMockPackageManagerInternal; + @Mock UserManagerInternal mMockUserManagerInternal; + @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor; @@ -276,6 +279,10 @@ public class DisplayManagerServiceTest { LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class); LocalServices.addService( VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal); + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); // TODO: b/287945043 mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java new file mode 100644 index 000000000000..1ce79a5b596b --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import static android.os.Process.INVALID_UID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContextWrapper; +import android.content.pm.PackageManagerInternal; +import android.provider.DeviceConfigInterface; + +import androidx.test.core.app.ApplicationProvider; +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.pm.UserManagerInternal; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class SmallAreaDetectionControllerTest { + + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + @Mock + private PackageManagerInternal mMockPackageManagerInternal; + @Mock + private UserManagerInternal mMockUserManagerInternal; + + private SmallAreaDetectionController mSmallAreaDetectionController; + + private static final String PKG_A = "com.a.b.c"; + private static final String PKG_B = "com.d.e.f"; + private static final String PKG_NOT_INSTALLED = "com.not.installed"; + private static final float THRESHOLD_A = 0.05f; + private static final float THRESHOLD_B = 0.07f; + private static final int USER_1 = 110; + private static final int USER_2 = 111; + private static final int UID_A_1 = 11011111; + private static final int UID_A_2 = 11111111; + private static final int UID_B_1 = 11022222; + private static final int UID_B_2 = 11122222; + + @Before + public void setup() { + LocalServices.removeServiceForTest(PackageManagerInternal.class); + LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal); + LocalServices.removeServiceForTest(UserManagerInternal.class); + LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal); + + when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2}); + when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1); + when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2); + when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1); + when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2); + when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn( + INVALID_UID); + when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn( + INVALID_UID); + + mSmallAreaDetectionController = spy(new SmallAreaDetectionController( + new ContextWrapper(ApplicationProvider.getApplicationContext()), + DeviceConfigInterface.REAL)); + doNothing().when(mSmallAreaDetectionController).updateSmallAreaDetection(any(), any()); + } + + @Test + public void testUpdateAllowlist_validProperty() { + final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_includeInvalidRow() { + final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_B_1, UID_B_2}; + final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_includeNotInstalledPkg() { + final String property = + PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B; + mSmallAreaDetectionController.updateAllowlist(property); + + final int[] resultUidArray = {UID_A_1, UID_A_2}; + final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A}; + verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray), + eq(resultThresholdArray)); + } + + @Test + public void testUpdateAllowlist_invalidProperty() { + final String property = PKG_A; + mSmallAreaDetectionController.updateAllowlist(property); + + verify(mSmallAreaDetectionController, never()).updateSmallAreaDetection(any(), any()); + } +} |