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 final private 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 */ BluetoothMapClient(Context context, ServiceListener l)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 doBind()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 android.os.Process.myUserHandle())) { 129 Log.e(TAG, "Could not bind to Bluetooth MAP MCE Service with " + intent); 130 return false; 131 } 132 return true; 133 } 134 finalize()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 */ close()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 */ isConnected(BluetoothDevice device)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 */ connect(BluetoothDevice device)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 */ disconnect(BluetoothDevice device)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 getConnectedDevices()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 getDevicesMatchingConnectionStates(int[] states)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 getConnectionState(BluetoothDevice device)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 */ setPriority(BluetoothDevice device, int priority)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 */ getPriority(BluetoothDevice device)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 */ sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)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 */ getUnreadMessages(BluetoothDevice device)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 isEnabled()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 isValidDevice(BluetoothDevice device)422 private static boolean isValidDevice(BluetoothDevice device) { 423 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 424 } 425 426 } 427