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.content.ComponentName;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.ServiceConnection;
     23 import android.os.Binder;
     24 import android.os.IBinder;
     25 import android.os.RemoteException;
     26 import android.util.Log;
     27 
     28 import java.util.ArrayList;
     29 import java.util.List;
     30 
     31 /**
     32  * This class provides the APIs to control the Bluetooth MAP
     33  * Profile.
     34  *
     35  * @hide
     36  */
     37 public final class BluetoothMap implements BluetoothProfile {
     38 
     39     private static final String TAG = "BluetoothMap";
     40     private static final boolean DBG = true;
     41     private static final boolean VDBG = false;
     42 
     43     public static final String ACTION_CONNECTION_STATE_CHANGED =
     44             "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
     45 
     46     private volatile IBluetoothMap mService;
     47     private final Context mContext;
     48     private ServiceListener mServiceListener;
     49     private BluetoothAdapter mAdapter;
     50 
     51     /** There was an error trying to obtain the state */
     52     public static final int STATE_ERROR = -1;
     53 
     54     public static final int RESULT_FAILURE = 0;
     55     public static final int RESULT_SUCCESS = 1;
     56     /** Connection canceled before completion. */
     57     public static final int RESULT_CANCELED = 2;
     58 
     59     private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
     60             new IBluetoothStateChangeCallback.Stub() {
     61                 public void onBluetoothStateChange(boolean up) {
     62                     if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
     63                     if (!up) {
     64                         if (VDBG) Log.d(TAG, "Unbinding service...");
     65                         synchronized (mConnection) {
     66                             try {
     67                                 mService = null;
     68                                 mContext.unbindService(mConnection);
     69                             } catch (Exception re) {
     70                                 Log.e(TAG, "", re);
     71                             }
     72                         }
     73                     } else {
     74                         synchronized (mConnection) {
     75                             try {
     76                                 if (mService == null) {
     77                                     if (VDBG) Log.d(TAG, "Binding service...");
     78                                     doBind();
     79                                 }
     80                             } catch (Exception re) {
     81                                 Log.e(TAG, "", re);
     82                             }
     83                         }
     84                     }
     85                 }
     86             };
     87 
     88     /**
     89      * Create a BluetoothMap proxy object.
     90      */
     91     /*package*/ BluetoothMap(Context context, ServiceListener l) {
     92         if (DBG) Log.d(TAG, "Create BluetoothMap proxy object");
     93         mContext = context;
     94         mServiceListener = l;
     95         mAdapter = BluetoothAdapter.getDefaultAdapter();
     96         IBluetoothManager mgr = mAdapter.getBluetoothManager();
     97         if (mgr != null) {
     98             try {
     99                 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
    100             } catch (RemoteException e) {
    101                 Log.e(TAG, "", e);
    102             }
    103         }
    104         doBind();
    105     }
    106 
    107     boolean doBind() {
    108         Intent intent = new Intent(IBluetoothMap.class.getName());
    109         ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
    110         intent.setComponent(comp);
    111         if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
    112                 mContext.getUser())) {
    113             Log.e(TAG, "Could not bind to Bluetooth MAP Service with " + intent);
    114             return false;
    115         }
    116         return true;
    117     }
    118 
    119     protected void finalize() throws Throwable {
    120         try {
    121             close();
    122         } finally {
    123             super.finalize();
    124         }
    125     }
    126 
    127     /**
    128      * Close the connection to the backing service.
    129      * Other public functions of BluetoothMap will return default error
    130      * results once close() has been called. Multiple invocations of close()
    131      * are ok.
    132      */
    133     public synchronized void close() {
    134         IBluetoothManager mgr = mAdapter.getBluetoothManager();
    135         if (mgr != null) {
    136             try {
    137                 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
    138             } catch (Exception e) {
    139                 Log.e(TAG, "", e);
    140             }
    141         }
    142 
    143         synchronized (mConnection) {
    144             if (mService != null) {
    145                 try {
    146                     mService = null;
    147                     mContext.unbindService(mConnection);
    148                 } catch (Exception re) {
    149                     Log.e(TAG, "", re);
    150                 }
    151             }
    152         }
    153         mServiceListener = null;
    154     }
    155 
    156     /**
    157      * Get the current state of the BluetoothMap service.
    158      *
    159      * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
    160      * connected to the Map service.
    161      */
    162     public int getState() {
    163         if (VDBG) log("getState()");
    164         final IBluetoothMap service = mService;
    165         if (service != null) {
    166             try {
    167                 return service.getState();
    168             } catch (RemoteException e) {
    169                 Log.e(TAG, e.toString());
    170             }
    171         } else {
    172             Log.w(TAG, "Proxy not attached to service");
    173             if (DBG) log(Log.getStackTraceString(new Throwable()));
    174         }
    175         return BluetoothMap.STATE_ERROR;
    176     }
    177 
    178     /**
    179      * Get the currently connected remote Bluetooth device (PCE).
    180      *
    181      * @return The remote Bluetooth device, or null if not in connected or connecting state, or if
    182      * this proxy object is not connected to the Map service.
    183      */
    184     public BluetoothDevice getClient() {
    185         if (VDBG) log("getClient()");
    186         final IBluetoothMap service = mService;
    187         if (service != null) {
    188             try {
    189                 return service.getClient();
    190             } catch (RemoteException e) {
    191                 Log.e(TAG, e.toString());
    192             }
    193         } else {
    194             Log.w(TAG, "Proxy not attached to service");
    195             if (DBG) log(Log.getStackTraceString(new Throwable()));
    196         }
    197         return null;
    198     }
    199 
    200     /**
    201      * Returns true if the specified Bluetooth device is connected.
    202      * Returns false if not connected, or if this proxy object is not
    203      * currently connected to the Map service.
    204      */
    205     public boolean isConnected(BluetoothDevice device) {
    206         if (VDBG) log("isConnected(" + device + ")");
    207         final IBluetoothMap service = mService;
    208         if (service != null) {
    209             try {
    210                 return service.isConnected(device);
    211             } catch (RemoteException e) {
    212                 Log.e(TAG, e.toString());
    213             }
    214         } else {
    215             Log.w(TAG, "Proxy not attached to service");
    216             if (DBG) log(Log.getStackTraceString(new Throwable()));
    217         }
    218         return false;
    219     }
    220 
    221     /**
    222      * Initiate connection. Initiation of outgoing connections is not
    223      * supported for MAP server.
    224      */
    225     public boolean connect(BluetoothDevice device) {
    226         if (DBG) log("connect(" + device + ")" + "not supported for MAPS");
    227         return false;
    228     }
    229 
    230     /**
    231      * Initiate disconnect.
    232      *
    233      * @param device Remote Bluetooth Device
    234      * @return false on error, true otherwise
    235      */
    236     public boolean disconnect(BluetoothDevice device) {
    237         if (DBG) log("disconnect(" + device + ")");
    238         final IBluetoothMap service = mService;
    239         if (service != null && isEnabled() && isValidDevice(device)) {
    240             try {
    241                 return service.disconnect(device);
    242             } catch (RemoteException e) {
    243                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    244                 return false;
    245             }
    246         }
    247         if (service == null) Log.w(TAG, "Proxy not attached to service");
    248         return false;
    249     }
    250 
    251     /**
    252      * Check class bits for possible Map support.
    253      * This is a simple heuristic that tries to guess if a device with the
    254      * given class bits might support Map. It is not accurate for all
    255      * devices. It tries to err on the side of false positives.
    256      *
    257      * @return True if this device might support Map.
    258      */
    259     public static boolean doesClassMatchSink(BluetoothClass btClass) {
    260         // TODO optimize the rule
    261         switch (btClass.getDeviceClass()) {
    262             case BluetoothClass.Device.COMPUTER_DESKTOP:
    263             case BluetoothClass.Device.COMPUTER_LAPTOP:
    264             case BluetoothClass.Device.COMPUTER_SERVER:
    265             case BluetoothClass.Device.COMPUTER_UNCATEGORIZED:
    266                 return true;
    267             default:
    268                 return false;
    269         }
    270     }
    271 
    272     /**
    273      * Get the list of connected devices. Currently at most one.
    274      *
    275      * @return list of connected devices
    276      */
    277     public List<BluetoothDevice> getConnectedDevices() {
    278         if (DBG) log("getConnectedDevices()");
    279         final IBluetoothMap service = mService;
    280         if (service != null && isEnabled()) {
    281             try {
    282                 return service.getConnectedDevices();
    283             } catch (RemoteException e) {
    284                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    285                 return new ArrayList<BluetoothDevice>();
    286             }
    287         }
    288         if (service == null) Log.w(TAG, "Proxy not attached to service");
    289         return new ArrayList<BluetoothDevice>();
    290     }
    291 
    292     /**
    293      * Get the list of devices matching specified states. Currently at most one.
    294      *
    295      * @return list of matching devices
    296      */
    297     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    298         if (DBG) log("getDevicesMatchingStates()");
    299         final IBluetoothMap service = mService;
    300         if (service != null && isEnabled()) {
    301             try {
    302                 return service.getDevicesMatchingConnectionStates(states);
    303             } catch (RemoteException e) {
    304                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    305                 return new ArrayList<BluetoothDevice>();
    306             }
    307         }
    308         if (service == null) Log.w(TAG, "Proxy not attached to service");
    309         return new ArrayList<BluetoothDevice>();
    310     }
    311 
    312     /**
    313      * Get connection state of device
    314      *
    315      * @return device connection state
    316      */
    317     public int getConnectionState(BluetoothDevice device) {
    318         if (DBG) log("getConnectionState(" + device + ")");
    319         final IBluetoothMap service = mService;
    320         if (service != null && isEnabled() && isValidDevice(device)) {
    321             try {
    322                 return service.getConnectionState(device);
    323             } catch (RemoteException e) {
    324                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    325                 return BluetoothProfile.STATE_DISCONNECTED;
    326             }
    327         }
    328         if (service == null) Log.w(TAG, "Proxy not attached to service");
    329         return BluetoothProfile.STATE_DISCONNECTED;
    330     }
    331 
    332     /**
    333      * Set priority of the profile
    334      *
    335      * <p> The device should already be paired.
    336      * Priority can be one of {@link #PRIORITY_ON} or
    337      * {@link #PRIORITY_OFF},
    338      *
    339      * @param device Paired bluetooth device
    340      * @param priority
    341      * @return true if priority is set, false on error
    342      */
    343     public boolean setPriority(BluetoothDevice device, int priority) {
    344         if (DBG) log("setPriority(" + device + ", " + priority + ")");
    345         final IBluetoothMap service = mService;
    346         if (service != null && isEnabled() && isValidDevice(device)) {
    347             if (priority != BluetoothProfile.PRIORITY_OFF
    348                     && priority != BluetoothProfile.PRIORITY_ON) {
    349                 return false;
    350             }
    351             try {
    352                 return service.setPriority(device, priority);
    353             } catch (RemoteException e) {
    354                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    355                 return false;
    356             }
    357         }
    358         if (service == null) Log.w(TAG, "Proxy not attached to service");
    359         return false;
    360     }
    361 
    362     /**
    363      * Get the priority of the profile.
    364      *
    365      * <p> The priority can be any of:
    366      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
    367      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
    368      *
    369      * @param device Bluetooth device
    370      * @return priority of the device
    371      */
    372     public int getPriority(BluetoothDevice device) {
    373         if (VDBG) log("getPriority(" + device + ")");
    374         final IBluetoothMap service = mService;
    375         if (service != null && isEnabled() && isValidDevice(device)) {
    376             try {
    377                 return service.getPriority(device);
    378             } catch (RemoteException e) {
    379                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    380                 return PRIORITY_OFF;
    381             }
    382         }
    383         if (service == null) Log.w(TAG, "Proxy not attached to service");
    384         return PRIORITY_OFF;
    385     }
    386 
    387     private final ServiceConnection mConnection = new ServiceConnection() {
    388         public void onServiceConnected(ComponentName className, IBinder service) {
    389             if (DBG) log("Proxy object connected");
    390             mService = IBluetoothMap.Stub.asInterface(Binder.allowBlocking(service));
    391             if (mServiceListener != null) {
    392                 mServiceListener.onServiceConnected(BluetoothProfile.MAP, BluetoothMap.this);
    393             }
    394         }
    395 
    396         public void onServiceDisconnected(ComponentName className) {
    397             if (DBG) log("Proxy object disconnected");
    398             mService = null;
    399             if (mServiceListener != null) {
    400                 mServiceListener.onServiceDisconnected(BluetoothProfile.MAP);
    401             }
    402         }
    403     };
    404 
    405     private static void log(String msg) {
    406         Log.d(TAG, msg);
    407     }
    408 
    409     private boolean isEnabled() {
    410         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    411         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
    412         log("Bluetooth is Not enabled");
    413         return false;
    414     }
    415     private static boolean isValidDevice(BluetoothDevice device) {
    416         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
    417     }
    418 
    419 }
    420