• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.BluetoothManager
21 import android.bluetooth.BluetoothProfile
22 import android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED
23 import android.bluetooth.BluetoothProfile.STATE_CONNECTED
24 import android.bluetooth.BluetoothVolumeControl
25 import android.content.Context
26 import android.content.IntentFilter
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.SharingStarted
35 import kotlinx.coroutines.flow.filter
36 import kotlinx.coroutines.flow.first
37 import kotlinx.coroutines.flow.map
38 import kotlinx.coroutines.flow.shareIn
39 import pandora.vcp.VCPGrpc.VCPImplBase
40 import pandora.vcp.VcpProto.*
41 
42 @kotlinx.coroutines.ExperimentalCoroutinesApi
43 class Vcp(val context: Context) : VCPImplBase(), Closeable {
44     private val TAG = "PandoraVcp"
45 
46     private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default.limitedParallelism(1))
47 
48     private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
49     private val bluetoothAdapter = bluetoothManager.adapter
50 
51     private val bluetoothVolumeControl =
52         getProfileProxy<BluetoothVolumeControl>(context, BluetoothProfile.VOLUME_CONTROL)
53 
54     private val flow =
55         intentFlow(
56                 context,
<lambda>null57                 IntentFilter().apply {
58                     addAction(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED)
59                 },
60                 scope,
61             )
62             .shareIn(scope, SharingStarted.Eagerly)
63 
closenull64     override fun close() {
65         // Deinit the CoroutineScope
66         scope.cancel()
67     }
68 
setDeviceVolumenull69     override fun setDeviceVolume(
70         request: SetDeviceVolumeRequest,
71         responseObserver: StreamObserver<Empty>,
72     ) {
73         grpcUnary<Empty>(scope, responseObserver) {
74             val device = request.connection.toBluetoothDevice(bluetoothAdapter)
75 
76             Log.i(TAG, "setDeviceVolume(${device}, ${request.volume})")
77 
78             bluetoothVolumeControl.setDeviceVolume(device, request.volume, false)
79 
80             Empty.getDefaultInstance()
81         }
82     }
83 
setVolumeOffsetnull84     override fun setVolumeOffset(
85         request: SetVolumeOffsetRequest,
86         responseObserver: StreamObserver<Empty>,
87     ) {
88         grpcUnary<Empty>(scope, responseObserver) {
89             val device = request.connection.toBluetoothDevice(bluetoothAdapter)
90 
91             Log.i(TAG, "setVolumeOffset(${device}, ${request.offset})")
92 
93             bluetoothVolumeControl.setVolumeOffset(device, 1, request.offset)
94 
95             Empty.getDefaultInstance()
96         }
97     }
98 
waitConnectnull99     override fun waitConnect(request: WaitConnectRequest, responseObserver: StreamObserver<Empty>) {
100         grpcUnary<Empty>(scope, responseObserver) {
101             val device = request.connection.toBluetoothDevice(bluetoothAdapter)
102             Log.i(TAG, "waitPeripheral(${device}")
103             if (bluetoothVolumeControl.getConnectionState(device) != STATE_CONNECTED) {
104                 Log.d(TAG, "Manual call to setConnectionPolicy")
105                 bluetoothVolumeControl.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED)
106                 Log.d(TAG, "wait for bluetoothVolumeControl profile connection")
107                 flow
108                     .filter { it.getBluetoothDeviceExtra() == device }
109                     .map { it.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR) }
110                     .filter { it == STATE_CONNECTED }
111                     .first()
112             }
113 
114             Empty.getDefaultInstance()
115         }
116     }
117 
setGainSettingnull118     override fun setGainSetting(
119         request: SetGainSettingRequest,
120         responseObserver: StreamObserver<Empty>,
121     ) {
122         grpcUnary<Empty>(scope, responseObserver) {
123             val device = request.connection.toBluetoothDevice(bluetoothAdapter)
124             Log.i(TAG, "setGainSetting(${device}, ${request.gainSetting})")
125 
126             bluetoothVolumeControl.getAudioInputControlServices(device).forEach {
127                 it.setGainSetting(request.gainSetting)
128             }
129 
130             Empty.getDefaultInstance()
131         }
132     }
133 
setMutenull134     override fun setMute(request: SetMuteRequest, responseObserver: StreamObserver<Empty>) {
135         grpcUnary<Empty>(scope, responseObserver) {
136             val device = request.connection.toBluetoothDevice(bluetoothAdapter)
137             Log.i(TAG, "setMute(${device}, ${request.mute})")
138 
139             bluetoothVolumeControl.getAudioInputControlServices(device).forEach {
140                 it.setMute(request.mute)
141             }
142 
143             Empty.getDefaultInstance()
144         }
145     }
146 
setGainModenull147     override fun setGainMode(request: SetGainModeRequest, responseObserver: StreamObserver<Empty>) {
148         grpcUnary<Empty>(scope, responseObserver) {
149             val device = request.connection.toBluetoothDevice(bluetoothAdapter)
150             Log.i(TAG, "setMute(${device}, ${request.gainMode})")
151 
152             bluetoothVolumeControl.getAudioInputControlServices(device).forEach {
153                 it.setGainMode(request.gainMode)
154             }
155 
156             Empty.getDefaultInstance()
157         }
158     }
159 }
160