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