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.bluetooth.BluetoothAdapter
20 import android.bluetooth.BluetoothLeAudio
21 import android.bluetooth.BluetoothManager
22 import android.bluetooth.BluetoothProfile
23 import android.content.Context
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.media.*
27 import android.util.Log
28 import com.google.protobuf.Empty
29 import io.grpc.stub.StreamObserver
30 import java.io.Closeable
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.Dispatchers
33 import kotlinx.coroutines.cancel
34 import kotlinx.coroutines.flow.Flow
35 import kotlinx.coroutines.flow.SharingStarted
36 import kotlinx.coroutines.flow.filter
37 import kotlinx.coroutines.flow.first
38 import kotlinx.coroutines.flow.map
39 import kotlinx.coroutines.flow.shareIn
40 import pandora.LeAudioGrpc.LeAudioImplBase
41 import pandora.LeAudioProto.*
42 
43 @kotlinx.coroutines.ExperimentalCoroutinesApi
44 class LeAudio(val context: Context) : LeAudioImplBase(), Closeable {
45 
46     private val TAG = "PandoraLeAudio"
47 
48     private val scope: CoroutineScope
49     private val flow: Flow<Intent>
50 
51     private val audioManager = context.getSystemService(AudioManager::class.java)!!
52 
53     private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
54     private val bluetoothAdapter = bluetoothManager.adapter
55     private val bluetoothLeAudio =
56         getProfileProxy<BluetoothLeAudio>(context, BluetoothProfile.LE_AUDIO)
57 
58     init {
59         scope = CoroutineScope(Dispatchers.Default)
60         val intentFilter = IntentFilter()
61         intentFilter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED)
62 
63         flow = intentFlow(context, intentFilter, scope).shareIn(scope, SharingStarted.Eagerly)
64     }
65 
closenull66     override fun close() {
67         bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO, bluetoothLeAudio)
68         scope.cancel()
69     }
70 
opennull71     override fun open(request: OpenRequest, responseObserver: StreamObserver<Empty>) {
72         grpcUnary<Empty>(scope, responseObserver) {
73             val device = request.connection.toBluetoothDevice(bluetoothAdapter)
74             Log.i(TAG, "open: device=$device")
75 
76             if (bluetoothLeAudio.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
77                 bluetoothLeAudio.connect(device)
78                 val state =
79                     flow
80                         .filter {
81                             it.getAction() ==
82                                 BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED
83                         }
84                         .filter { it.getBluetoothDeviceExtra() == device }
85                         .map {
86                             it.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR)
87                         }
88                         .filter {
89                             it == BluetoothProfile.STATE_CONNECTED ||
90                                 it == BluetoothProfile.STATE_DISCONNECTED
91                         }
92                         .first()
93 
94                 if (state == BluetoothProfile.STATE_DISCONNECTED) {
95                     throw RuntimeException("open failed, LE_AUDIO has been disconnected")
96                 }
97             }
98 
99             Empty.getDefaultInstance()
100         }
101     }
102 }
103