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.annotation.UnsupportedAppUsage;
     20 import android.app.PendingIntent;
     21 import android.content.Context;
     22 import android.net.Uri;
     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 MCE Profile.
     33  *
     34  * @hide
     35  */
     36 public final class BluetoothMapClient implements BluetoothProfile {
     37 
     38     private static final String TAG = "BluetoothMapClient";
     39     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
     40     private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
     41 
     42     public static final String ACTION_CONNECTION_STATE_CHANGED =
     43             "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
     44     public static final String ACTION_MESSAGE_RECEIVED =
     45             "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED";
     46     /* Actions to be used for pending intents */
     47     public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY =
     48             "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY";
     49     public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
     50             "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
     51 
     52     /* Extras used in ACTION_MESSAGE_RECEIVED intent.
     53      * NOTE: HANDLE is only valid for a single session with the device. */
     54     public static final String EXTRA_MESSAGE_HANDLE =
     55             "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
     56     public static final String EXTRA_MESSAGE_TIMESTAMP =
     57             "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
     58     public static final String EXTRA_MESSAGE_READ_STATUS =
     59             "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
     60     public static final String EXTRA_SENDER_CONTACT_URI =
     61             "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
     62     public static final String EXTRA_SENDER_CONTACT_NAME =
     63             "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
     64 
     65     /** There was an error trying to obtain the state */
     66     public static final int STATE_ERROR = -1;
     67 
     68     public static final int RESULT_FAILURE = 0;
     69     public static final int RESULT_SUCCESS = 1;
     70     /** Connection canceled before completion. */
     71     public static final int RESULT_CANCELED = 2;
     72 
     73     private static final int UPLOADING_FEATURE_BITMASK = 0x08;
     74 
     75     private BluetoothAdapter mAdapter;
     76     private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector =
     77             new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT,
     78                     "BluetoothMapClient", IBluetoothMapClient.class.getName()) {
     79                 @Override
     80                 public IBluetoothMapClient getServiceInterface(IBinder service) {
     81                     return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
     82                 }
     83     };
     84 
     85     /**
     86      * Create a BluetoothMapClient proxy object.
     87      */
     88     /*package*/ BluetoothMapClient(Context context, ServiceListener listener) {
     89         if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object");
     90         mAdapter = BluetoothAdapter.getDefaultAdapter();
     91         mProfileConnector.connect(context, listener);
     92     }
     93 
     94     protected void finalize() throws Throwable {
     95         try {
     96             close();
     97         } finally {
     98             super.finalize();
     99         }
    100     }
    101 
    102     /**
    103      * Close the connection to the backing service.
    104      * Other public functions of BluetoothMap will return default error
    105      * results once close() has been called. Multiple invocations of close()
    106      * are ok.
    107      */
    108     public void close() {
    109         mProfileConnector.disconnect();
    110     }
    111 
    112     private IBluetoothMapClient getService() {
    113         return mProfileConnector.getService();
    114     }
    115 
    116     /**
    117      * Returns true if the specified Bluetooth device is connected.
    118      * Returns false if not connected, or if this proxy object is not
    119      * currently connected to the Map service.
    120      */
    121     public boolean isConnected(BluetoothDevice device) {
    122         if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
    123         final IBluetoothMapClient service = getService();
    124         if (service != null) {
    125             try {
    126                 return service.isConnected(device);
    127             } catch (RemoteException e) {
    128                 Log.e(TAG, e.toString());
    129             }
    130         } else {
    131             Log.w(TAG, "Proxy not attached to service");
    132             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    133         }
    134         return false;
    135     }
    136 
    137     /**
    138      * Initiate connection. Initiation of outgoing connections is not
    139      * supported for MAP server.
    140      */
    141     public boolean connect(BluetoothDevice device) {
    142         if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
    143         final IBluetoothMapClient service = getService();
    144         if (service != null) {
    145             try {
    146                 return service.connect(device);
    147             } catch (RemoteException e) {
    148                 Log.e(TAG, e.toString());
    149             }
    150         } else {
    151             Log.w(TAG, "Proxy not attached to service");
    152             if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
    153         }
    154         return false;
    155     }
    156 
    157     /**
    158      * Initiate disconnect.
    159      *
    160      * @param device Remote Bluetooth Device
    161      * @return false on error, true otherwise
    162      */
    163     public boolean disconnect(BluetoothDevice device) {
    164         if (DBG) Log.d(TAG, "disconnect(" + device + ")");
    165         final IBluetoothMapClient service = getService();
    166         if (service != null && isEnabled() && isValidDevice(device)) {
    167             try {
    168                 return service.disconnect(device);
    169             } catch (RemoteException e) {
    170                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    171             }
    172         }
    173         if (service == null) Log.w(TAG, "Proxy not attached to service");
    174         return false;
    175     }
    176 
    177     /**
    178      * Get the list of connected devices. Currently at most one.
    179      *
    180      * @return list of connected devices
    181      */
    182     @Override
    183     public List<BluetoothDevice> getConnectedDevices() {
    184         if (DBG) Log.d(TAG, "getConnectedDevices()");
    185         final IBluetoothMapClient service = getService();
    186         if (service != null && isEnabled()) {
    187             try {
    188                 return service.getConnectedDevices();
    189             } catch (RemoteException e) {
    190                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    191                 return new ArrayList<>();
    192             }
    193         }
    194         if (service == null) Log.w(TAG, "Proxy not attached to service");
    195         return new ArrayList<>();
    196     }
    197 
    198     /**
    199      * Get the list of devices matching specified states. Currently at most one.
    200      *
    201      * @return list of matching devices
    202      */
    203     @Override
    204     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
    205         if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
    206         final IBluetoothMapClient service = getService();
    207         if (service != null && isEnabled()) {
    208             try {
    209                 return service.getDevicesMatchingConnectionStates(states);
    210             } catch (RemoteException e) {
    211                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    212                 return new ArrayList<>();
    213             }
    214         }
    215         if (service == null) Log.w(TAG, "Proxy not attached to service");
    216         return new ArrayList<>();
    217     }
    218 
    219     /**
    220      * Get connection state of device
    221      *
    222      * @return device connection state
    223      */
    224     @Override
    225     public int getConnectionState(BluetoothDevice device) {
    226         if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
    227         final IBluetoothMapClient service = getService();
    228         if (service != null && isEnabled() && isValidDevice(device)) {
    229             try {
    230                 return service.getConnectionState(device);
    231             } catch (RemoteException e) {
    232                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    233                 return BluetoothProfile.STATE_DISCONNECTED;
    234             }
    235         }
    236         if (service == null) Log.w(TAG, "Proxy not attached to service");
    237         return BluetoothProfile.STATE_DISCONNECTED;
    238     }
    239 
    240     /**
    241      * Set priority of the profile
    242      *
    243      * <p> The device should already be paired.  Priority can be one of {@link #PRIORITY_ON} or
    244      * {@link #PRIORITY_OFF},
    245      *
    246      * @param device Paired bluetooth device
    247      * @return true if priority is set, false on error
    248      */
    249     public boolean setPriority(BluetoothDevice device, int priority) {
    250         if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
    251         final IBluetoothMapClient service = getService();
    252         if (service != null && isEnabled() && isValidDevice(device)) {
    253             if (priority != BluetoothProfile.PRIORITY_OFF
    254                     && priority != BluetoothProfile.PRIORITY_ON) {
    255                 return false;
    256             }
    257             try {
    258                 return service.setPriority(device, priority);
    259             } catch (RemoteException e) {
    260                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    261                 return false;
    262             }
    263         }
    264         if (service == null) Log.w(TAG, "Proxy not attached to service");
    265         return false;
    266     }
    267 
    268     /**
    269      * Get the priority of the profile.
    270      *
    271      * <p> The priority can be any of:
    272      * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
    273      * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
    274      *
    275      * @param device Bluetooth device
    276      * @return priority of the device
    277      */
    278     public int getPriority(BluetoothDevice device) {
    279         if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
    280         final IBluetoothMapClient service = getService();
    281         if (service != null && isEnabled() && isValidDevice(device)) {
    282             try {
    283                 return service.getPriority(device);
    284             } catch (RemoteException e) {
    285                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    286                 return PRIORITY_OFF;
    287             }
    288         }
    289         if (service == null) Log.w(TAG, "Proxy not attached to service");
    290         return PRIORITY_OFF;
    291     }
    292 
    293     /**
    294      * Send a message.
    295      *
    296      * Send an SMS message to either the contacts primary number or the telephone number specified.
    297      *
    298      * @param device Bluetooth device
    299      * @param contacts Uri[] of the contacts
    300      * @param message Message to be sent
    301      * @param sentIntent intent issued when message is sent
    302      * @param deliveredIntent intent issued when message is delivered
    303      * @return true if the message is enqueued, false on error
    304      */
    305     @UnsupportedAppUsage
    306     public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
    307             PendingIntent sentIntent, PendingIntent deliveredIntent) {
    308         if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
    309         final IBluetoothMapClient service = getService();
    310         if (service != null && isEnabled() && isValidDevice(device)) {
    311             try {
    312                 return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
    313             } catch (RemoteException e) {
    314                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    315                 return false;
    316             }
    317         }
    318         return false;
    319     }
    320 
    321     /**
    322      * Get unread messages.  Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}.
    323      *
    324      * @param device Bluetooth device
    325      * @return true if the message is enqueued, false on error
    326      */
    327     public boolean getUnreadMessages(BluetoothDevice device) {
    328         if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
    329         final IBluetoothMapClient service = getService();
    330         if (service != null && isEnabled() && isValidDevice(device)) {
    331             try {
    332                 return service.getUnreadMessages(device);
    333             } catch (RemoteException e) {
    334                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    335                 return false;
    336             }
    337         }
    338         return false;
    339     }
    340 
    341     /**
    342      * Returns the "Uploading" feature bit value from the SDP record's
    343      * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114).
    344      * @param device The Bluetooth device to get this value for.
    345      * @return Returns true if the Uploading bit value in SDP record's
    346      *         MapSupportedFeatures field is set. False is returned otherwise.
    347      */
    348     public boolean isUploadingSupported(BluetoothDevice device) {
    349         final IBluetoothMapClient service = getService();
    350         try {
    351             return (service != null && isEnabled() && isValidDevice(device))
    352                 && ((service.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0);
    353         } catch (RemoteException e) {
    354             Log.e(TAG, e.getMessage());
    355         }
    356         return false;
    357     }
    358 
    359     private boolean isEnabled() {
    360         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
    361         if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
    362         if (DBG) Log.d(TAG, "Bluetooth is Not enabled");
    363         return false;
    364     }
    365 
    366     private static boolean isValidDevice(BluetoothDevice device) {
    367         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
    368     }
    369 
    370 }
    371