xref: /aosp_15_r20/frameworks/base/packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui
18 
19 import android.content.Context
20 import android.graphics.Path
21 import android.graphics.Rect
22 import android.hardware.camera2.CameraManager
23 import com.android.systemui.res.R
24 import java.util.concurrent.Executor
25 
26 /**
27  * Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
28  * protection around a display cutout based on config_frontBuiltInDisplayCutoutProtection and
29  * config_enableDisplayCutoutProtection
30  */
31 class CameraAvailabilityListener(
32     private val cameraManager: CameraManager,
33     private val cameraProtectionInfoList: List<CameraProtectionInfo>,
34     excludedPackages: String,
35     private val executor: Executor
36 ) {
37     private var activeProtectionInfo: CameraProtectionInfo? = null
38     private var openCamera: OpenCameraInfo? = null
39     private val unavailablePhysicalCameras = mutableSetOf<String>()
40     private val excludedPackageIds: Set<String>
41     private val listeners = mutableListOf<CameraTransitionCallback>()
42     private val availabilityCallback: CameraManager.AvailabilityCallback =
43         object : CameraManager.AvailabilityCallback() {
onCameraClosednull44             override fun onCameraClosed(logicalCameraId: String) {
45                 openCamera = null
46                 if (activeProtectionInfo?.logicalCameraId == logicalCameraId) {
47                     notifyCameraInactive()
48                 }
49                 activeProtectionInfo = null
50             }
51 
onCameraOpenednull52             override fun onCameraOpened(logicalCameraId: String, packageId: String) {
53                 openCamera = OpenCameraInfo(logicalCameraId, packageId)
54                 if (isExcluded(packageId)) {
55                     return
56                 }
57                 val protectionInfo =
58                     cameraProtectionInfoList.firstOrNull {
59                         logicalCameraId == it.logicalCameraId &&
60                             it.physicalCameraId !in unavailablePhysicalCameras
61                     }
62                 if (protectionInfo != null) {
63                     activeProtectionInfo = protectionInfo
64                     notifyCameraActive(protectionInfo)
65                 }
66             }
67 
onPhysicalCameraAvailablenull68             override fun onPhysicalCameraAvailable(
69                 logicalCameraId: String,
70                 physicalCameraId: String
71             ) {
72                 unavailablePhysicalCameras -= physicalCameraId
73                 val openCamera = this@CameraAvailabilityListener.openCamera ?: return
74                 if (openCamera.logicalCameraId != logicalCameraId) {
75                     return
76                 }
77                 if (isExcluded(openCamera.packageId)) {
78                     return
79                 }
80                 val newActiveInfo =
81                     cameraProtectionInfoList.find {
82                         it.logicalCameraId == logicalCameraId &&
83                             it.physicalCameraId == physicalCameraId
84                     }
85                 if (newActiveInfo != null) {
86                     activeProtectionInfo = newActiveInfo
87                     notifyCameraActive(newActiveInfo)
88                 }
89             }
90 
onPhysicalCameraUnavailablenull91             override fun onPhysicalCameraUnavailable(
92                 logicalCameraId: String,
93                 physicalCameraId: String
94             ) {
95                 unavailablePhysicalCameras += physicalCameraId
96                 val activeInfo = activeProtectionInfo ?: return
97                 if (
98                     activeInfo.logicalCameraId == logicalCameraId &&
99                         activeInfo.physicalCameraId == physicalCameraId
100                 ) {
101                     activeProtectionInfo = null
102                     notifyCameraInactive()
103                 }
104             }
105         }
106 
107     init {
108         excludedPackageIds = excludedPackages.split(",").toSet()
109     }
110 
111     /**
112      * Start listening for availability events, and maybe notify listeners
113      *
114      * @return true if we started listening
115      */
startListeningnull116     fun startListening() {
117         registerCameraListener()
118     }
119 
stopnull120     fun stop() {
121         unregisterCameraListener()
122     }
123 
addTransitionCallbacknull124     fun addTransitionCallback(callback: CameraTransitionCallback) {
125         listeners.add(callback)
126     }
127 
removeTransitionCallbacknull128     fun removeTransitionCallback(callback: CameraTransitionCallback) {
129         listeners.remove(callback)
130     }
131 
isExcludednull132     private fun isExcluded(packageId: String): Boolean {
133         return excludedPackageIds.contains(packageId)
134     }
135 
registerCameraListenernull136     private fun registerCameraListener() {
137         cameraManager.registerAvailabilityCallback(executor, availabilityCallback)
138     }
139 
unregisterCameraListenernull140     private fun unregisterCameraListener() {
141         cameraManager.unregisterAvailabilityCallback(availabilityCallback)
142     }
143 
notifyCameraActivenull144     private fun notifyCameraActive(info: CameraProtectionInfo) {
145         listeners.forEach {
146             it.onApplyCameraProtection(info.cutoutProtectionPath, info.bounds)
147         }
148     }
149 
notifyCameraInactivenull150     private fun notifyCameraInactive() {
151         listeners.forEach { it.onHideCameraProtection() }
152     }
153 
154     /** Callbacks to tell a listener that a relevant camera turned on and off. */
155     interface CameraTransitionCallback {
onApplyCameraProtectionnull156         fun onApplyCameraProtection(protectionPath: Path, bounds: Rect)
157 
158         fun onHideCameraProtection()
159     }
160 
161     companion object Factory {
162         fun build(
163             context: Context,
164             executor: Executor,
165             cameraProtectionLoader: CameraProtectionLoader
166         ): CameraAvailabilityListener {
167             val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
168             val res = context.resources
169             val cameraProtectionInfoList = cameraProtectionLoader.loadCameraProtectionInfoList()
170             val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
171 
172             return CameraAvailabilityListener(manager, cameraProtectionInfoList, excluded, executor)
173         }
174     }
175 
176     private data class OpenCameraInfo(
177         val logicalCameraId: String,
178         val packageId: String,
179     )
180 }
181