1 /* 2 * Copyright (C) 2017 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.googlecode.android_scripting.facade.bluetooth; 18 19 import android.app.Service; 20 import android.bluetooth.BluetoothA2dp; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothCodecConfig; 23 import android.bluetooth.BluetoothCodecStatus; 24 import android.bluetooth.BluetoothDevice; 25 import android.bluetooth.BluetoothProfile; 26 import android.bluetooth.BluetoothUuid; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.os.Bundle; 32 import android.os.ParcelUuid; 33 34 import com.googlecode.android_scripting.Log; 35 import com.googlecode.android_scripting.facade.EventFacade; 36 import com.googlecode.android_scripting.facade.FacadeManager; 37 import com.googlecode.android_scripting.jsonrpc.RpcReceiver; 38 import com.googlecode.android_scripting.rpc.Rpc; 39 import com.googlecode.android_scripting.rpc.RpcParameter; 40 41 import java.util.List; 42 43 public class BluetoothA2dpFacade extends RpcReceiver { 44 static final ParcelUuid[] SINK_UUIDS = { 45 BluetoothUuid.A2DP_SINK, BluetoothUuid.ADV_AUDIO_DIST, 46 }; 47 private BluetoothCodecConfig mBluetoothCodecConfig; 48 49 private final Service mService; 50 private final EventFacade mEventFacade; 51 private final BroadcastReceiver mBluetoothA2dpReceiver; 52 private final BluetoothAdapter mBluetoothAdapter; 53 54 private static volatile boolean sIsA2dpReady = false; 55 private static BluetoothA2dp sA2dpProfile = null; 56 BluetoothA2dpFacade(FacadeManager manager)57 public BluetoothA2dpFacade(FacadeManager manager) { 58 super(manager); 59 mService = manager.getService(); 60 mEventFacade = manager.getReceiver(EventFacade.class); 61 62 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 63 mBluetoothA2dpReceiver = new BluetoothA2dpReceiver(); 64 mBluetoothCodecConfig = new BluetoothCodecConfig( 65 BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID, 66 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT, 67 BluetoothCodecConfig.SAMPLE_RATE_NONE, 68 BluetoothCodecConfig.BITS_PER_SAMPLE_NONE, 69 BluetoothCodecConfig.CHANNEL_MODE_NONE, 70 0L, 0L, 0L, 0L); 71 mBluetoothAdapter.getProfileProxy(mService, new A2dpServiceListener(), 72 BluetoothProfile.A2DP); 73 74 mService.registerReceiver(mBluetoothA2dpReceiver, 75 new IntentFilter(BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED)); 76 } 77 78 class A2dpServiceListener implements BluetoothProfile.ServiceListener { 79 @Override onServiceConnected(int profile, BluetoothProfile proxy)80 public void onServiceConnected(int profile, BluetoothProfile proxy) { 81 sA2dpProfile = (BluetoothA2dp) proxy; 82 sIsA2dpReady = true; 83 } 84 85 @Override onServiceDisconnected(int profile)86 public void onServiceDisconnected(int profile) { 87 sIsA2dpReady = false; 88 } 89 } 90 91 class BluetoothA2dpReceiver extends BroadcastReceiver { 92 @Override onReceive(Context context, Intent intent)93 public void onReceive(Context context, Intent intent) { 94 String action = intent.getAction(); 95 96 if (BluetoothA2dp.ACTION_CODEC_CONFIG_CHANGED.equals(action)) { 97 BluetoothCodecStatus codecStatus = intent.getParcelableExtra( 98 BluetoothCodecStatus.EXTRA_CODEC_STATUS); 99 if (codecStatus.getCodecConfig().equals(mBluetoothCodecConfig)) { 100 mEventFacade.postEvent("BluetoothA2dpCodecConfigChanged", new Bundle()); 101 } 102 } 103 } 104 } 105 106 107 108 /** 109 * Connect A2DP Profile to input BluetoothDevice 110 * 111 * @param device the BluetoothDevice object to connect to 112 * @return if the connection was successfull or not 113 */ a2dpConnect(BluetoothDevice device)114 public Boolean a2dpConnect(BluetoothDevice device) { 115 List<BluetoothDevice> sinks = sA2dpProfile.getConnectedDevices(); 116 if (sinks != null) { 117 for (BluetoothDevice sink : sinks) { 118 sA2dpProfile.disconnect(sink); 119 } 120 } 121 return sA2dpProfile.connect(device); 122 } 123 124 /** 125 * Disconnect A2DP Profile from input BluetoothDevice 126 * 127 * @param device the BluetoothDevice object to disconnect from 128 * @return if the disconnection was successfull or not 129 */ a2dpDisconnect(BluetoothDevice device)130 public Boolean a2dpDisconnect(BluetoothDevice device) { 131 if (sA2dpProfile == null) return false; 132 if (sA2dpProfile.getPriority(device) > BluetoothProfile.PRIORITY_ON) { 133 sA2dpProfile.setPriority(device, BluetoothProfile.PRIORITY_ON); 134 } 135 return sA2dpProfile.disconnect(device); 136 } 137 138 /** 139 * Checks to see if the A2DP profile is ready for use. 140 * 141 * @return Returns true if the A2DP Profile is ready. 142 */ 143 @Rpc(description = "Is A2dp profile ready.") bluetoothA2dpIsReady()144 public Boolean bluetoothA2dpIsReady() { 145 return sIsA2dpReady; 146 } 147 148 /** 149 * Set Bluetooth A2DP connection priority 150 * 151 * @param deviceStr the Bluetooth device's mac address to set the connection priority of 152 * @param priority the integer priority to be set 153 */ 154 @Rpc(description = "Set priority of the profile") bluetoothA2dpSetPriority( @pcParametername = "device", description = "Mac address of a BT device.") String deviceStr, @RpcParameter(name = "priority", description = "Priority that needs to be set.") Integer priority)155 public void bluetoothA2dpSetPriority( 156 @RpcParameter(name = "device", description = "Mac address of a BT device.") 157 String deviceStr, 158 @RpcParameter(name = "priority", description = "Priority that needs to be set.") 159 Integer priority) 160 throws Exception { 161 if (sA2dpProfile == null) return; 162 BluetoothDevice device = 163 BluetoothFacade.getDevice(mBluetoothAdapter.getBondedDevices(), deviceStr); 164 Log.d("Changing priority of device " + device.getAlias() + " p: " + priority); 165 sA2dpProfile.setPriority(device, priority); 166 } 167 168 169 /** 170 * Connect to remote device using the A2DP profile. 171 * 172 * @param deviceID the name or mac address of the remote Bluetooth device. 173 * @return True if connected successfully. 174 * @throws Exception 175 */ 176 @Rpc(description = "Connect to an A2DP device.") bluetoothA2dpConnect( @pcParametername = "deviceID", description = "Name or MAC address of a bluetooth device.") String deviceID)177 public Boolean bluetoothA2dpConnect( 178 @RpcParameter(name = "deviceID", 179 description = "Name or MAC address of a bluetooth device.") 180 String deviceID) 181 throws Exception { 182 if (sA2dpProfile == null) { 183 return false; 184 } 185 BluetoothDevice mDevice = 186 BluetoothFacade.getDevice( 187 mBluetoothAdapter.getBondedDevices(), deviceID); 188 Log.d("Connecting to device " + mDevice.getAlias()); 189 return a2dpConnect(mDevice); 190 } 191 192 /** 193 * Disconnect a remote device using the A2DP profile. 194 * 195 * @param deviceID the name or mac address of the remote Bluetooth device. 196 * @return True if connected successfully. 197 * @throws Exception 198 */ 199 @Rpc(description = "Disconnect an A2DP device.") bluetoothA2dpDisconnect( @pcParametername = "deviceID", description = "Name or MAC address of a device.") String deviceID)200 public Boolean bluetoothA2dpDisconnect( 201 @RpcParameter(name = "deviceID", description = "Name or MAC address of a device.") 202 String deviceID) 203 throws Exception { 204 if (sA2dpProfile == null) { 205 return false; 206 } 207 List<BluetoothDevice> connectedA2dpDevices = 208 sA2dpProfile.getConnectedDevices(); 209 Log.d("Connected a2dp devices " + connectedA2dpDevices); 210 BluetoothDevice mDevice = BluetoothFacade.getDevice( 211 connectedA2dpDevices, deviceID); 212 return a2dpDisconnect(mDevice); 213 } 214 215 /** 216 * Get the list of devices connected through the A2DP profile. 217 * 218 * @return List of bluetooth devices that are in one of the following states: 219 * connected, connecting, and disconnecting. 220 */ 221 @Rpc(description = "Get all the devices connected through A2DP.") bluetoothA2dpGetConnectedDevices()222 public List<BluetoothDevice> bluetoothA2dpGetConnectedDevices() { 223 while (!sIsA2dpReady) { 224 continue; 225 } 226 return sA2dpProfile.getDevicesMatchingConnectionStates( 227 new int[] { BluetoothProfile.STATE_CONNECTED, 228 BluetoothProfile.STATE_CONNECTING, 229 BluetoothProfile.STATE_DISCONNECTING}); 230 } 231 isSelectableCodec(BluetoothCodecConfig target, BluetoothCodecConfig capability)232 private boolean isSelectableCodec(BluetoothCodecConfig target, 233 BluetoothCodecConfig capability) { 234 return target.getCodecType() == capability.getCodecType() 235 && (target.getSampleRate() & capability.getSampleRate()) != 0 236 && (target.getBitsPerSample() & capability.getBitsPerSample()) != 0 237 && (target.getChannelMode() & capability.getChannelMode()) != 0; 238 } 239 240 /** 241 * Set active devices with giving codec config 242 * 243 * @param codecType codec type want to set to, list in BluetoothCodecConfig. 244 * @param sampleRate sample rate want to set to, list in BluetoothCodecConfig. 245 * @param bitsPerSample bits per sample want to set to, list in BluetoothCodecConfig. 246 * @param channelMode channel mode want to set to, list in BluetoothCodecConfig. 247 * @return True if set codec config successfully. 248 */ 249 @Rpc(description = "Set A2dp codec config.") bluetoothA2dpSetCodecConfigPreference( @pcParametername = "codecType") Integer codecType, @RpcParameter(name = "sampleRate") Integer sampleRate, @RpcParameter(name = "bitsPerSample") Integer bitsPerSample, @RpcParameter(name = "channelMode") Integer channelMode, @RpcParameter(name = "codecSpecific1") Long codecSpecific1)250 public boolean bluetoothA2dpSetCodecConfigPreference( 251 @RpcParameter(name = "codecType") Integer codecType, 252 @RpcParameter(name = "sampleRate") Integer sampleRate, 253 @RpcParameter(name = "bitsPerSample") Integer bitsPerSample, 254 @RpcParameter(name = "channelMode") Integer channelMode, 255 @RpcParameter(name = "codecSpecific1") Long codecSpecific1) 256 throws Exception { 257 while (!sIsA2dpReady) { 258 continue; 259 } 260 BluetoothCodecConfig codecConfig = new BluetoothCodecConfig( 261 codecType, 262 BluetoothCodecConfig.CODEC_PRIORITY_HIGHEST, 263 sampleRate, 264 bitsPerSample, 265 channelMode, 266 codecSpecific1, 267 0L, 0L, 0L); 268 BluetoothDevice activeDevice = sA2dpProfile.getActiveDevice(); 269 if (activeDevice == null) { 270 Log.e("No active device"); 271 throw new Exception("No active device"); 272 } 273 BluetoothCodecStatus currentCodecStatus = sA2dpProfile.getCodecStatus(activeDevice); 274 BluetoothCodecConfig currentCodecConfig = currentCodecStatus.getCodecConfig(); 275 if (isSelectableCodec(codecConfig, currentCodecConfig) 276 && codecConfig.getCodecSpecific1() == currentCodecConfig.getCodecSpecific1()) { 277 Log.e("Same as current codec configuration " + currentCodecConfig); 278 return false; 279 } 280 for (BluetoothCodecConfig selectable : 281 currentCodecStatus.getCodecsSelectableCapabilities()) { 282 if (isSelectableCodec(codecConfig, selectable)) { 283 mBluetoothCodecConfig = codecConfig; 284 sA2dpProfile.setCodecConfigPreference(activeDevice, mBluetoothCodecConfig); 285 return true; 286 } 287 } 288 return false; 289 } 290 291 /** 292 * Get current active device codec config 293 * 294 * @return Current active device codec config, 295 */ 296 @Rpc(description = "Get current codec config.") bluetoothA2dpGetCurrentCodecConfig()297 public BluetoothCodecConfig bluetoothA2dpGetCurrentCodecConfig() throws Exception { 298 while (!sIsA2dpReady) { 299 continue; 300 } 301 if (sA2dpProfile.getActiveDevice() == null) { 302 Log.e("No active device."); 303 throw new Exception("No active device"); 304 } 305 return sA2dpProfile.getCodecStatus(sA2dpProfile.getActiveDevice()).getCodecConfig(); 306 } 307 308 @Override shutdown()309 public void shutdown() { 310 mService.unregisterReceiver(mBluetoothA2dpReceiver); 311 } 312 } 313