blob: 3015710e8a985070f25fd87295dfa645deb050bc [file] [log] [blame]
Evan Laird6075dd72020-02-21 14:33:42 -05001/*
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
17package com.android.systemui
18
19import android.content.Context
20import android.graphics.Path
21import android.graphics.Rect
22import android.graphics.RectF
23import android.hardware.camera2.CameraManager
24import android.util.PathParser
25import java.util.concurrent.Executor
26
27import kotlin.math.roundToInt
28
Evan Laird377838b2020-03-06 16:55:46 -050029const val TAG = "CameraAvailabilityListener"
Evan Laird6075dd72020-02-21 14:33:42 -050030
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 */
36class CameraAvailabilityListener(
37 private val cameraManager: CameraManager,
38 private val cutoutProtectionPath: Path,
39 private val targetCameraId: String,
Evan Lairdb38dbfb2020-03-17 15:31:21 -040040 excludedPackages: String,
Evan Laird6075dd72020-02-21 14:33:42 -050041 private val executor: Executor
42) {
43 private var cutoutBounds = Rect()
Evan Lairdb38dbfb2020-03-17 15:31:21 -040044 private val excludedPackageIds: Set<String>
Evan Laird6075dd72020-02-21 14:33:42 -050045 private val listeners = mutableListOf<CameraTransitionCallback>()
46 private val availabilityCallback: CameraManager.AvailabilityCallback =
47 object : CameraManager.AvailabilityCallback() {
Evan Lairdb38dbfb2020-03-17 15:31:21 -040048 override fun onCameraClosed(cameraId: String) {
Evan Laird6075dd72020-02-21 14:33:42 -050049 if (targetCameraId == cameraId) {
50 notifyCameraInactive()
51 }
52 }
53
Evan Lairdb38dbfb2020-03-17 15:31:21 -040054 override fun onCameraOpened(cameraId: String, packageId: String) {
55 if (targetCameraId == cameraId && !isExcluded(packageId)) {
Evan Laird6075dd72020-02-21 14:33:42 -050056 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())
Evan Lairdb38dbfb2020-03-17 15:31:21 -040069 excludedPackageIds = excludedPackages.split(",").toSet()
Evan Laird6075dd72020-02-21 14:33:42 -050070 }
71
72 /**
73 * Start listening for availability events, and maybe notify listeners
74 *
75 * @return true if we started listening
76 */
77 fun startListening() {
78 registerCameraListener()
79 }
80
81 fun stop() {
82 unregisterCameraListener()
83 }
84
85 fun addTransitionCallback(callback: CameraTransitionCallback) {
86 listeners.add(callback)
87 }
88
89 fun removeTransitionCallback(callback: CameraTransitionCallback) {
90 listeners.remove(callback)
91 }
92
Evan Lairdb38dbfb2020-03-17 15:31:21 -040093 private fun isExcluded(packageId: String): Boolean {
94 return excludedPackageIds.contains(packageId)
95 }
96
Evan Laird6075dd72020-02-21 14:33:42 -050097 private fun registerCameraListener() {
98 cameraManager.registerAvailabilityCallback(executor, availabilityCallback)
99 }
100
101 private fun unregisterCameraListener() {
102 cameraManager.unregisterAvailabilityCallback(availabilityCallback)
103 }
104
105 private fun notifyCameraActive() {
106 listeners.forEach { it.onApplyCameraProtection(cutoutProtectionPath, cutoutBounds) }
107 }
108
109 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 {
117 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)
Evan Lairdb38dbfb2020-03-17 15:31:21 -0400128 val excluded = res.getString(R.string.config_cameraProtectionExcludedPackages)
Evan Laird6075dd72020-02-21 14:33:42 -0500129
130 return CameraAvailabilityListener(
Evan Lairdb38dbfb2020-03-17 15:31:21 -0400131 manager, pathFromString(pathString), cameraId, excluded, executor)
Evan Laird6075dd72020-02-21 14:33:42 -0500132 }
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 }
Evan Lairdb38dbfb2020-03-17 15:31:21 -0400146}