Loading packages/SystemUI/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -543,12 +543,16 @@ <!-- ID for the camera of outer display that needs extra protection --> <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. --> <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> <!-- 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. "com.android.systemui,com.android.xyz" --> Loading packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt +98 −34 Original line number Diff line number Diff line Loading @@ -38,23 +38,72 @@ class CameraAvailabilityListener( excludedPackages: String, 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 listeners = mutableListOf<CameraTransitionCallback>() private val availabilityCallback: CameraManager.AvailabilityCallback = object : CameraManager.AvailabilityCallback() { override fun onCameraClosed(cameraId: String) { cameraProtectionInfoList.forEach { if (cameraId == it.cameraId) { override fun onCameraClosed(logicalCameraId: String) { openCamera = null if (activeProtectionInfo?.logicalCameraId == logicalCameraId) { 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) { cameraProtectionInfoList.forEach { if (cameraId == it.cameraId && !isExcluded(packageId)) { notifyCameraActive(it) override fun onPhysicalCameraAvailable( logicalCameraId: String, physicalCameraId: String ) { 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() } } } Loading Loading @@ -106,24 +155,21 @@ class CameraAvailabilityListener( 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 { fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) fun onHideCameraProtection() } companion object Factory { fun build(context: Context, executor: Executor): CameraAvailabilityListener { val manager = context .getSystemService(Context.CAMERA_SERVICE) as CameraManager val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager val res = context.resources val cameraProtectionInfoList = loadCameraProtectionInfoList(res) val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages) return CameraAvailabilityListener( manager, cameraProtectionInfoList, excluded, executor) return CameraAvailabilityListener(manager, cameraProtectionInfoList, excluded, executor) } private fun pathFromString(pathString: String): Path { Loading @@ -140,17 +186,21 @@ class CameraAvailabilityListener( private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> { val list = mutableListOf<CameraProtectionInfo>() val front = loadCameraProtectionInfo( 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( val inner = loadCameraProtectionInfo( res, R.string.config_protectedInnerCameraId, R.string.config_protectedInnerPhysicalCameraId, R.string.config_innerBuiltInDisplayCutoutProtection ) if (inner != null) { Loading @@ -162,28 +212,42 @@ class CameraAvailabilityListener( private fun loadCameraProtectionInfo( res: Resources, cameraIdRes: Int, physicalCameraIdRes: Int, pathRes: Int ): CameraProtectionInfo? { val cameraId = res.getString(cameraIdRes) if (cameraId == null || cameraId.isEmpty()) { 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( val protectionBounds = Rect( computed.left.roundToInt(), computed.top.roundToInt(), computed.right.roundToInt(), computed.bottom.roundToInt() ) return CameraProtectionInfo(cameraId, protectionPath, protectionBounds) return CameraProtectionInfo( logicalCameraId, physicalCameraId, protectionPath, protectionBounds ) } } data class CameraProtectionInfo( val cameraId: String, val logicalCameraId: String, val physicalCameraId: String?, val cutoutProtectionPath: Path, val cutoutBounds: Rect val cutoutBounds: Rect, ) private data class OpenCameraInfo( val logicalCameraId: String, val packageId: String, ) } packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +15 −0 Original line number Diff line number Diff line Loading @@ -179,6 +179,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { @VisibleForTesting protected DisplayInfo mDisplayInfo = new DisplayInfo(); private DisplayCutout mDisplayCutout; private boolean mPendingManualConfigUpdate; @VisibleForTesting protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) { Loading Loading @@ -571,6 +572,11 @@ public class ScreenDecorations implements CoreStartable, Dumpable { setupDecorations(); return; } if (mPendingManualConfigUpdate) { mPendingManualConfigUpdate = false; onConfigurationChanged(mContext.getResources().getConfiguration()); } } } }; Loading Loading @@ -1064,6 +1070,15 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mExecutor.execute(() -> { 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; mPendingConfigChange = false; updateConfiguration(); Loading packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt +297 −88 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -543,12 +543,16 @@ <!-- ID for the camera of outer display that needs extra protection --> <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. --> <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> <!-- 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. "com.android.systemui,com.android.xyz" --> Loading
packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt +98 −34 Original line number Diff line number Diff line Loading @@ -38,23 +38,72 @@ class CameraAvailabilityListener( excludedPackages: String, 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 listeners = mutableListOf<CameraTransitionCallback>() private val availabilityCallback: CameraManager.AvailabilityCallback = object : CameraManager.AvailabilityCallback() { override fun onCameraClosed(cameraId: String) { cameraProtectionInfoList.forEach { if (cameraId == it.cameraId) { override fun onCameraClosed(logicalCameraId: String) { openCamera = null if (activeProtectionInfo?.logicalCameraId == logicalCameraId) { 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) { cameraProtectionInfoList.forEach { if (cameraId == it.cameraId && !isExcluded(packageId)) { notifyCameraActive(it) override fun onPhysicalCameraAvailable( logicalCameraId: String, physicalCameraId: String ) { 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() } } } Loading Loading @@ -106,24 +155,21 @@ class CameraAvailabilityListener( 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 { fun onApplyCameraProtection(protectionPath: Path, bounds: Rect) fun onHideCameraProtection() } companion object Factory { fun build(context: Context, executor: Executor): CameraAvailabilityListener { val manager = context .getSystemService(Context.CAMERA_SERVICE) as CameraManager val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager val res = context.resources val cameraProtectionInfoList = loadCameraProtectionInfoList(res) val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages) return CameraAvailabilityListener( manager, cameraProtectionInfoList, excluded, executor) return CameraAvailabilityListener(manager, cameraProtectionInfoList, excluded, executor) } private fun pathFromString(pathString: String): Path { Loading @@ -140,17 +186,21 @@ class CameraAvailabilityListener( private fun loadCameraProtectionInfoList(res: Resources): List<CameraProtectionInfo> { val list = mutableListOf<CameraProtectionInfo>() val front = loadCameraProtectionInfo( 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( val inner = loadCameraProtectionInfo( res, R.string.config_protectedInnerCameraId, R.string.config_protectedInnerPhysicalCameraId, R.string.config_innerBuiltInDisplayCutoutProtection ) if (inner != null) { Loading @@ -162,28 +212,42 @@ class CameraAvailabilityListener( private fun loadCameraProtectionInfo( res: Resources, cameraIdRes: Int, physicalCameraIdRes: Int, pathRes: Int ): CameraProtectionInfo? { val cameraId = res.getString(cameraIdRes) if (cameraId == null || cameraId.isEmpty()) { 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( val protectionBounds = Rect( computed.left.roundToInt(), computed.top.roundToInt(), computed.right.roundToInt(), computed.bottom.roundToInt() ) return CameraProtectionInfo(cameraId, protectionPath, protectionBounds) return CameraProtectionInfo( logicalCameraId, physicalCameraId, protectionPath, protectionBounds ) } } data class CameraProtectionInfo( val cameraId: String, val logicalCameraId: String, val physicalCameraId: String?, val cutoutProtectionPath: Path, val cutoutBounds: Rect val cutoutBounds: Rect, ) private data class OpenCameraInfo( val logicalCameraId: String, val packageId: String, ) }
packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +15 −0 Original line number Diff line number Diff line Loading @@ -179,6 +179,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { @VisibleForTesting protected DisplayInfo mDisplayInfo = new DisplayInfo(); private DisplayCutout mDisplayCutout; private boolean mPendingManualConfigUpdate; @VisibleForTesting protected void showCameraProtection(@NonNull Path protectionPath, @NonNull Rect bounds) { Loading Loading @@ -571,6 +572,11 @@ public class ScreenDecorations implements CoreStartable, Dumpable { setupDecorations(); return; } if (mPendingManualConfigUpdate) { mPendingManualConfigUpdate = false; onConfigurationChanged(mContext.getResources().getConfiguration()); } } } }; Loading Loading @@ -1064,6 +1070,15 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mExecutor.execute(() -> { 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; mPendingConfigChange = false; updateConfiguration(); Loading
packages/SystemUI/tests/src/com/android/systemui/CameraAvailabilityListenerTest.kt +297 −88 File changed.Preview size limit exceeded, changes collapsed. Show changes