Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fd6f1b6d authored by Shawn Lin's avatar Shawn Lin Committed by Android (Google) Code Review
Browse files

Merge "Support face auth animation for inner display" into main

parents c976448a 12acedf2
Loading
Loading
Loading
Loading
+11 −5
Original line number Diff line number Diff line
@@ -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>
+64 −19
Original line number Diff line number Diff line
@@ -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) {
                    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
    )
}
+162 −0
Original line number Diff line number Diff line
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))
    }
}