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.NonNull; 20 import android.annotation.RequiresPermission; 21 import android.annotation.SdkConstant; 22 import android.annotation.SdkConstant.SdkConstantType; 23 import android.annotation.SuppressLint; 24 import android.annotation.SystemApi; 25 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 26 import android.content.AttributionSource; 27 import android.content.Context; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.util.CloseGuard; 31 import android.util.Log; 32 33 import java.util.Collections; 34 import java.util.List; 35 36 /** 37 * This class provides the APIs to control the Bluetooth PBAP Client Profile. 38 * 39 * @hide 40 */ 41 @SystemApi 42 public final class BluetoothPbapClient implements BluetoothProfile, AutoCloseable { 43 44 private static final String TAG = "BluetoothPbapClient"; 45 private static final boolean DBG = false; 46 private static final boolean VDBG = false; 47 48 private final CloseGuard mCloseGuard; 49 50 /** 51 * Intent used to broadcast the change in connection state of the PBAP Client profile. 52 * 53 * <p>This intent will have 3 extras: 54 * 55 * <ul> 56 * <li>{@link #EXTRA_STATE} - The current state of the profile. 57 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. 58 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 59 * </ul> 60 * 61 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link 62 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link 63 * #STATE_DISCONNECTING}. 64 * 65 * @hide 66 */ 67 @SystemApi 68 @SuppressLint("ActionValue") 69 @RequiresBluetoothConnectPermission 70 @RequiresPermission( 71 allOf = { 72 android.Manifest.permission.BLUETOOTH_CONNECT, 73 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 74 }) 75 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 76 public static final String ACTION_CONNECTION_STATE_CHANGED = 77 "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED"; 78 79 /** There was an error trying to obtain the state */ 80 /** @hide */ 81 public static final int STATE_ERROR = -1; 82 83 /** @hide */ 84 public static final int RESULT_FAILURE = 0; 85 86 /** @hide */ 87 public static final int RESULT_SUCCESS = 1; 88 89 /** Connection canceled before completion. */ 90 /** @hide */ 91 public static final int RESULT_CANCELED = 2; 92 93 private final BluetoothAdapter mAdapter; 94 private final AttributionSource mAttributionSource; 95 96 private IBluetoothPbapClient mService; 97 98 /** 99 * Create a BluetoothPbapClient proxy object. 100 * 101 * @hide 102 */ BluetoothPbapClient(Context context, BluetoothAdapter adapter)103 BluetoothPbapClient(Context context, BluetoothAdapter adapter) { 104 if (DBG) { 105 Log.d(TAG, "Create BluetoothPbapClient proxy object"); 106 } 107 mAdapter = adapter; 108 mAttributionSource = adapter.getAttributionSource(); 109 mService = null; 110 mCloseGuard = new CloseGuard(); 111 mCloseGuard.open("close"); 112 } 113 114 /** @hide */ 115 @SuppressWarnings("Finalize") // TODO(b/314811467) finalize()116 protected void finalize() { 117 if (mCloseGuard != null) { 118 mCloseGuard.warnIfOpen(); 119 } 120 close(); 121 } 122 123 /** 124 * Close the connection to the backing service. Other public functions of BluetoothPbapClient 125 * will return default error results once close() has been called. Multiple invocations of 126 * close() are ok. 127 * 128 * @hide 129 */ 130 @Override close()131 public synchronized void close() { 132 mAdapter.closeProfileProxy(this); 133 if (mCloseGuard != null) { 134 mCloseGuard.close(); 135 } 136 } 137 138 /** @hide */ 139 @Override onServiceConnected(IBinder service)140 public void onServiceConnected(IBinder service) { 141 mService = IBluetoothPbapClient.Stub.asInterface(service); 142 } 143 144 /** @hide */ 145 @Override onServiceDisconnected()146 public void onServiceDisconnected() { 147 mService = null; 148 } 149 getService()150 private IBluetoothPbapClient getService() { 151 return mService; 152 } 153 154 /** @hide */ 155 @Override getAdapter()156 public BluetoothAdapter getAdapter() { 157 return mAdapter; 158 } 159 160 /** 161 * Initiate connection. Upon successful connection to remote PBAP server the Client will attempt 162 * to automatically download the users phonebook and call log. 163 * 164 * @param device a remote device we want connect to 165 * @return <code>true</code> if command has been issued successfully; <code>false</code> 166 * otherwise; 167 * @hide 168 */ 169 @RequiresBluetoothConnectPermission 170 @RequiresPermission( 171 allOf = { 172 android.Manifest.permission.BLUETOOTH_CONNECT, 173 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 174 }) connect(BluetoothDevice device)175 public boolean connect(BluetoothDevice device) { 176 if (DBG) { 177 log("connect(" + device + ") for PBAP Client."); 178 } 179 final IBluetoothPbapClient service = getService(); 180 if (service == null) { 181 Log.w(TAG, "Proxy not attached to service"); 182 if (DBG) log(Log.getStackTraceString(new Throwable())); 183 } else if (isEnabled() && isValidDevice(device)) { 184 try { 185 return service.connect(device, mAttributionSource); 186 } catch (RemoteException e) { 187 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 188 } 189 } 190 return false; 191 } 192 193 /** 194 * Initiate disconnect. 195 * 196 * @param device Remote Bluetooth Device 197 * @return false on error, true otherwise 198 * @hide 199 */ 200 @RequiresBluetoothConnectPermission 201 @RequiresPermission( 202 allOf = { 203 android.Manifest.permission.BLUETOOTH_CONNECT, 204 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 205 }) disconnect(BluetoothDevice device)206 public boolean disconnect(BluetoothDevice device) { 207 if (DBG) { 208 log("disconnect(" + device + ")" + new Exception()); 209 } 210 final IBluetoothPbapClient service = getService(); 211 if (service == null) { 212 Log.w(TAG, "Proxy not attached to service"); 213 if (DBG) log(Log.getStackTraceString(new Throwable())); 214 } else if (isEnabled() && isValidDevice(device)) { 215 try { 216 return service.disconnect(device, mAttributionSource); 217 } catch (RemoteException e) { 218 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 219 } 220 } 221 return false; 222 } 223 224 /** 225 * {@inheritDoc} 226 * 227 * @hide 228 */ 229 @SystemApi 230 @Override 231 @RequiresBluetoothConnectPermission 232 @RequiresPermission( 233 allOf = { 234 android.Manifest.permission.BLUETOOTH_CONNECT, 235 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 236 }) getConnectedDevices()237 public @NonNull List<BluetoothDevice> getConnectedDevices() { 238 if (DBG) { 239 log("getConnectedDevices()"); 240 } 241 final IBluetoothPbapClient service = getService(); 242 if (service == null) { 243 Log.w(TAG, "Proxy not attached to service"); 244 if (DBG) log(Log.getStackTraceString(new Throwable())); 245 } else if (isEnabled()) { 246 try { 247 return Attributable.setAttributionSource( 248 service.getConnectedDevices(mAttributionSource), mAttributionSource); 249 } catch (RemoteException e) { 250 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 251 throw e.rethrowAsRuntimeException(); 252 } 253 } 254 return Collections.emptyList(); 255 } 256 257 /** 258 * {@inheritDoc} 259 * 260 * @hide 261 */ 262 @SystemApi 263 @Override 264 @RequiresBluetoothConnectPermission 265 @RequiresPermission( 266 allOf = { 267 android.Manifest.permission.BLUETOOTH_CONNECT, 268 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 269 }) 270 @NonNull getDevicesMatchingConnectionStates(@onNull int[] states)271 public List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) { 272 if (DBG) { 273 log("getDevicesMatchingStates()"); 274 } 275 final IBluetoothPbapClient service = getService(); 276 if (service == null) { 277 Log.w(TAG, "Proxy not attached to service"); 278 if (DBG) log(Log.getStackTraceString(new Throwable())); 279 } else if (isEnabled()) { 280 try { 281 return Attributable.setAttributionSource( 282 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 283 mAttributionSource); 284 } catch (RemoteException e) { 285 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 286 throw e.rethrowAsRuntimeException(); 287 } 288 } 289 return Collections.emptyList(); 290 } 291 292 /** 293 * {@inheritDoc} 294 * 295 * @hide 296 */ 297 @SystemApi 298 @Override 299 @RequiresBluetoothConnectPermission 300 @RequiresPermission( 301 allOf = { 302 android.Manifest.permission.BLUETOOTH_CONNECT, 303 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 304 }) getConnectionState(@onNull BluetoothDevice device)305 public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { 306 if (DBG) { 307 log("getConnectionState(" + device + ")"); 308 } 309 final IBluetoothPbapClient service = getService(); 310 if (service == null) { 311 Log.w(TAG, "Proxy not attached to service"); 312 if (DBG) log(Log.getStackTraceString(new Throwable())); 313 } else if (isEnabled() && isValidDevice(device)) { 314 try { 315 return service.getConnectionState(device, mAttributionSource); 316 } catch (RemoteException e) { 317 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 318 throw e.rethrowAsRuntimeException(); 319 } 320 } 321 return BluetoothProfile.STATE_DISCONNECTED; 322 } 323 log(String msg)324 private static void log(String msg) { 325 Log.d(TAG, msg); 326 } 327 isEnabled()328 private boolean isEnabled() { 329 return mAdapter.isEnabled(); 330 } 331 isValidDevice(BluetoothDevice device)332 private static boolean isValidDevice(BluetoothDevice device) { 333 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 334 } 335 336 /** 337 * Set priority of the profile 338 * 339 * <p>The device should already be paired. Priority can be one of {@link #PRIORITY_ON} or {@link 340 * #PRIORITY_OFF}, 341 * 342 * @param device Paired bluetooth device 343 * @return true if priority is set, false on error 344 * @hide 345 */ 346 @RequiresBluetoothConnectPermission 347 @RequiresPermission( 348 allOf = { 349 android.Manifest.permission.BLUETOOTH_CONNECT, 350 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 351 }) setPriority(BluetoothDevice device, int priority)352 public boolean setPriority(BluetoothDevice device, int priority) { 353 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 354 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 355 } 356 357 /** 358 * Set connection policy of the profile 359 * 360 * <p>The device should already be paired. Connection policy can be one of {@link 361 * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link 362 * #CONNECTION_POLICY_UNKNOWN} 363 * 364 * @param device Paired bluetooth device 365 * @param connectionPolicy is the connection policy to set to for this profile 366 * @return true if connectionPolicy is set, false on error 367 * @hide 368 */ 369 @SystemApi 370 @RequiresBluetoothConnectPermission 371 @RequiresPermission( 372 allOf = { 373 android.Manifest.permission.BLUETOOTH_CONNECT, 374 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 375 }) setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)376 public boolean setConnectionPolicy( 377 @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { 378 if (DBG) { 379 log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 380 } 381 final IBluetoothPbapClient service = getService(); 382 if (service == null) { 383 Log.w(TAG, "Proxy not attached to service"); 384 if (DBG) log(Log.getStackTraceString(new Throwable())); 385 } else if (isEnabled() 386 && isValidDevice(device) 387 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 388 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { 389 try { 390 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 391 } catch (RemoteException e) { 392 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 393 throw e.rethrowAsRuntimeException(); 394 } 395 } 396 return false; 397 } 398 399 /** 400 * Get the priority of the profile. 401 * 402 * <p>The priority can be any of: {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link 403 * #PRIORITY_UNDEFINED} 404 * 405 * @param device Bluetooth device 406 * @return priority of the device 407 * @hide 408 */ 409 @RequiresBluetoothConnectPermission 410 @RequiresPermission( 411 allOf = { 412 android.Manifest.permission.BLUETOOTH_CONNECT, 413 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 414 }) getPriority(BluetoothDevice device)415 public int getPriority(BluetoothDevice device) { 416 if (VDBG) log("getPriority(" + device + ")"); 417 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); 418 } 419 420 /** 421 * Get the connection policy of the profile. 422 * 423 * <p>The connection policy can be any of: {@link #CONNECTION_POLICY_ALLOWED}, {@link 424 * #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 425 * 426 * @param device Bluetooth device 427 * @return connection policy of the device 428 * @hide 429 */ 430 @SystemApi 431 @RequiresBluetoothConnectPermission 432 @RequiresPermission( 433 allOf = { 434 android.Manifest.permission.BLUETOOTH_CONNECT, 435 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 436 }) getConnectionPolicy(@onNull BluetoothDevice device)437 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 438 if (VDBG) { 439 log("getConnectionPolicy(" + device + ")"); 440 } 441 final IBluetoothPbapClient service = getService(); 442 if (service == null) { 443 Log.w(TAG, "Proxy not attached to service"); 444 if (DBG) log(Log.getStackTraceString(new Throwable())); 445 } else if (isEnabled() && isValidDevice(device)) { 446 try { 447 return service.getConnectionPolicy(device, mAttributionSource); 448 } catch (RemoteException e) { 449 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 450 throw e.rethrowAsRuntimeException(); 451 } 452 } 453 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 454 } 455 } 456