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 */ BluetoothMapClient(Context context, ServiceListener listener)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 finalize()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 */ close()108 public void close() { 109 mProfileConnector.disconnect(); 110 } 111 getService()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 */ isConnected(BluetoothDevice device)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 */ connect(BluetoothDevice device)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 */ disconnect(BluetoothDevice device)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 getConnectedDevices()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 getDevicesMatchingConnectionStates(int[] states)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 getConnectionState(BluetoothDevice device)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 */ setPriority(BluetoothDevice device, int priority)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 */ getPriority(BluetoothDevice device)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 sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)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 */ getUnreadMessages(BluetoothDevice device)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 */ isUploadingSupported(BluetoothDevice device)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 isEnabled()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 isValidDevice(BluetoothDevice device)366 private static boolean isValidDevice(BluetoothDevice device) { 367 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 368 } 369 370 } 371