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.graphics.RectF
23 import android.hardware.camera2.CameraManager
24 import android.util.PathParser
25 import java.util.concurrent.Executor
26 
27 import kotlin.math.roundToInt
28 
29 const val TAG = "CameraAvailabilityListener"
30 
31 /**
32  * Listens for usage of the Camera and controls the ScreenDecorations transition to show extra
33  * protection around a display cutout based on config_frontBuiltInDisplayCutoutProtection and
34  * config_enableDisplayCutoutProtection
35  */
36 class CameraAvailabilityListener(
37     private val cameraManager: CameraManager,
38     private val cutoutProtectionPath: Path,
39     private val targetCameraId: String,
40     excludedPackages: String,
41     private val executor: Executor
42 ) {
43     private var cutoutBounds = Rect()
44     private val excludedPackageIds: Set<String>
45     private val listeners = mutableListOf<CameraTransitionCallback>()
46     private val availabilityCallback: CameraManager.AvailabilityCallback =
47             object : CameraManager.AvailabilityCallback() {
onCameraClosednull48                 override fun onCameraClosed(cameraId: String) {
49                     if (targetCameraId == cameraId) {
50                         notifyCameraInactive()
51                     }
52                 }
53 
onCameraOpenednull54                 override fun onCameraOpened(cameraId: String, packageId: String) {
55                     if (targetCameraId == cameraId && !isExcluded(packageId)) {
56                         notifyCameraActive()
57                     }
58                 }
59     }
60 
61     init {
62         val computed = RectF()
63         cutoutProtectionPath.computeBounds(computed, false /* unused */)
64         cutoutBounds.set(
65                 computed.left.roundToInt(),
66                 computed.top.roundToInt(),
67                 computed.right.roundToInt(),
68                 computed.bottom.roundToInt())
69         excludedPackageIds = excludedPackages.split(",").toSet()
70     }
71 
72     /**
73      * Start listening for availability events, and maybe notify listeners
74      *
75      * @return true if we started listening
76      */
startListeningnull77     fun startListening() {
78         registerCameraListener()
79     }
80 
stopnull81     fun stop() {
82         unregisterCameraListener()
83     }
84 
addTransitionCallbacknull85     fun addTransitionCallback(callback: CameraTransitionCallback) {
86         listeners.add(callback)
87     }
88 
removeTransitionCallbacknull89     fun removeTransitionCallback(callback: CameraTransitionCallback) {
90         listeners.remove(callback)
91     }
92 
isExcludednull93     private fun isExcluded(packageId: String): Boolean {
94         return excludedPackageIds.contains(packageId)
95     }
96 
registerCameraListenernull97     private fun registerCameraListener() {
98         cameraManager.registerAvailabilityCallback(executor, availabilityCallback)
99     }
100 
unregisterCameraListenernull101     private fun unregisterCameraListener() {
102         cameraManager.unregisterAvailabilityCallback(availabilityCallback)
103     }
104 
notifyCameraActivenull105     private fun notifyCameraActive() {
106         listeners.forEach { it.onApplyCameraProtection(cutoutProtectionPath, cutoutBounds) }
107     }
108 
notifyCameraInactivenull109     private fun notifyCameraInactive() {
110         listeners.forEach { it.onHideCameraProtection() }
111     }
112 
113     /**
114      * Callbacks to tell a listener that a relevant camera turned on and off.
115      */
116     interface CameraTransitionCallback {
onApplyCameraProtectionnull117         fun onApplyCameraProtection(protectionPath: Path, bounds: Rect)
118         fun onHideCameraProtection()
119     }
120 
121     companion object Factory {
122         fun build(context: Context, executor: Executor): CameraAvailabilityListener {
123             val manager = context
124                     .getSystemService(Context.CAMERA_SERVICE) as CameraManager
125             val res = context.resources
126             val pathString = res.getString(R.string.config_frontBuiltInDisplayCutoutProtection)
127             val cameraId = res.getString(R.string.config_protectedCameraId)
128             val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
129 
130             return CameraAvailabilityListener(
131                     manager, pathFromString(pathString), cameraId, excluded, executor)
132         }
133 
134         private fun pathFromString(pathString: String): Path {
135             val spec = pathString.trim()
136             val p: Path
137             try {
138                 p = PathParser.createPathFromPathData(spec)
139             } catch (e: Throwable) {
140                 throw IllegalArgumentException("Invalid protection path", e)
141             }
142 
143             return p
144         }
145     }
146 }
147