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 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 if (mService != null) { 180 try { 181 return mService.isConnected(device); 182 } catch (RemoteException e) { 183 Log.e(TAG, e.toString()); 184 } 185 } else { 186 Log.w(TAG, "Proxy not attached to service"); 187 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 188 } 189 return false; 190 } 191 192 /** 193 * Initiate connection. Initiation of outgoing connections is not 194 * supported for MAP server. 195 */ connect(BluetoothDevice device)196 public boolean connect(BluetoothDevice device) { 197 if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE"); 198 if (mService != null) { 199 try { 200 return mService.connect(device); 201 } catch (RemoteException e) { 202 Log.e(TAG, e.toString()); 203 } 204 } else { 205 Log.w(TAG, "Proxy not attached to service"); 206 if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); 207 } 208 return false; 209 } 210 211 /** 212 * Initiate disconnect. 213 * 214 * @param device Remote Bluetooth Device 215 * @return false on error, true otherwise 216 */ disconnect(BluetoothDevice device)217 public boolean disconnect(BluetoothDevice device) { 218 if (DBG) Log.d(TAG, "disconnect(" + device + ")"); 219 if (mService != null && isEnabled() && 220 isValidDevice(device)) { 221 try { 222 return mService.disconnect(device); 223 } catch (RemoteException e) { 224 Log.e(TAG, Log.getStackTraceString(new Throwable())); 225 } 226 } 227 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 228 return false; 229 } 230 231 /** 232 * Get the list of connected devices. Currently at most one. 233 * 234 * @return list of connected devices 235 */ 236 @Override getConnectedDevices()237 public List<BluetoothDevice> getConnectedDevices() { 238 if (DBG) Log.d(TAG, "getConnectedDevices()"); 239 if (mService != null && isEnabled()) { 240 try { 241 return mService.getConnectedDevices(); 242 } catch (RemoteException e) { 243 Log.e(TAG, Log.getStackTraceString(new Throwable())); 244 return new ArrayList<>(); 245 } 246 } 247 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 248 return new ArrayList<>(); 249 } 250 251 /** 252 * Get the list of devices matching specified states. Currently at most one. 253 * 254 * @return list of matching devices 255 */ 256 @Override getDevicesMatchingConnectionStates(int[] states)257 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 258 if (DBG) Log.d(TAG, "getDevicesMatchingStates()"); 259 if (mService != null && isEnabled()) { 260 try { 261 return mService.getDevicesMatchingConnectionStates(states); 262 } catch (RemoteException e) { 263 Log.e(TAG, Log.getStackTraceString(new Throwable())); 264 return new ArrayList<>(); 265 } 266 } 267 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 268 return new ArrayList<>(); 269 } 270 271 /** 272 * Get connection state of device 273 * 274 * @return device connection state 275 */ 276 @Override getConnectionState(BluetoothDevice device)277 public int getConnectionState(BluetoothDevice device) { 278 if (DBG) Log.d(TAG, "getConnectionState(" + device + ")"); 279 if (mService != null && isEnabled() && 280 isValidDevice(device)) { 281 try { 282 return mService.getConnectionState(device); 283 } catch (RemoteException e) { 284 Log.e(TAG, Log.getStackTraceString(new Throwable())); 285 return BluetoothProfile.STATE_DISCONNECTED; 286 } 287 } 288 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 289 return BluetoothProfile.STATE_DISCONNECTED; 290 } 291 292 /** 293 * Set priority of the profile 294 * 295 * <p> The device should already be paired. Priority can be one of {@link #PRIORITY_ON} or 296 * {@link #PRIORITY_OFF}, 297 * 298 * @param device Paired bluetooth device 299 * @return true if priority is set, false on error 300 */ setPriority(BluetoothDevice device, int priority)301 public boolean setPriority(BluetoothDevice device, int priority) { 302 if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")"); 303 if (mService != null && isEnabled() && 304 isValidDevice(device)) { 305 if (priority != BluetoothProfile.PRIORITY_OFF && 306 priority != BluetoothProfile.PRIORITY_ON) { 307 return false; 308 } 309 try { 310 return mService.setPriority(device, priority); 311 } catch (RemoteException e) { 312 Log.e(TAG, Log.getStackTraceString(new Throwable())); 313 return false; 314 } 315 } 316 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 317 return false; 318 } 319 320 /** 321 * Get the priority of the profile. 322 * 323 * <p> The priority can be any of: 324 * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF}, 325 * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 326 * 327 * @param device Bluetooth device 328 * @return priority of the device 329 */ getPriority(BluetoothDevice device)330 public int getPriority(BluetoothDevice device) { 331 if (VDBG) Log.d(TAG, "getPriority(" + device + ")"); 332 if (mService != null && isEnabled() && 333 isValidDevice(device)) { 334 try { 335 return mService.getPriority(device); 336 } catch (RemoteException e) { 337 Log.e(TAG, Log.getStackTraceString(new Throwable())); 338 return PRIORITY_OFF; 339 } 340 } 341 if (mService == null) Log.w(TAG, "Proxy not attached to service"); 342 return PRIORITY_OFF; 343 } 344 345 /** 346 * Send a message. 347 * 348 * Send an SMS message to either the contacts primary number or the telephone number specified. 349 * 350 * @param device Bluetooth device 351 * @param contacts Uri[] of the contacts 352 * @param message Message to be sent 353 * @param sentIntent intent issued when message is sent 354 * @param deliveredIntent intent issued when message is delivered 355 * @return true if the message is enqueued, false on error 356 */ sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent)357 public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message, 358 PendingIntent sentIntent, PendingIntent deliveredIntent) { 359 if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message); 360 if (mService != null && isEnabled() && isValidDevice(device)) { 361 try { 362 return mService.sendMessage(device, contacts, message, sentIntent, deliveredIntent); 363 } catch (RemoteException e) { 364 Log.e(TAG, Log.getStackTraceString(new Throwable())); 365 return false; 366 } 367 } 368 return false; 369 } 370 371 /** 372 * Get unread messages. Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}. 373 * 374 * @param device Bluetooth device 375 * @return true if the message is enqueued, false on error 376 */ getUnreadMessages(BluetoothDevice device)377 public boolean getUnreadMessages(BluetoothDevice device) { 378 if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")"); 379 if (mService != null && isEnabled() && isValidDevice(device)) { 380 try { 381 return mService.getUnreadMessages(device); 382 } catch (RemoteException e) { 383 Log.e(TAG, Log.getStackTraceString(new Throwable())); 384 return false; 385 } 386 } 387 return false; 388 } 389 390 private final ServiceConnection mConnection = new ServiceConnection() { 391 public void onServiceConnected(ComponentName className, IBinder service) { 392 if (DBG) Log.d(TAG, "Proxy object connected"); 393 mService = IBluetoothMapClient.Stub.asInterface(service); 394 if (mServiceListener != null) { 395 mServiceListener.onServiceConnected(BluetoothProfile.MAP_CLIENT, 396 BluetoothMapClient.this); 397 } 398 } 399 400 public void onServiceDisconnected(ComponentName className) { 401 if (DBG) Log.d(TAG, "Proxy object disconnected"); 402 mService = null; 403 if (mServiceListener != null) { 404 mServiceListener.onServiceDisconnected(BluetoothProfile.MAP_CLIENT); 405 } 406 } 407 }; 408 isEnabled()409 private boolean isEnabled() { 410 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 411 if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true; 412 if (DBG) Log.d(TAG, "Bluetooth is Not enabled"); 413 return false; 414 } 415 isValidDevice(BluetoothDevice device)416 private boolean isValidDevice(BluetoothDevice device) { 417 if (device == null) return false; 418 419 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 420 return false; 421 } 422 423 424 } 425