Merge "Extract loading of camera protection info into a separate class" into main
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 5f5cca8..e8499d3 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -17,15 +17,11 @@
package com.android.systemui
import android.content.Context
-import android.content.res.Resources
import android.graphics.Path
import android.graphics.Rect
-import android.graphics.RectF
import android.hardware.camera2.CameraManager
-import android.util.PathParser
import com.android.systemui.res.R
import java.util.concurrent.Executor
-import kotlin.math.roundToInt
/**
* Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
@@ -163,89 +159,20 @@
}
companion object Factory {
- fun build(context: Context, executor: Executor): CameraAvailabilityListener {
+ fun build(
+ context: Context,
+ executor: Executor,
+ cameraProtectionLoader: CameraProtectionLoader
+ ): CameraAvailabilityListener {
val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val res = context.resources
- val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
+ val cameraProtectionInfoList = cameraProtectionLoader.loadCameraProtectionInfoList()
val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
return CameraAvailabilityListener(manager, cameraProtectionInfoList, excluded, executor)
}
-
- private fun pathFromString(pathString: String): Path {
- val spec = pathString.trim()
- val p: Path
- try {
- p = PathParser.createPathFromPathData(spec)
- } catch (e: Throwable) {
- throw IllegalArgumentException("Invalid protection path", e)
- }
-
- return p
- }
-
- private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
- val list = mutableListOf<CameraProtectionInfo>()
- val front =
- loadCameraProtectionInfo(
- res,
- R.string.config_protectedCameraId,
- R.string.config_protectedPhysicalCameraId,
- R.string.config_frontBuiltInDisplayCutoutProtection
- )
- if (front != null) {
- list.add(front)
- }
- val inner =
- loadCameraProtectionInfo(
- res,
- R.string.config_protectedInnerCameraId,
- R.string.config_protectedInnerPhysicalCameraId,
- R.string.config_innerBuiltInDisplayCutoutProtection
- )
- if (inner != null) {
- list.add(inner)
- }
- return list
- }
-
- private fun loadCameraProtectionInfo(
- res: Resources,
- cameraIdRes: Int,
- physicalCameraIdRes: Int,
- pathRes: Int
- ): CameraProtectionInfo? {
- val logicalCameraId = res.getString(cameraIdRes)
- if (logicalCameraId.isNullOrEmpty()) {
- return null
- }
- val physicalCameraId = res.getString(physicalCameraIdRes)
- val protectionPath = pathFromString(res.getString(pathRes))
- val computed = RectF()
- protectionPath.computeBounds(computed)
- val protectionBounds =
- Rect(
- computed.left.roundToInt(),
- computed.top.roundToInt(),
- computed.right.roundToInt(),
- computed.bottom.roundToInt()
- )
- return CameraProtectionInfo(
- logicalCameraId,
- physicalCameraId,
- protectionPath,
- protectionBounds
- )
- }
}
- data class CameraProtectionInfo(
- val logicalCameraId: String,
- val physicalCameraId: String?,
- val cutoutProtectionPath: Path,
- val cutoutBounds: Rect,
- )
-
private data class OpenCameraInfo(
val logicalCameraId: String,
val packageId: String,
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
new file mode 100644
index 0000000..bbab4de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionInfo.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.systemui
+
+import android.graphics.Path
+import android.graphics.Rect
+
+data class CameraProtectionInfo(
+ val logicalCameraId: String,
+ val physicalCameraId: String?,
+ val cutoutProtectionPath: Path,
+ val cutoutBounds: Rect,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
new file mode 100644
index 0000000..8fe9389
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/CameraProtectionLoader.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 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.systemui
+
+import android.content.Context
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.util.PathParser
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlin.math.roundToInt
+
+class CameraProtectionLoader @Inject constructor(private val context: Context) {
+
+ fun loadCameraProtectionInfoList(): List<CameraProtectionInfo> {
+ val list = mutableListOf<CameraProtectionInfo>()
+ val front =
+ loadCameraProtectionInfo(
+ R.string.config_protectedCameraId,
+ R.string.config_protectedPhysicalCameraId,
+ R.string.config_frontBuiltInDisplayCutoutProtection
+ )
+ if (front != null) {
+ list.add(front)
+ }
+ val inner =
+ loadCameraProtectionInfo(
+ R.string.config_protectedInnerCameraId,
+ R.string.config_protectedInnerPhysicalCameraId,
+ R.string.config_innerBuiltInDisplayCutoutProtection
+ )
+ if (inner != null) {
+ list.add(inner)
+ }
+ return list
+ }
+
+ private fun loadCameraProtectionInfo(
+ cameraIdRes: Int,
+ physicalCameraIdRes: Int,
+ pathRes: Int
+ ): CameraProtectionInfo? {
+ val logicalCameraId = context.getString(cameraIdRes)
+ if (logicalCameraId.isNullOrEmpty()) {
+ return null
+ }
+ val physicalCameraId = context.getString(physicalCameraIdRes)
+ val protectionPath = pathFromString(context.getString(pathRes))
+ val computed = RectF()
+ protectionPath.computeBounds(computed)
+ val protectionBounds =
+ Rect(
+ computed.left.roundToInt(),
+ computed.top.roundToInt(),
+ computed.right.roundToInt(),
+ computed.bottom.roundToInt()
+ )
+ return CameraProtectionInfo(
+ logicalCameraId,
+ physicalCameraId,
+ protectionPath,
+ protectionBounds
+ )
+ }
+
+ private fun pathFromString(pathString: String): Path {
+ return try {
+ PathParser.createPathFromPathData(pathString.trim())
+ } catch (e: Throwable) {
+ throw IllegalArgumentException("Invalid protection path", e)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index d6d5c26..3e03fb8 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -146,6 +146,7 @@
private final ThreadFactory mThreadFactory;
private final DecorProviderFactory mDotFactory;
private final FaceScanningProviderFactory mFaceScanningFactory;
+ private final CameraProtectionLoader mCameraProtectionLoader;
public final int mFaceScanningViewId;
@VisibleForTesting
@@ -333,7 +334,8 @@
FaceScanningProviderFactory faceScanningFactory,
ScreenDecorationsLogger logger,
FacePropertyRepository facePropertyRepository,
- JavaAdapter javaAdapter) {
+ JavaAdapter javaAdapter,
+ CameraProtectionLoader cameraProtectionLoader) {
mContext = context;
mSecureSettings = secureSettings;
mCommandRegistry = commandRegistry;
@@ -343,6 +345,7 @@
mThreadFactory = threadFactory;
mDotFactory = dotFactory;
mFaceScanningFactory = faceScanningFactory;
+ mCameraProtectionLoader = cameraProtectionLoader;
mFaceScanningViewId = com.android.systemui.res.R.id.face_scanning_anim;
mLogger = logger;
mFacePropertyRepository = facePropertyRepository;
@@ -981,7 +984,9 @@
Resources res = mContext.getResources();
boolean enabled = res.getBoolean(R.bool.config_enableDisplayCutoutProtection);
if (enabled) {
- mCameraListener = CameraAvailabilityListener.Factory.build(mContext, mExecutor);
+ mCameraListener =
+ CameraAvailabilityListener.Factory.build(
+ mContext, mExecutor, mCameraProtectionLoader);
mCameraListener.addTransitionCallback(mCameraTransitionCallback);
mCameraListener.startListening();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
index e921a59..64cd526 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -344,10 +344,15 @@
}
private fun createAndStartSut(): CameraAvailabilityListener {
- return CameraAvailabilityListener.build(context, context.mainExecutor).also {
- it.addTransitionCallback(cameraTransitionCallback)
- it.startListening()
- }
+ return CameraAvailabilityListener.build(
+ context,
+ context.mainExecutor,
+ CameraProtectionLoader((context))
+ )
+ .also {
+ it.addTransitionCallback(cameraTransitionCallback)
+ it.startListening()
+ }
}
private class TestCameraTransitionCallback :
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
new file mode 100644
index 0000000..238e5e9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraProtectionLoaderTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 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.systemui
+
+import android.graphics.Rect
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CameraProtectionLoaderTest : SysuiTestCase() {
+
+ private val loader = CameraProtectionLoader(context)
+
+ @Before
+ fun setUp() {
+ overrideResource(R.string.config_protectedCameraId, OUTER_CAMERA_LOGICAL_ID)
+ overrideResource(R.string.config_protectedPhysicalCameraId, OUTER_CAMERA_PHYSICAL_ID)
+ overrideResource(
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ OUTER_CAMERA_PROTECTION_PATH
+ )
+ overrideResource(R.string.config_protectedInnerCameraId, INNER_CAMERA_LOGICAL_ID)
+ overrideResource(R.string.config_protectedInnerPhysicalCameraId, INNER_CAMERA_PHYSICAL_ID)
+ overrideResource(
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ INNER_CAMERA_PROTECTION_PATH
+ )
+ }
+
+ @Test
+ fun loadCameraProtectionInfoList() {
+ val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
+ assertThat(protectionInfos)
+ .containsExactly(OUTER_CAMERA_PROTECTION_INFO, INNER_CAMERA_PROTECTION_INFO)
+ }
+
+ @Test
+ fun loadCameraProtectionInfoList_outerCameraIdEmpty_onlyReturnsInnerInfo() {
+ overrideResource(R.string.config_protectedCameraId, "")
+
+ val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
+ assertThat(protectionInfos).containsExactly(INNER_CAMERA_PROTECTION_INFO)
+ }
+
+ @Test
+ fun loadCameraProtectionInfoList_innerCameraIdEmpty_onlyReturnsOuterInfo() {
+ overrideResource(R.string.config_protectedInnerCameraId, "")
+
+ val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
+ assertThat(protectionInfos).containsExactly(OUTER_CAMERA_PROTECTION_INFO)
+ }
+
+ @Test
+ fun loadCameraProtectionInfoList_innerAndOuterCameraIdsEmpty_returnsEmpty() {
+ overrideResource(R.string.config_protectedCameraId, "")
+ overrideResource(R.string.config_protectedInnerCameraId, "")
+
+ val protectionInfos = loader.loadCameraProtectionInfoList().map { it.toTestableVersion() }
+
+ assertThat(protectionInfos).isEmpty()
+ }
+
+ private fun CameraProtectionInfo.toTestableVersion() =
+ TestableProtectionInfo(logicalCameraId, physicalCameraId, cutoutBounds)
+
+ /**
+ * "Testable" version, because the original version contains a Path property, which doesn't
+ * implement equals.
+ */
+ private data class TestableProtectionInfo(
+ val logicalCameraId: String,
+ val physicalCameraId: String?,
+ val cutoutBounds: Rect,
+ )
+
+ companion object {
+ private const val OUTER_CAMERA_LOGICAL_ID = "1"
+ private const val OUTER_CAMERA_PHYSICAL_ID = "11"
+ private const val OUTER_CAMERA_PROTECTION_PATH = "M 0,0 H 10,10 V 10,10 H 0,10 Z"
+ private val OUTER_CAMERA_PROTECTION_BOUNDS =
+ Rect(/* left = */ 0, /* top = */ 0, /* right = */ 10, /* bottom = */ 10)
+ private val OUTER_CAMERA_PROTECTION_INFO =
+ TestableProtectionInfo(
+ OUTER_CAMERA_LOGICAL_ID,
+ OUTER_CAMERA_PHYSICAL_ID,
+ OUTER_CAMERA_PROTECTION_BOUNDS
+ )
+
+ private const val INNER_CAMERA_LOGICAL_ID = "2"
+ private const val INNER_CAMERA_PHYSICAL_ID = "22"
+ private const val INNER_CAMERA_PROTECTION_PATH = "M 0,0 H 20,20 V 20,20 H 0,20 Z"
+ private val INNER_CAMERA_PROTECTION_BOUNDS =
+ Rect(/* left = */ 0, /* top = */ 0, /* right = */ 20, /* bottom = */ 20)
+ private val INNER_CAMERA_PROTECTION_INFO =
+ TestableProtectionInfo(
+ INNER_CAMERA_LOGICAL_ID,
+ INNER_CAMERA_PHYSICAL_ID,
+ INNER_CAMERA_PROTECTION_BOUNDS
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index c07148b..1f1fa72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -176,6 +176,8 @@
private FakeFacePropertyRepository mFakeFacePropertyRepository =
new FakeFacePropertyRepository();
private List<DecorProvider> mMockCutoutList;
+ private final CameraProtectionLoader mCameraProtectionLoader =
+ new CameraProtectionLoader(mContext);
@Before
public void setup() {
@@ -247,7 +249,7 @@
mThreadFactory,
mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
- mFakeFacePropertyRepository, mJavaAdapter) {
+ mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader) {
@Override
public void start() {
super.start();
@@ -1243,7 +1245,7 @@
mDotViewController,
mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
- mFakeFacePropertyRepository, mJavaAdapter);
+ mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader);
screenDecorations.start();
when(mContext.getDisplay()).thenReturn(mDisplay);
when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {