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

Commit 50844eec authored by Chris Göllner's avatar Chris Göllner
Browse files

[Status Bar][Camera cutout protection] Add support for multiple displays

For devices with multiple displays, and cameras on all displays, the
camera IDs might not be unique, and reused on different displays, based
on being the primary camera or not.
To be able to differentiate we use physical camera ids.

Test: Manual
Test: CameraAvailabilityListenerTest.kt
Test: ScreenDecorationsTest.java
Fixes: 311147359
Flag: NONE
Change-Id: Ic47097eb9770246a14307a71b6e9cd4b72dd292e
parent f93a7ee9
Loading
Loading
Loading
Loading
+4 −0
Original line number Original line Diff line number Diff line
@@ -543,12 +543,16 @@


    <!--  ID for the camera of outer display that needs extra protection -->
    <!--  ID for the camera of outer display that needs extra protection -->
    <string translatable="false" name="config_protectedCameraId"></string>
    <string translatable="false" name="config_protectedCameraId"></string>
    <!--  Physical ID for the camera of outer display that needs extra protection -->
    <string translatable="false" name="config_protectedPhysicalCameraId"></string>


    <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
    <!-- Similar to config_frontBuiltInDisplayCutoutProtection but for inner display. -->
    <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>
    <string translatable="false" name="config_innerBuiltInDisplayCutoutProtection"></string>


    <!-- ID for the camera of inner display that needs extra protection -->
    <!-- ID for the camera of inner display that needs extra protection -->
    <string translatable="false" name="config_protectedInnerCameraId"></string>
    <string translatable="false" name="config_protectedInnerCameraId"></string>
    <!-- Physical ID for the camera of inner display that needs extra protection -->
    <string translatable="false" name="config_protectedInnerPhysicalCameraId"></string>


    <!-- Comma-separated list of packages to exclude from camera protection e.g.
    <!-- Comma-separated list of packages to exclude from camera protection e.g.
    "com.android.systemui,com.android.xyz" -->
    "com.android.systemui,com.android.xyz" -->
+98 −34
Original line number Original line Diff line number Diff line
@@ -38,23 +38,72 @@ class CameraAvailabilityListener(
    excludedPackages: String,
    excludedPackages: String,
    private val executor: Executor
    private val executor: Executor
) {
) {
    private var activeProtectionInfo: CameraProtectionInfo? = null
    private var openCamera: OpenCameraInfo? = null
    private val unavailablePhysicalCameras = mutableSetOf<String>()
    private val excludedPackageIds: Set<String>
    private val excludedPackageIds: Set<String>
    private val listeners = mutableListOf<CameraTransitionCallback>()
    private val listeners = mutableListOf<CameraTransitionCallback>()
    private val availabilityCallback: CameraManager.AvailabilityCallback =
    private val availabilityCallback: CameraManager.AvailabilityCallback =
        object : CameraManager.AvailabilityCallback() {
        object : CameraManager.AvailabilityCallback() {
                override fun onCameraClosed(cameraId: String) {
            override fun onCameraClosed(logicalCameraId: String) {
                    cameraProtectionInfoList.forEach {
                openCamera = null
                        if (cameraId == it.cameraId) {
                if (activeProtectionInfo?.logicalCameraId == logicalCameraId) {
                    notifyCameraInactive()
                    notifyCameraInactive()
                }
                }
                activeProtectionInfo = null
            }

            override fun onCameraOpened(logicalCameraId: String, packageId: String) {
                openCamera = OpenCameraInfo(logicalCameraId, packageId)
                if (isExcluded(packageId)) {
                    return
                }
                val protectionInfo =
                    cameraProtectionInfoList.firstOrNull {
                        logicalCameraId == it.logicalCameraId &&
                            it.physicalCameraId !in unavailablePhysicalCameras
                    }
                if (protectionInfo != null) {
                    activeProtectionInfo = protectionInfo
                    notifyCameraActive(protectionInfo)
                }
                }
            }
            }


                override fun onCameraOpened(cameraId: String, packageId: String) {
            override fun onPhysicalCameraAvailable(
                    cameraProtectionInfoList.forEach {
                logicalCameraId: String,
                        if (cameraId == it.cameraId && !isExcluded(packageId)) {
                physicalCameraId: String
                            notifyCameraActive(it)
            ) {
                unavailablePhysicalCameras -= physicalCameraId
                val openCamera = this@CameraAvailabilityListener.openCamera ?: return
                if (openCamera.logicalCameraId != logicalCameraId) {
                    return
                }
                }
                if (isExcluded(openCamera.packageId)) {
                    return
                }
                val newActiveInfo =
                    cameraProtectionInfoList.find {
                        it.logicalCameraId == logicalCameraId &&
                            it.physicalCameraId == physicalCameraId
                    }
                if (newActiveInfo != null) {
                    activeProtectionInfo = newActiveInfo
                    notifyCameraActive(newActiveInfo)
                }
            }

            override fun onPhysicalCameraUnavailable(
                logicalCameraId: String,
                physicalCameraId: String
            ) {
                unavailablePhysicalCameras += physicalCameraId
                val activeInfo = activeProtectionInfo ?: return
                if (
                    activeInfo.logicalCameraId == logicalCameraId &&
                        activeInfo.physicalCameraId == physicalCameraId
                ) {
                    activeProtectionInfo = null
                    notifyCameraInactive()
                }
                }
            }
            }
        }
        }
@@ -106,24 +155,21 @@ class CameraAvailabilityListener(
        listeners.forEach { it.onHideCameraProtection() }
        listeners.forEach { it.onHideCameraProtection() }
    }
    }


    /**
    /** Callbacks to tell a listener that a relevant camera turned on and off. */
     * Callbacks to tell a listener that a relevant camera turned on and off.
     */
    interface CameraTransitionCallback {
    interface CameraTransitionCallback {
        fun onApplyCameraProtection(protectionPath: Path, bounds: Rect)
        fun onApplyCameraProtection(protectionPath: Path, bounds: Rect)

        fun onHideCameraProtection()
        fun onHideCameraProtection()
    }
    }


    companion object Factory {
    companion object Factory {
        fun build(context: Context, executor: Executor): CameraAvailabilityListener {
        fun build(context: Context, executor: Executor): CameraAvailabilityListener {
            val manager = context
            val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
                    .getSystemService(Context.CAMERA_SERVICE) as CameraManager
            val res = context.resources
            val res = context.resources
            val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
            val cameraProtectionInfoList = loadCameraProtectionInfoList(res)
            val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
            val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)


            return CameraAvailabilityListener(
            return CameraAvailabilityListener(manager, cameraProtectionInfoList, excluded, executor)
                    manager, cameraProtectionInfoList, excluded, executor)
        }
        }


        private fun pathFromString(pathString: String): Path {
        private fun pathFromString(pathString: String): Path {
@@ -140,17 +186,21 @@ class CameraAvailabilityListener(


        private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
        private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> {
            val list = mutableListOf<CameraProtectionInfo>()
            val list = mutableListOf<CameraProtectionInfo>()
            val front = loadCameraProtectionInfo(
            val front =
                loadCameraProtectionInfo(
                    res,
                    res,
                    R.string.config_protectedCameraId,
                    R.string.config_protectedCameraId,
                    R.string.config_protectedPhysicalCameraId,
                    R.string.config_frontBuiltInDisplayCutoutProtection
                    R.string.config_frontBuiltInDisplayCutoutProtection
                )
                )
            if (front != null) {
            if (front != null) {
                list.add(front)
                list.add(front)
            }
            }
            val inner = loadCameraProtectionInfo(
            val inner =
                loadCameraProtectionInfo(
                    res,
                    res,
                    R.string.config_protectedInnerCameraId,
                    R.string.config_protectedInnerCameraId,
                    R.string.config_protectedInnerPhysicalCameraId,
                    R.string.config_innerBuiltInDisplayCutoutProtection
                    R.string.config_innerBuiltInDisplayCutoutProtection
                )
                )
            if (inner != null) {
            if (inner != null) {
@@ -162,28 +212,42 @@ class CameraAvailabilityListener(
        private fun loadCameraProtectionInfo(
        private fun loadCameraProtectionInfo(
            res: Resources,
            res: Resources,
            cameraIdRes: Int,
            cameraIdRes: Int,
            physicalCameraIdRes: Int,
            pathRes: Int
            pathRes: Int
        ): CameraProtectionInfo? {
        ): CameraProtectionInfo? {
            val cameraId = res.getString(cameraIdRes)
            val logicalCameraId = res.getString(cameraIdRes)
            if (cameraId == null || cameraId.isEmpty()) {
            if (logicalCameraId.isNullOrEmpty()) {
                return null
                return null
            }
            }
            val physicalCameraId = res.getString(physicalCameraIdRes)
            val protectionPath = pathFromString(res.getString(pathRes))
            val protectionPath = pathFromString(res.getString(pathRes))
            val computed = RectF()
            val computed = RectF()
            protectionPath.computeBounds(computed)
            protectionPath.computeBounds(computed)
            val protectionBounds = Rect(
            val protectionBounds =
                Rect(
                    computed.left.roundToInt(),
                    computed.left.roundToInt(),
                    computed.top.roundToInt(),
                    computed.top.roundToInt(),
                    computed.right.roundToInt(),
                    computed.right.roundToInt(),
                    computed.bottom.roundToInt()
                    computed.bottom.roundToInt()
                )
                )
            return CameraProtectionInfo(cameraId, protectionPath, protectionBounds)
            return CameraProtectionInfo(
                logicalCameraId,
                physicalCameraId,
                protectionPath,
                protectionBounds
            )
        }
        }
    }
    }


    data class CameraProtectionInfo(
    data class CameraProtectionInfo(
            val cameraId: String,
        val logicalCameraId: String,
        val physicalCameraId: String?,
        val cutoutProtectionPath: Path,
        val cutoutProtectionPath: Path,
            val cutoutBounds: Rect
        val cutoutBounds: Rect,
    )

    private data class OpenCameraInfo(
        val logicalCameraId: String,
        val packageId: String,
    )
    )
}
}
+15 −0
Original line number Original line Diff line number Diff line
@@ -179,6 +179,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
    @VisibleForTesting
    @VisibleForTesting
    protected DisplayInfo mDisplayInfo = new DisplayInfo();
    protected DisplayInfo mDisplayInfo = new DisplayInfo();
    private DisplayCutout mDisplayCutout;
    private DisplayCutout mDisplayCutout;
    private boolean mPendingManualConfigUpdate;


    @VisibleForTesting
    @VisibleForTesting
    protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
    protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) {
@@ -571,6 +572,11 @@ public class ScreenDecorations implements CoreStartable, Dumpable {
                        setupDecorations();
                        setupDecorations();
                        return;
                        return;
                    }
                    }

                    if (mPendingManualConfigUpdate) {
                        mPendingManualConfigUpdate = false;
                        onConfigurationChanged(mContext.getResources().getConfiguration());
                    }
                }
                }
            }
            }
        };
        };
@@ -1064,6 +1070,15 @@ public class ScreenDecorations implements CoreStartable, Dumpable {


        mExecutor.execute(() -> {
        mExecutor.execute(() -> {
            Trace.beginSection("ScreenDecorations#onConfigurationChanged");
            Trace.beginSection("ScreenDecorations#onConfigurationChanged");
            mContext.getDisplay().getDisplayInfo(mDisplayInfo);
            if (displaySizeChanged(mDisplaySize, mDisplayInfo)) {
                // We expect the display change event to happen first, but in this case, we received
                // onConfigurationChanged first.
                // Return so that we handle the display change event first, and then manually
                // trigger the config update.
                mPendingManualConfigUpdate = true;
                return;
            }
            int oldRotation = mRotation;
            int oldRotation = mRotation;
            mPendingConfigChange = false;
            mPendingConfigChange = false;
            updateConfiguration();
            updateConfiguration();
+297 −88

File changed.

Preview size limit exceeded, changes collapsed.