Home | History | Annotate | Download | only in bluetooth
      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.AudioSink, BluetoothUuid.AdvAudioDist,
     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 boolean sIsA2dpReady = false;
     55     private static BluetoothA2dp sA2dpProfile = null;
     56 
     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
     80         public void onServiceConnected(int profile, BluetoothProfile proxy) {
     81             sA2dpProfile = (BluetoothA2dp) proxy;
     82             sIsA2dpReady = true;
     83         }
     84 
     85         @Override
     86         public void onServiceDisconnected(int profile) {
     87             sIsA2dpReady = false;
     88         }
     89     }
     90 
     91     class BluetoothA2dpReceiver extends BroadcastReceiver {
     92         @Override
     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     */
    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     */
    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.")
    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")
    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.getAliasName() + " 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.")
    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.getAliasName());
    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.")
    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.")
    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 
    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.")
    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(null, 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.")
    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
    309     public void shutdown() {
    310         mService.unregisterReceiver(mBluetoothA2dpReceiver);
    311     }
    312 }
    313