• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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