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