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