1 /* 2 * Copyright (C) 2022 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.pandora 18 19 import android.annotation.SuppressLint 20 import android.bluetooth.BluetoothDevice 21 import android.bluetooth.BluetoothHeadsetClient 22 import android.bluetooth.BluetoothManager 23 import android.bluetooth.BluetoothProfile 24 import android.content.Context 25 import android.content.Intent 26 import android.content.IntentFilter 27 import android.telecom.InCallService 28 import android.telecom.TelecomManager 29 import com.google.protobuf.Empty 30 import io.grpc.stub.StreamObserver 31 import java.io.Closeable 32 import kotlinx.coroutines.CoroutineScope 33 import kotlinx.coroutines.Dispatchers 34 import kotlinx.coroutines.cancel 35 import kotlinx.coroutines.flow.Flow 36 import kotlinx.coroutines.flow.SharingStarted 37 import kotlinx.coroutines.flow.shareIn 38 import pandora.HFPGrpc.HFPImplBase 39 import pandora.HfpProto.* 40 41 private const val TAG = "PandoraHfpHandsfree" 42 43 @kotlinx.coroutines.ExperimentalCoroutinesApi 44 class HfpHandsfree(val context: Context) : HFPImplBase(), Closeable { 45 private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default.limitedParallelism(1)) 46 private val flow: Flow<Intent> 47 48 private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!! 49 private val telecomManager = context.getSystemService(TelecomManager::class.java)!! 50 private val bluetoothAdapter = bluetoothManager.adapter 51 52 private val bluetoothHfpClient = 53 getProfileProxy<BluetoothHeadsetClient>(context, BluetoothProfile.HEADSET_CLIENT) 54 55 companion object { 56 @SuppressLint("StaticFieldLeak") private lateinit var inCallService: InCallService 57 } 58 <lambda>null59 init { 60 val intentFilter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED) 61 flow = intentFlow(context, intentFilter, scope).shareIn(scope, SharingStarted.Eagerly) 62 } 63 closenull64 override fun close() { 65 bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, bluetoothHfpClient) 66 scope.cancel() 67 } 68 answerCallAsHandsfreenull69 override fun answerCallAsHandsfree( 70 request: AnswerCallAsHandsfreeRequest, 71 responseObserver: StreamObserver<AnswerCallAsHandsfreeResponse> 72 ) { 73 grpcUnary(scope, responseObserver) { 74 bluetoothHfpClient.acceptCall( 75 request.connection.toBluetoothDevice(bluetoothAdapter), 76 BluetoothHeadsetClient.CALL_ACCEPT_NONE 77 ) 78 AnswerCallAsHandsfreeResponse.getDefaultInstance() 79 } 80 } 81 endCallAsHandsfreenull82 override fun endCallAsHandsfree( 83 request: EndCallAsHandsfreeRequest, 84 responseObserver: StreamObserver<EndCallAsHandsfreeResponse> 85 ) { 86 grpcUnary(scope, responseObserver) { 87 for (call in 88 bluetoothHfpClient.getCurrentCalls( 89 request.connection.toBluetoothDevice(bluetoothAdapter) 90 )) { 91 bluetoothHfpClient.terminateCall( 92 request.connection.toBluetoothDevice(bluetoothAdapter), 93 call 94 ) 95 } 96 EndCallAsHandsfreeResponse.getDefaultInstance() 97 } 98 } 99 declineCallAsHandsfreenull100 override fun declineCallAsHandsfree( 101 request: DeclineCallAsHandsfreeRequest, 102 responseObserver: StreamObserver<DeclineCallAsHandsfreeResponse> 103 ) { 104 grpcUnary(scope, responseObserver) { 105 bluetoothHfpClient.rejectCall(request.connection.toBluetoothDevice(bluetoothAdapter)) 106 DeclineCallAsHandsfreeResponse.getDefaultInstance() 107 } 108 } 109 connectToAudioAsHandsfreenull110 override fun connectToAudioAsHandsfree( 111 request: ConnectToAudioAsHandsfreeRequest, 112 responseObserver: StreamObserver<ConnectToAudioAsHandsfreeResponse> 113 ) { 114 grpcUnary(scope, responseObserver) { 115 bluetoothHfpClient.connectAudio(request.connection.toBluetoothDevice(bluetoothAdapter)) 116 ConnectToAudioAsHandsfreeResponse.getDefaultInstance() 117 } 118 } 119 disconnectFromAudioAsHandsfreenull120 override fun disconnectFromAudioAsHandsfree( 121 request: DisconnectFromAudioAsHandsfreeRequest, 122 responseObserver: StreamObserver<DisconnectFromAudioAsHandsfreeResponse> 123 ) { 124 grpcUnary(scope, responseObserver) { 125 bluetoothHfpClient.disconnectAudio( 126 request.connection.toBluetoothDevice(bluetoothAdapter) 127 ) 128 DisconnectFromAudioAsHandsfreeResponse.getDefaultInstance() 129 } 130 } 131 makeCallAsHandsfreenull132 override fun makeCallAsHandsfree( 133 request: MakeCallAsHandsfreeRequest, 134 responseObserver: StreamObserver<MakeCallAsHandsfreeResponse> 135 ) { 136 grpcUnary(scope, responseObserver) { 137 bluetoothHfpClient.dial( 138 request.connection.toBluetoothDevice(bluetoothAdapter), 139 request.number 140 ) 141 MakeCallAsHandsfreeResponse.getDefaultInstance() 142 } 143 } 144 callTransferAsHandsfreenull145 override fun callTransferAsHandsfree( 146 request: CallTransferAsHandsfreeRequest, 147 responseObserver: StreamObserver<CallTransferAsHandsfreeResponse> 148 ) { 149 grpcUnary(scope, responseObserver) { 150 bluetoothHfpClient.explicitCallTransfer( 151 request.connection.toBluetoothDevice(bluetoothAdapter) 152 ) 153 CallTransferAsHandsfreeResponse.getDefaultInstance() 154 } 155 } 156 enableSlcAsHandsfreenull157 override fun enableSlcAsHandsfree( 158 request: EnableSlcAsHandsfreeRequest, 159 responseObserver: StreamObserver<Empty> 160 ) { 161 grpcUnary(scope, responseObserver) { 162 bluetoothHfpClient.setConnectionPolicy( 163 request.connection.toBluetoothDevice(bluetoothAdapter), 164 BluetoothProfile.CONNECTION_POLICY_ALLOWED 165 ) 166 Empty.getDefaultInstance() 167 } 168 } 169 disableSlcAsHandsfreenull170 override fun disableSlcAsHandsfree( 171 request: DisableSlcAsHandsfreeRequest, 172 responseObserver: StreamObserver<Empty> 173 ) { 174 grpcUnary(scope, responseObserver) { 175 bluetoothHfpClient.setConnectionPolicy( 176 request.connection.toBluetoothDevice(bluetoothAdapter), 177 BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 178 ) 179 Empty.getDefaultInstance() 180 } 181 } 182 setVoiceRecognitionAsHandsfreenull183 override fun setVoiceRecognitionAsHandsfree( 184 request: SetVoiceRecognitionAsHandsfreeRequest, 185 responseObserver: StreamObserver<SetVoiceRecognitionAsHandsfreeResponse> 186 ) { 187 grpcUnary(scope, responseObserver) { 188 if (request.enabled) { 189 bluetoothHfpClient.startVoiceRecognition( 190 request.connection.toBluetoothDevice(bluetoothAdapter) 191 ) 192 } else { 193 bluetoothHfpClient.stopVoiceRecognition( 194 request.connection.toBluetoothDevice(bluetoothAdapter) 195 ) 196 } 197 SetVoiceRecognitionAsHandsfreeResponse.getDefaultInstance() 198 } 199 } 200 sendDtmfFromHandsfreenull201 override fun sendDtmfFromHandsfree( 202 request: SendDtmfFromHandsfreeRequest, 203 responseObserver: StreamObserver<SendDtmfFromHandsfreeResponse> 204 ) { 205 grpcUnary(scope, responseObserver) { 206 bluetoothHfpClient.sendDTMF( 207 request.connection.toBluetoothDevice(bluetoothAdapter), 208 request.code.toByte() 209 ) 210 SendDtmfFromHandsfreeResponse.getDefaultInstance() 211 } 212 } 213 } 214