1 /* <lambda>null2 * Copyright (C) 2024 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.qs.tiles.impl.sensorprivacy 18 19 import android.hardware.SensorPrivacyManager.Sensors.CAMERA 20 import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE 21 import android.hardware.SensorPrivacyManager.Sensors.Sensor 22 import android.os.UserHandle 23 import android.provider.DeviceConfig 24 import android.util.Log 25 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow 26 import com.android.systemui.dagger.qualifiers.Background 27 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger 28 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor 29 import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel 30 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController 31 import dagger.assisted.Assisted 32 import dagger.assisted.AssistedFactory 33 import dagger.assisted.AssistedInject 34 import kotlin.coroutines.CoroutineContext 35 import kotlinx.coroutines.channels.awaitClose 36 import kotlinx.coroutines.flow.Flow 37 import kotlinx.coroutines.flow.distinctUntilChanged 38 import kotlinx.coroutines.flow.flow 39 import kotlinx.coroutines.flow.flowOn 40 import kotlinx.coroutines.flow.onStart 41 import kotlinx.coroutines.withContext 42 43 /** Observes SensorPrivacyToggle mode state changes providing the [SensorPrivacyToggleTileModel]. */ 44 class SensorPrivacyToggleTileDataInteractor 45 @AssistedInject 46 constructor( 47 @Background private val bgCoroutineContext: CoroutineContext, 48 private val privacyController: IndividualSensorPrivacyController, 49 @Assisted @Sensor private val sensorId: Int, 50 ) : QSTileDataInteractor<SensorPrivacyToggleTileModel> { 51 @AssistedFactory 52 interface Factory { 53 fun create(@Sensor id: Int): SensorPrivacyToggleTileDataInteractor 54 } 55 56 override fun tileData( 57 user: UserHandle, 58 triggers: Flow<DataUpdateTrigger> 59 ): Flow<SensorPrivacyToggleTileModel> = 60 conflatedCallbackFlow { 61 val callback = 62 IndividualSensorPrivacyController.Callback { sensor, blocked -> 63 if (sensor == sensorId) trySend(SensorPrivacyToggleTileModel(blocked)) 64 } 65 privacyController.addCallback(callback) // does not emit an initial state 66 awaitClose { privacyController.removeCallback(callback) } 67 } 68 .onStart { 69 emit(SensorPrivacyToggleTileModel(privacyController.isSensorBlocked(sensorId))) 70 } 71 .distinctUntilChanged() 72 .flowOn(bgCoroutineContext) 73 74 override fun availability(user: UserHandle) = 75 flow { emit(isAvailable()) }.flowOn(bgCoroutineContext) 76 77 private suspend fun isAvailable(): Boolean { 78 return privacyController.supportsSensorToggle(sensorId) && isSensorDeviceConfigSet() 79 } 80 81 private suspend fun isSensorDeviceConfigSet(): Boolean = 82 withContext(bgCoroutineContext) { 83 try { 84 val deviceConfigName = getDeviceConfigName(sensorId) 85 return@withContext DeviceConfig.getBoolean( 86 DeviceConfig.NAMESPACE_PRIVACY, 87 deviceConfigName, 88 true 89 ) 90 } catch (exception: IllegalArgumentException) { 91 Log.w( 92 TAG, 93 "isDeviceConfigSet for sensorId $sensorId: " + 94 "Defaulting to true due to exception. ", 95 exception 96 ) 97 return@withContext true 98 } 99 } 100 101 private fun getDeviceConfigName(sensorId: Int): String { 102 if (sensorId == MICROPHONE) { 103 return "mic_toggle_enabled" 104 } else if (sensorId == CAMERA) { 105 return "camera_toggle_enabled" 106 } else { 107 throw IllegalArgumentException("getDeviceConfigName: unexpected sensorId: $sensorId") 108 } 109 } 110 111 private companion object { 112 const val TAG = "SensorPrivacyToggleTileException" 113 } 114 } 115