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