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