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