summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/values/config.xml16
-rw-r--r--packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt162
3 files changed, 237 insertions, 24 deletions
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 1add90ff4083..09918842f754 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -531,19 +531,25 @@
</string>
<!-- A path similar to frameworks/base/core/res/res/values/config.xml
- config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a display
- cutout. If present as well as config_enableDisplayCutoutProtection is set to true, then
- SystemUI will draw this "protection path" instead of the display cutout path that is normally
- used for anti-aliasing.
+ config_mainBuiltInDisplayCutout that describes a path larger than the exact path of a outer
+ display cutout. If present as well as config_enableDisplayCutoutProtection is set to true,
+ then SystemUI will draw this "protection path" instead of the display cutout path that is
+ normally used for anti-aliasing.
This path will only be drawn when the front-facing camera turns on, otherwise the main
DisplayCutout path will be rendered
-->
<string translatable="false" name="config_frontBuiltInDisplayCutoutProtection"></string>
- <!-- ID for the camera that needs extra protection -->
+ <!-- ID for the camera of outer display that needs extra protection -->
<string translatable="false" name="config_protectedCameraId"></string>
+ <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
+ <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
+
+ <!-- ID for the camera of inner display that needs extra protection -->
+ <string translatable="false" name="config_protectedInnerCameraId"></string>
+
<!-- Comma-separated list of packages to exclude from camera protection e.g.
"com.android.systemui,com.android.xyz" -->
<string translatable="false" name="config_cameraProtectionExcludedPackages"></string>
diff --git a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
index 2d2ebe99d651..d33d279023da 100644
--- a/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt
@@ -17,6 +17,7 @@
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
@@ -33,37 +34,32 @@ import kotlin.math.roundToInt
*/
class CameraAvailabilityListener(
private val cameraManager: CameraManager,
- private val cutoutProtectionPath: Path,
- private val targetCameraId: String,
+ private val cameraProtectionInfoList: List<CameraProtectionInfo>,
excludedPackages: String,
private val executor: Executor
) {
- private var cutoutBounds = Rect()
private val excludedPackageIds: Set<String>
private val listeners = mutableListOf<CameraTransitionCallback>()
private val availabilityCallback: CameraManager.AvailabilityCallback =
object : CameraManager.AvailabilityCallback() {
override fun onCameraClosed(cameraId: String) {
- if (targetCameraId == cameraId) {
- notifyCameraInactive()
+ cameraProtectionInfoList.forEach {
+ if (cameraId == it.cameraId) {
+ notifyCameraInactive()
+ }
}
}
override fun onCameraOpened(cameraId: String, packageId: String) {
- if (targetCameraId == cameraId && !isExcluded(packageId)) {
- notifyCameraActive()
+ cameraProtectionInfoList.forEach {
+ if (cameraId == it.cameraId && !isExcluded(packageId)) {
+ notifyCameraActive(it)
+ }
}
}
}
init {
- val computed = RectF()
- cutoutProtectionPath.computeBounds(computed, false /* unused */)
- cutoutBounds.set(
- computed.left.roundToInt(),
- computed.top.roundToInt(),
- computed.right.roundToInt(),
- computed.bottom.roundToInt())
excludedPackageIds = excludedPackages.split(",").toSet()
}
@@ -100,8 +96,10 @@ class CameraAvailabilityListener(
cameraManager.unregisterAvailabilityCallback(availabilityCallback)
}
- private fun notifyCameraActive() {
- listeners.forEach { it.onApplyCameraProtection(cutoutProtectionPath, cutoutBounds) }
+ private fun notifyCameraActive(info: CameraProtectionInfo) {
+ listeners.forEach {
+ it.onApplyCameraProtection(info.cutoutProtectionPath, info.cutoutBounds)
+ }
}
private fun notifyCameraInactive() {
@@ -121,12 +119,11 @@ class CameraAvailabilityListener(
val manager = context
.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val res = context.resources
- val pathString = res.getString(R.string.config_frontBuiltInDisplayCutoutProtection)
- val cameraId = res.getString(R.string.config_protectedCameraId)
+ val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
return CameraAvailabilityListener(
- manager, pathFromString(pathString), cameraId, excluded, executor)
+ manager, cameraProtectionInfoList, excluded, executor)
}
private fun pathFromString(pathString: String): Path {
@@ -140,5 +137,53 @@ class CameraAvailabilityListener(
return p
}
+
+ private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
+ val list = mutableListOf<CameraProtectionInfo>()
+ val front = loadCameraProtectionInfo(
+ res,
+ R.string.config_protectedCameraId,
+ R.string.config_frontBuiltInDisplayCutoutProtection
+ )
+ if (front != null) {
+ list.add(front)
+ }
+ val inner = loadCameraProtectionInfo(
+ res,
+ R.string.config_protectedInnerCameraId,
+ R.string.config_innerBuiltInDisplayCutoutProtection
+ )
+ if (inner != null) {
+ list.add(inner)
+ }
+ return list
+ }
+
+ private fun loadCameraProtectionInfo(
+ res: Resources,
+ cameraIdRes: Int,
+ pathRes: Int
+ ): CameraProtectionInfo? {
+ val cameraId = res.getString(cameraIdRes)
+ if (cameraId == null || cameraId.isEmpty()) {
+ return null
+ }
+ 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(cameraId, protectionPath, protectionBounds)
+ }
}
+
+ data class CameraProtectionInfo (
+ val cameraId: String,
+ val cutoutProtectionPath: Path,
+ val cutoutBounds: Rect
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
new file mode 100644
index 000000000000..b1421b21b377
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt
@@ -0,0 +1,162 @@
+package com.android.systemui
+
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.hardware.camera2.CameraManager
+import android.testing.AndroidTestingRunner
+import android.util.PathParser
+import androidx.test.filters.SmallTest
+import com.android.systemui.res.R
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.concurrent.Executor
+import kotlin.math.roundToInt
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class CameraAvailabilityListenerTest : SysuiTestCase() {
+ companion object {
+ const val EXCLUDED_PKG = "test.excluded.package"
+ const val CAMERA_ID_FRONT = "0"
+ const val CAMERA_ID_INNER = "1"
+ const val PROTECTION_PATH_STRING_FRONT = "M 50,50 a 20,20 0 1 0 40,0 a 20,20 0 1 0 -40,0 Z"
+ const val PROTECTION_PATH_STRING_INNER = "M 40,40 a 10,10 0 1 0 20,0 a 10,10 0 1 0 -20,0 Z"
+ val PATH_RECT_FRONT = rectFromPath(pathFromString(PROTECTION_PATH_STRING_FRONT))
+ val PATH_RECT_INNER = rectFromPath(pathFromString(PROTECTION_PATH_STRING_INNER))
+
+ 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 rectFromPath(path: Path): Rect {
+ val computed = RectF()
+ path.computeBounds(computed)
+ return Rect(
+ computed.left.roundToInt(),
+ computed.top.roundToInt(),
+ computed.right.roundToInt(),
+ computed.bottom.roundToInt()
+ )
+ }
+ }
+
+ @Mock private lateinit var cameraManager: CameraManager
+ @Mock
+ private lateinit var cameraTransitionCb: CameraAvailabilityListener.CameraTransitionCallback
+ private lateinit var cameraAvailabilityListener: CameraAvailabilityListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_cameraProtectionExcludedPackages, EXCLUDED_PKG)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_protectedCameraId, CAMERA_ID_FRONT)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(
+ R.string.config_frontBuiltInDisplayCutoutProtection,
+ PROTECTION_PATH_STRING_FRONT
+ )
+ context
+ .getOrCreateTestableResources()
+ .addOverride(R.string.config_protectedInnerCameraId, CAMERA_ID_INNER)
+ context
+ .getOrCreateTestableResources()
+ .addOverride(
+ R.string.config_innerBuiltInDisplayCutoutProtection,
+ PROTECTION_PATH_STRING_INNER
+ )
+
+ context.addMockSystemService(CameraManager::class.java, cameraManager)
+
+ cameraAvailabilityListener =
+ CameraAvailabilityListener.Factory.build(context, context.mainExecutor)
+ }
+
+ @Test
+ fun testFrontCamera() {
+ var path: Path? = null
+ var rect: Rect? = null
+ val callback =
+ object : CameraAvailabilityListener.CameraTransitionCallback {
+ override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+ path = protectionPath
+ rect = bounds
+ }
+
+ override fun onHideCameraProtection() {}
+ }
+
+ cameraAvailabilityListener.addTransitionCallback(callback)
+ cameraAvailabilityListener.startListening()
+
+ val callbackCaptor = withArgCaptor {
+ verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+ }
+
+ callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, "")
+ assertNotNull(path)
+ assertEquals(PATH_RECT_FRONT, rect)
+ }
+
+ @Test
+ fun testInnerCamera() {
+ var path: Path? = null
+ var rect: Rect? = null
+ val callback =
+ object : CameraAvailabilityListener.CameraTransitionCallback {
+ override fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) {
+ path = protectionPath
+ rect = bounds
+ }
+
+ override fun onHideCameraProtection() {}
+ }
+
+ cameraAvailabilityListener.addTransitionCallback(callback)
+ cameraAvailabilityListener.startListening()
+
+ val callbackCaptor = withArgCaptor {
+ verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+ }
+
+ callbackCaptor.onCameraOpened(CAMERA_ID_INNER, "")
+ assertNotNull(path)
+ assertEquals(PATH_RECT_INNER, rect)
+ }
+
+ @Test
+ fun testExcludedPackage() {
+ cameraAvailabilityListener.addTransitionCallback(cameraTransitionCb)
+ cameraAvailabilityListener.startListening()
+
+ val callbackCaptor = withArgCaptor {
+ verify(cameraManager).registerAvailabilityCallback(any(Executor::class.java), capture())
+ }
+ callbackCaptor.onCameraOpened(CAMERA_ID_FRONT, EXCLUDED_PKG)
+
+ verify(cameraTransitionCb, never())
+ .onApplyCameraProtection(any(Path::class.java), any(Rect::class.java))
+ }
+}