Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2014 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 android.bluetooth;
     18 
     19 import android.annotation.UnsupportedAppUsage;
     20 import android.content.Context;
     21 import android.os.Binder;
     22 import android.os.IBinder;
     23 import android.os.RemoteException;
     24 import android.util.Log;
     25 
     26 import java.util.ArrayList;
     27 import java.util.List;
     28 
     29 /**
     30  * This class provides the public APIs to control the Bluetooth A2DP Sink
     31  * profile.
     32  *
     33  * <p>BluetoothA2dpSink is a proxy object for controlling the Bluetooth A2DP Sink
     34  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
     35  * the BluetoothA2dpSink proxy object.
     36  *
     37  * @hide
     38  */
     39 public final class BluetoothA2dpSink implements BluetoothProfile {
     40     private static final String TAG = "BluetoothA2dpSink";
     41     private static final boolean DBG = true;
     42     private static final boolean VDBG = false;
     43 
     44     /**
     45      * Intent used to broadcast the change in connection state of the A2DP Sink
     46      * profile.
     47      *
     48      * <p>This intent will have 3 extras:
     49      * <ul>
     50      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
     51      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
     52      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
     53      * </ul>
     54      *
     55      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     56      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
     57      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
     58      *
     59      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
     60      * receive.
     61      */
     62     public static final String ACTION_CONNECTION_STATE_CHANGED =
     63             "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
     64 
     65     /**
     66      * Intent used to broadcast the change in the Playing state of the A2DP Sink
     67      * profile.
     68      *
     69      * <p>This intent will have 3 extras:
     70      * <ul>
     71      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
     72      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
     73      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
     74      * </ul>
     75      *
     76      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
     77      * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
     78      *
     79      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
     80      * receive.
     81      */
     82     public static final String ACTION_PLAYING_STATE_CHANGED =
     83             "android.bluetooth.a2dp-sink.profile.action.PLAYING_STATE_CHANGED";
     84 
     85     /**
     86      * A2DP sink device is streaming music. This state can be one of
     87      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
     88      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
     89      */
     90     public static final int STATE_PLAYING = 10;
     91 
     92     /**
     93      * A2DP sink device is NOT streaming music. This state can be one of
     94      * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
     95      * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
     96      */
     97     public static final int STATE_NOT_PLAYING = 11;
     98 
     99     /**
    100      * Intent used to broadcast the change in the Playing state of the A2DP Sink
    101      * profile.
    102      *
    103      * <p>This intent will have 3 extras:
    104      * <ul>
    105      * <li> {@link #EXTRA_AUDIO_CONFIG} - The audio configuration for the remote device. </li>
    106      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
    107      * </ul>
    108      *
    109      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
    110      * receive.
    111      */
    112     public static final String ACTION_AUDIO_CONFIG_CHANGED =
    113             "android.bluetooth.a2dp-sink.profile.action.AUDIO_CONFIG_CHANGED";
    114 
    115     /**
    116      * Extra for the {@link #ACTION_AUDIO_CONFIG_CHANGED} intent.
    117      *
    118      * This extra represents the current audio configuration of the A2DP source device.
    119      * {@see BluetoothAudioConfig}
    120      */
    121     public static final String EXTRA_AUDIO_CONFIG =
    122             "android.bluetooth.a2dp-sink.profile.extra.AUDIO_CONFIG";
    123 
    124     private BluetoothAdapter mAdapter;
    125     private final BluetoothProfileConnector<IBluetoothA2dpSink> mProfileConnector =
    126             new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK,
    127                     "BluetoothA2dpSink", IBluetoothA2dpSink.class.getName()) {
    128                 @Override
    129                 public IBluetoothA2dpSink getServiceInterface(IBinder service) {
    130                     return IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
    131                 }
    132     };
    133 
    134     /**
    135      * Create a BluetoothA2dp proxy object for interacting with the local
    136      * Bluetooth A2DP service.
    137      */
    138     /*package*/ BluetoothA2dpSink(Context context, ServiceListener listener) {
    139         mAdapter = BluetoothAdapter.getDefaultAdapter();
    140         mProfileConnector.connect(context, listener);
    141     }
    142 
    143     /*package*/ void close() {
    144         mProfileConnector.disconnect();
    145     }
    146 
    147     private IBluetoothA2dpSink getService() {
    148         return mProfileConnector.getService();
    149     }
    150 
    151     @Override
    152     public void finalize() {
    153         close();
    154     }
    155 
    156     /**
    157      * Initiate connection to a profile of the remote bluetooth device.
    158      *
    159      * <p> Currently, the system supports only 1 connection to the
    160      * A2DP profile. The API will automatically disconnect connected
    161      * devices before connecting.
    162      *
    163      * <p> This API returns false in scenarios like the profile on the
    164      * device is already connected or Bluetooth is not turned on.
    165      * When this API returns true, it is guaranteed that
    166      * connection state intent for the profile will be broadcasted with
    167      * the state. Users can get the connection state of the profile
    168      * from this intent.
    169      *
    170      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    171      * permission.
    172      *
    173      * @param device Remote Bluetooth Device
    174      * @return false on immediate error, true otherwise
    175      * @hide
    176      */
    177     public boolean connect(BluetoothDevice device) {
    178         if (DBG) log("connect(" + device + ")");
    179         final IBluetoothA2dpSink service = getService();
    180         if (service != null && isEnabled() && isValidDevice(device)) {
    181             try {
    182                 return service.connect(device);
    183             } catch (RemoteException e) {
    184                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    185                 return false;
    186             }
    187         }
    188         if (service == null) Log.w(TAG, "Proxy not attached to service");
    189         return false;
    190     }
    191 
    192     /**
    193      * Initiate disconnection from a profile
    194      *
    195      * <p> This API will return false in scenarios like the profile on the
    196      * Bluetooth device is not in connected state etc. When this API returns,
    197      * true, it is guaranteed that the connection state change
    198      * intent will be broadcasted with the state. Users can get the
    199      * disconnection state of the profile from this intent.
    200      *
    201      * <p> If the disconnection is initiated by a remote device, the state
    202      * will transition from {@link #STATE_CONNECTED} to
    203      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
    204      * host (local) device the state will transition from
    205      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
    206      * state {@link #STATE_DISCONNECTED}. The transition to
    207      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
    208      * two scenarios.
    209      *
    210      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    211      * permission.
    212      *
    213      * @param device Remote Bluetooth Device
    214      * @return false on immediate error, true otherwise
    215      * @hide
    216      */
    217     @UnsupportedAppUsage
    218     public boolean disconnect(BluetoothDevice device) {
    219         if (DBG) log("disconnect(" + device + ")");
    220         final IBluetoothA2dpSink service = getService();
    221         if (service != null && isEnabled() && isValidDevice(device)) {
    222             try {
    223                 return service.disconnect(device);
    224             } catch (RemoteException e) {
    225                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    226                 return false;
    227             }
    228         }
    229         if (service == null) Log.w(TAG, "Proxy not attached to service");
    230         return false;
    231     }
    232 
    233     /**
    234      * {@inheritDoc}
    235      */
    236     @Override
    237     public List<BluetoothDevice> getConnectedDevices() {
    238         if (VDBG) log("getConnectedDevices()");
    239         final IBluetoothA2dpSink service = getService();
    240         if (service != null && isEnabled()) {
    241             try {
    242                 return service.getConnectedDevices();
    243             } catch (RemoteException e) {
    244                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    245                 return new ArrayList<BluetoothDevice>();
    246             }
    247         }
    248         if (service == null) Log.w(TAG, "Proxy not attached to service");
    249         return new ArrayList<BluetoothDevice>();
    250     }
    251 
    252     /**
    253      * {@inheritDoc}
    254      */
    255     @Override
    256     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    257         if (VDBG) log("getDevicesMatchingStates()");
    258         final IBluetoothA2dpSink service = getService();
    259         if (service != null && isEnabled()) {
    260             try {
    261                 return service.getDevicesMatchingConnectionStates(states);
    262             } catch (RemoteException e) {
    263                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    264                 return new ArrayList<BluetoothDevice>();
    265             }
    266         }
    267         if (service == null) Log.w(TAG, "Proxy not attached to service");
    268         return new ArrayList<BluetoothDevice>();
    269     }
    270 
    271     /**
    272      * {@inheritDoc}
    273      */
    274     @Override
    275     public int getConnectionState(BluetoothDevice device) {
    276         if (VDBG) log("getState(" + device + ")");
    277         final IBluetoothA2dpSink service = getService();
    278         if (service != null && isEnabled() && isValidDevice(device)) {
    279             try {
    280                 return service.getConnectionState(device);
    281             } catch (RemoteException e) {
    282                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    283                 return BluetoothProfile.STATE_DISCONNECTED;
    284             }
    285         }
    286         if (service == null) Log.w(TAG, "Proxy not attached to service");
    287         return BluetoothProfile.STATE_DISCONNECTED;
    288     }
    289 
    290     /**
    291      * Get the current audio configuration for the A2DP source device,
    292      * or null if the device has no audio configuration
    293      *
    294      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    295      *
    296      * @param device Remote bluetooth device.
    297      * @return audio configuration for the device, or null
    298      *
    299      * {@see BluetoothAudioConfig}
    300      */
    301     public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
    302         if (VDBG) log("getAudioConfig(" + device + ")");
    303         final IBluetoothA2dpSink service = getService();
    304         if (service != null && isEnabled() && isValidDevice(device)) {
    305             try {
    306                 return service.getAudioConfig(device);
    307             } catch (RemoteException e) {
    308                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    309                 return null;
    310             }
    311         }
    312         if (service == null) Log.w(TAG, "Proxy not attached to service");
    313         return null;
    314     }
    315 
    316     /**
    317      * Set priority of the profile
    318      *
    319      * <p> The device should already be paired.
    320      * Priority can be one of {@link #PRIORITY_ON} orgetBluetoothManager
    321      * {@link #PRIORITY_OFF},
    322      *
    323      * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    324      * permission.
    325      *
    326      * @param device Paired bluetooth device
    327      * @param priority
    328      * @return true if priority is set, false on error
    329      * @hide
    330      */
    331     public boolean setPriority(BluetoothDevice device, int priority) {
    332         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    333         final IBluetoothA2dpSink service = getService();
    334         if (service != null && isEnabled() && isValidDevice(device)) {
    335             if (priority != BluetoothProfile.PRIORITY_OFF
    336                     && priority != BluetoothProfile.PRIORITY_ON) {
    337                 return false;
    338             }
    339             try {
    340                 return service.setPriority(device, priority);
    341             } catch (RemoteException e) {
    342                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    343                 return false;
    344             }
    345         }
    346         if (service == null) Log.w(TAG, "Proxy not attached to service");
    347         return false;
    348     }
    349 
    350     /**
    351      * Get the priority of the profile.
    352      *
    353      * <p> The priority can be any of:
    354      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
    355      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
    356      *
    357      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    358      *
    359      * @param device Bluetooth device
    360      * @return priority of the device
    361      * @hide
    362      */
    363     public int getPriority(BluetoothDevice device) {
    364         if (VDBG) log("getPriority(" + device + ")");
    365         final IBluetoothA2dpSink service = getService();
    366         if (service != null && isEnabled() && isValidDevice(device)) {
    367             try {
    368                 return service.getPriority(device);
    369             } catch (RemoteException e) {
    370                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    371                 return BluetoothProfile.PRIORITY_OFF;
    372             }
    373         }
    374         if (service == null) Log.w(TAG, "Proxy not attached to service");
    375         return BluetoothProfile.PRIORITY_OFF;
    376     }
    377 
    378     /**
    379      * Check if A2DP profile is streaming music.
    380      *
    381      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
    382      *
    383      * @param device BluetoothDevice device
    384      */
    385     public boolean isA2dpPlaying(BluetoothDevice device) {
    386         final IBluetoothA2dpSink service = getService();
    387         if (service != null && isEnabled() && isValidDevice(device)) {
    388             try {
    389                 return service.isA2dpPlaying(device);
    390             } catch (RemoteException e) {
    391                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
    392                 return false;
    393             }
    394         }
    395         if (service == null) Log.w(TAG, "Proxy not attached to service");
    396         return false;
    397     }
    398 
    399     /**
    400      * Helper for converting a state to a string.
    401      *
    402      * For debug use only - strings are not internationalized.
    403      *
    404      * @hide
    405      */
    406     public static String stateToString(int state) {
    407         switch (state) {
    408             case STATE_DISCONNECTED:
    409                 return "disconnected";
    410             case STATE_CONNECTING:
    411                 return "connecting";
    412             case STATE_CONNECTED:
    413                 return "connected";
    414             case STATE_DISCONNECTING:
    415                 return "disconnecting";
    416             case STATE_PLAYING:
    417                 return "playing";
    418             case STATE_NOT_PLAYING:
    419                 return "not playing";
    420             default:
    421                 return "<unknown state " + state + ">";
    422         }
    423     }
    424 
    425     private boolean isEnabled() {
    426         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
    427     }
    428 
    429     private static boolean isValidDevice(BluetoothDevice device) {
    430         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
    431     }
    432 
    433     private static void log(String msg) {
    434         Log.d(TAG, msg);
    435     }
    436 }
    437