• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2025 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.accessibility.hearingaid
18 
19 import android.content.Context
20 import android.media.AudioManager
21 import android.util.Log
22 import androidx.collection.ArraySet
23 import com.android.settingslib.bluetooth.CachedBluetoothDevice
24 import com.android.settingslib.bluetooth.HapClientProfile
25 import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants
26 import com.android.settingslib.bluetooth.HearingAidAudioRoutingHelper
27 import com.android.systemui.dagger.qualifiers.Background
28 import com.android.systemui.res.R
29 import dagger.assisted.Assisted
30 import dagger.assisted.AssistedFactory
31 import dagger.assisted.AssistedInject
32 import kotlinx.coroutines.CoroutineDispatcher
33 import kotlinx.coroutines.CoroutineScope
34 import kotlinx.coroutines.launch
35 import kotlinx.coroutines.withContext
36 
37 /**
38  * The controller of the hearing device input routing.
39  *
40  * <p> It manages and update the input routing according to the value.
41  */
42 open class HearingDevicesInputRoutingController
43 @AssistedInject
44 constructor(
45     @Assisted context: Context,
46     private val audioManager: AudioManager,
47     @Background private val backgroundDispatcher: CoroutineDispatcher,
48 ) {
49     private val audioRoutingHelper = HearingAidAudioRoutingHelper(context)
50     private var cachedDevice: CachedBluetoothDevice? = null
51     private val bgCoroutineScope = CoroutineScope(backgroundDispatcher)
52 
53     /** Factory to create a [HearingDevicesInputRoutingController] instance. */
54     @AssistedFactory
55     interface Factory {
56         fun create(context: Context): HearingDevicesInputRoutingController
57     }
58 
59     /** Possible input routing UI. Need to align with [getInputRoutingOptions] */
60     enum class InputRoutingValue {
61         HEARING_DEVICE,
62         BUILTIN_MIC,
63     }
64 
65     companion object {
66         private const val TAG = "HearingDevicesInputRoutingController"
67 
68         /** Gets input routing options as strings. */
69         @JvmStatic
70         fun getInputRoutingOptions(context: Context): Array<String> {
71             return context.resources.getStringArray(R.array.hearing_device_input_routing_options)
72         }
73     }
74 
75     fun interface InputRoutingControlAvailableCallback {
76         fun onResult(available: Boolean)
77     }
78 
79     /**
80      * Sets the device for this controller to control the input routing.
81      *
82      * @param device the [CachedBluetoothDevice] set to the controller
83      */
84     fun setDevice(device: CachedBluetoothDevice?) {
85         this@HearingDevicesInputRoutingController.cachedDevice = device
86     }
87 
88     fun isInputRoutingControlAvailable(callback: InputRoutingControlAvailableCallback) {
89         bgCoroutineScope.launch {
90             val result = isInputRoutingControlAvailableInternal()
91             callback.onResult(result)
92         }
93     }
94 
95     /**
96      * Checks if input routing control is available for the currently set device.
97      *
98      * @return `true` if input routing control is available.
99      */
100     private suspend fun isInputRoutingControlAvailableInternal(): Boolean {
101         val device = cachedDevice ?: return false
102 
103         val memberDevices = device.memberDevice
104 
105         val inputInfos =
106             withContext(backgroundDispatcher) {
107                 audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)
108             }
109         val supportedInputDeviceAddresses = ArraySet<String>()
110         supportedInputDeviceAddresses.add(device.address)
111         if (memberDevices.isNotEmpty()) {
112             memberDevices.forEach { supportedInputDeviceAddresses.add(it.address) }
113         }
114 
115         val isValidInputDevice =
116             inputInfos.any { supportedInputDeviceAddresses.contains(it.address) }
117         // Not support ASHA hearing device for input routing feature
118         val isHapHearingDevice = device.profiles.any { profile -> profile is HapClientProfile }
119 
120         if (isHapHearingDevice && !isValidInputDevice) {
121             Log.d(TAG, "Not supported input type hearing device.")
122         }
123         return isHapHearingDevice && isValidInputDevice
124     }
125 
126     /** Gets the user's preferred [InputRoutingValue]. */
127     fun getUserPreferredInputRoutingValue(): Int {
128         val device = cachedDevice ?: return InputRoutingValue.HEARING_DEVICE.ordinal
129 
130         return if (device.device.isMicrophonePreferredForCalls) {
131             InputRoutingValue.HEARING_DEVICE.ordinal
132         } else {
133             InputRoutingValue.BUILTIN_MIC.ordinal
134         }
135     }
136 
137     /**
138      * Sets the input routing to [android.bluetooth.BluetoothDevice.setMicrophonePreferredForCalls]
139      * based on the input routing index.
140      *
141      * @param inputRoutingIndex The desired input routing index.
142      */
143     fun selectInputRouting(inputRoutingIndex: Int) {
144         val device = cachedDevice ?: return
145 
146         val useBuiltinMic = (inputRoutingIndex == InputRoutingValue.BUILTIN_MIC.ordinal)
147         val status =
148             audioRoutingHelper.setPreferredInputDeviceForCalls(
149                 device,
150                 if (useBuiltinMic) HearingAidAudioRoutingConstants.RoutingValue.BUILTIN_DEVICE
151                 else HearingAidAudioRoutingConstants.RoutingValue.AUTO,
152             )
153         if (!status) {
154             Log.d(TAG, "Fail to configure setPreferredInputDeviceForCalls")
155         }
156         setMicrophonePreferredForCallsForDeviceSet(device, !useBuiltinMic)
157     }
158 
159     private fun setMicrophonePreferredForCallsForDeviceSet(
160         device: CachedBluetoothDevice?,
161         enabled: Boolean,
162     ) {
163         device ?: return
164         device.device.isMicrophonePreferredForCalls = enabled
165         val memberDevices = device.memberDevice
166         if (memberDevices.isNotEmpty()) {
167             memberDevices.forEach { d -> d.device.isMicrophonePreferredForCalls = enabled }
168         }
169     }
170 }
171