1 /* 2 * Copyright (C) 2008 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 static android.annotation.SystemApi.Client.MODULE_LIBRARIES; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.RequiresPermission; 25 import android.annotation.SdkConstant; 26 import android.annotation.SdkConstant.SdkConstantType; 27 import android.annotation.SuppressLint; 28 import android.annotation.SystemApi; 29 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 30 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 31 import android.compat.annotation.UnsupportedAppUsage; 32 import android.content.AttributionSource; 33 import android.content.Context; 34 import android.net.TetheringManager.TetheredInterfaceCallback; 35 import android.net.TetheringManager.TetheredInterfaceRequest; 36 import android.os.Build; 37 import android.os.IBinder; 38 import android.os.RemoteException; 39 import android.util.Log; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.Collections; 44 import java.util.List; 45 import java.util.Objects; 46 import java.util.concurrent.Executor; 47 48 /** 49 * This class provides the APIs to control the Bluetooth Pan Profile. 50 * 51 * <p>BluetoothPan is a proxy object for controlling the Bluetooth Service via IPC. Use {@link 52 * BluetoothAdapter#getProfileProxy} to get the BluetoothPan proxy object. 53 * 54 * <p>Each method is protected with its appropriate permission. 55 * 56 * @hide 57 */ 58 @SystemApi 59 public final class BluetoothPan implements BluetoothProfile { 60 private static final String TAG = "BluetoothPan"; 61 private static final boolean DBG = true; 62 private static final boolean VDBG = false; 63 64 /** 65 * Intent used to broadcast the change in connection state of the Pan profile. 66 * 67 * <p>This intent will have 4 extras: 68 * 69 * <ul> 70 * <li>{@link #EXTRA_STATE} - The current state of the profile. 71 * <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. 72 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 73 * <li>{@link #EXTRA_LOCAL_ROLE} - Which local role the remote device is bound to. 74 * </ul> 75 * 76 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link 77 * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link 78 * #STATE_DISCONNECTING}. 79 * 80 * <p>{@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or {@link 81 * #LOCAL_PANU_ROLE} 82 */ 83 @SuppressLint("ActionValue") 84 @RequiresLegacyBluetoothPermission 85 @RequiresBluetoothConnectPermission 86 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 87 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 88 public static final String ACTION_CONNECTION_STATE_CHANGED = 89 "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; 90 91 /** 92 * Extra for {@link #ACTION_CONNECTION_STATE_CHANGED} intent The local role of the PAN profile 93 * that the remote device is bound to. It can be one of {@link #LOCAL_NAP_ROLE} or {@link 94 * #LOCAL_PANU_ROLE}. 95 */ 96 @SuppressLint("ActionValue") 97 public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; 98 99 /** 100 * Intent used to broadcast the change in tethering state of the Pan Profile 101 * 102 * <p>This intent will have 1 extra: 103 * 104 * <ul> 105 * <li>{@link #EXTRA_TETHERING_STATE} - The current state of Bluetooth tethering. 106 * </ul> 107 * 108 * <p>{@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or {@link 109 * #TETHERING_STATE_ON} 110 */ 111 @RequiresLegacyBluetoothPermission 112 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 113 public static final String ACTION_TETHERING_STATE_CHANGED = 114 "android.bluetooth.action.TETHERING_STATE_CHANGED"; 115 116 /** 117 * Extra for {@link #ACTION_TETHERING_STATE_CHANGED} intent The tethering state of the PAN 118 * profile. It can be one of {@link #TETHERING_STATE_OFF} or {@link #TETHERING_STATE_ON}. 119 */ 120 public static final String EXTRA_TETHERING_STATE = "android.bluetooth.extra.TETHERING_STATE"; 121 122 /** @hide */ 123 @IntDef({PAN_ROLE_NONE, LOCAL_NAP_ROLE, LOCAL_PANU_ROLE}) 124 @Retention(RetentionPolicy.SOURCE) 125 public @interface LocalPanRole {} 126 127 public static final int PAN_ROLE_NONE = 0; 128 129 /** The local device is acting as a Network Access Point. */ 130 public static final int LOCAL_NAP_ROLE = 1; 131 132 /** The local device is acting as a PAN User. */ 133 public static final int LOCAL_PANU_ROLE = 2; 134 135 /** @hide */ 136 @IntDef({PAN_ROLE_NONE, REMOTE_NAP_ROLE, REMOTE_PANU_ROLE}) 137 @Retention(RetentionPolicy.SOURCE) 138 public @interface RemotePanRole {} 139 140 public static final int REMOTE_NAP_ROLE = 1; 141 142 public static final int REMOTE_PANU_ROLE = 2; 143 144 /** 145 * @hide * 146 */ 147 @IntDef({TETHERING_STATE_OFF, TETHERING_STATE_ON}) 148 @Retention(RetentionPolicy.SOURCE) 149 public @interface TetheringState {} 150 151 public static final int TETHERING_STATE_OFF = 1; 152 153 public static final int TETHERING_STATE_ON = 2; 154 155 /** 156 * Return codes for the connect and disconnect Bluez / Dbus calls. 157 * 158 * @hide 159 */ 160 public static final int PAN_DISCONNECT_FAILED_NOT_CONNECTED = 1000; 161 162 /** @hide */ 163 public static final int PAN_CONNECT_FAILED_ALREADY_CONNECTED = 1001; 164 165 /** @hide */ 166 public static final int PAN_CONNECT_FAILED_ATTEMPT_FAILED = 1002; 167 168 /** @hide */ 169 public static final int PAN_OPERATION_GENERIC_FAILURE = 1003; 170 171 /** @hide */ 172 public static final int PAN_OPERATION_SUCCESS = 1004; 173 174 /** 175 * Request class used by Tethering to notify that the interface is closed. 176 * 177 * @see #requestTetheredInterface 178 * @hide 179 */ 180 public class BluetoothTetheredInterfaceRequest implements TetheredInterfaceRequest { 181 private IBluetoothPan mService; 182 private final IBluetoothPanCallback mPanCallback; 183 private final int mId; 184 BluetoothTetheredInterfaceRequest( @onNull IBluetoothPan service, @NonNull IBluetoothPanCallback panCallback, int id)185 private BluetoothTetheredInterfaceRequest( 186 @NonNull IBluetoothPan service, 187 @NonNull IBluetoothPanCallback panCallback, 188 int id) { 189 this.mService = service; 190 this.mPanCallback = panCallback; 191 this.mId = id; 192 } 193 194 /** Called when the Tethering interface has been released. */ 195 @RequiresPermission( 196 allOf = { 197 android.Manifest.permission.BLUETOOTH_CONNECT, 198 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 199 android.Manifest.permission.TETHER_PRIVILEGED, 200 }) 201 @Override release()202 public void release() { 203 if (mService == null) { 204 throw new IllegalStateException( 205 "The tethered interface has already been released."); 206 } 207 try { 208 mService.setBluetoothTethering(mPanCallback, mId, false, mAttributionSource); 209 } catch (RemoteException e) { 210 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 211 } finally { 212 mService = null; 213 } 214 } 215 } 216 217 private final Context mContext; 218 219 private final BluetoothAdapter mAdapter; 220 private final AttributionSource mAttributionSource; 221 222 private IBluetoothPan mService; 223 224 /** 225 * Create a BluetoothPan proxy object for interacting with the local Bluetooth Service which 226 * handles the Pan profile 227 * 228 * @hide 229 */ 230 @UnsupportedAppUsage BluetoothPan(Context context, BluetoothAdapter adapter)231 /* package */ BluetoothPan(Context context, BluetoothAdapter adapter) { 232 mAdapter = adapter; 233 mAttributionSource = adapter.getAttributionSource(); 234 mContext = context; 235 mService = null; 236 } 237 238 /** 239 * Closes the connection to the service and unregisters callbacks 240 * 241 * @hide 242 */ 243 @UnsupportedAppUsage close()244 public void close() { 245 if (VDBG) log("close()"); 246 mAdapter.closeProfileProxy(this); 247 } 248 249 /** @hide */ 250 @Override onServiceConnected(IBinder service)251 public void onServiceConnected(IBinder service) { 252 mService = IBluetoothPan.Stub.asInterface(service); 253 } 254 255 /** @hide */ 256 @Override onServiceDisconnected()257 public void onServiceDisconnected() { 258 mService = null; 259 } 260 getService()261 private IBluetoothPan getService() { 262 return mService; 263 } 264 265 /** @hide */ 266 @Override getAdapter()267 public BluetoothAdapter getAdapter() { 268 return mAdapter; 269 } 270 271 /** @hide */ 272 @SuppressWarnings("Finalize") // TODO(b/314811467) finalize()273 protected void finalize() { 274 close(); 275 } 276 277 /** 278 * Initiate connection to a profile of the remote bluetooth device. 279 * 280 * <p>This API returns false in scenarios like the profile on the device is already connected or 281 * Bluetooth is not turned on. When this API returns true, it is guaranteed that connection 282 * state intent for the profile will be broadcasted with the state. Users can get the connection 283 * state of the profile from this intent. 284 * 285 * @param device Remote Bluetooth Device 286 * @return false on immediate error, true otherwise 287 * @hide 288 */ 289 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 290 @RequiresBluetoothConnectPermission 291 @RequiresPermission( 292 allOf = { 293 android.Manifest.permission.BLUETOOTH_CONNECT, 294 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 295 }) connect(BluetoothDevice device)296 public boolean connect(BluetoothDevice device) { 297 if (DBG) log("connect(" + device + ")"); 298 final IBluetoothPan service = getService(); 299 if (service == null) { 300 Log.w(TAG, "Proxy not attached to service"); 301 if (DBG) log(Log.getStackTraceString(new Throwable())); 302 } else if (isEnabled() && isValidDevice(device)) { 303 try { 304 return service.connect(device, mAttributionSource); 305 } catch (RemoteException e) { 306 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 307 } 308 } 309 return false; 310 } 311 312 /** 313 * Initiate disconnection from a profile 314 * 315 * <p>This API will return false in scenarios like the profile on the Bluetooth device is not in 316 * connected state etc. When this API returns, true, it is guaranteed that the connection state 317 * change intent will be broadcasted with the state. Users can get the disconnection state of 318 * the profile from this intent. 319 * 320 * <p>If the disconnection is initiated by a remote device, the state will transition from 321 * {@link #STATE_CONNECTED} to {@link #STATE_DISCONNECTED}. If the disconnect is initiated by 322 * the host (local) device the state will transition from {@link #STATE_CONNECTED} to state 323 * {@link #STATE_DISCONNECTING} to state {@link #STATE_DISCONNECTED}. The transition to {@link 324 * #STATE_DISCONNECTING} can be used to distinguish between the two scenarios. 325 * 326 * @param device Remote Bluetooth Device 327 * @return false on immediate error, true otherwise 328 * @hide 329 */ 330 @UnsupportedAppUsage 331 @RequiresBluetoothConnectPermission 332 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnect(BluetoothDevice device)333 public boolean disconnect(BluetoothDevice device) { 334 if (DBG) log("disconnect(" + device + ")"); 335 final IBluetoothPan service = getService(); 336 if (service == null) { 337 Log.w(TAG, "Proxy not attached to service"); 338 if (DBG) log(Log.getStackTraceString(new Throwable())); 339 } else if (isEnabled() && isValidDevice(device)) { 340 try { 341 return service.disconnect(device, mAttributionSource); 342 } catch (RemoteException e) { 343 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 344 } 345 } 346 return false; 347 } 348 349 /** 350 * Set connection policy of the profile 351 * 352 * <p>The device should already be paired. Connection policy can be one of {@link 353 * #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, {@link 354 * #CONNECTION_POLICY_UNKNOWN} 355 * 356 * @param device Paired bluetooth device 357 * @param connectionPolicy is the connection policy to set to for this profile 358 * @return true if connectionPolicy is set, false on error 359 * @hide 360 */ 361 @SystemApi 362 @RequiresBluetoothConnectPermission 363 @RequiresPermission( 364 allOf = { 365 android.Manifest.permission.BLUETOOTH_CONNECT, 366 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 367 }) setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)368 public boolean setConnectionPolicy( 369 @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { 370 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 371 final IBluetoothPan service = getService(); 372 if (service == null) { 373 Log.w(TAG, "Proxy not attached to service"); 374 if (DBG) log(Log.getStackTraceString(new Throwable())); 375 } else if (isEnabled() 376 && isValidDevice(device) 377 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 378 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { 379 try { 380 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 381 } catch (RemoteException e) { 382 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 383 } 384 } 385 return false; 386 } 387 388 /** 389 * {@inheritDoc} 390 * 391 * @hide 392 */ 393 @SystemApi 394 @Override 395 @RequiresBluetoothConnectPermission 396 @RequiresPermission( 397 allOf = { 398 android.Manifest.permission.BLUETOOTH_CONNECT, 399 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 400 }) getConnectedDevices()401 public @NonNull List<BluetoothDevice> getConnectedDevices() { 402 if (VDBG) log("getConnectedDevices()"); 403 final IBluetoothPan service = getService(); 404 if (service == null) { 405 Log.w(TAG, "Proxy not attached to service"); 406 if (DBG) log(Log.getStackTraceString(new Throwable())); 407 } else if (isEnabled()) { 408 try { 409 return Attributable.setAttributionSource( 410 service.getConnectedDevices(mAttributionSource), mAttributionSource); 411 } catch (RemoteException e) { 412 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 413 } 414 } 415 return Collections.emptyList(); 416 } 417 418 /** 419 * {@inheritDoc} 420 * 421 * @hide 422 */ 423 @Override 424 @RequiresLegacyBluetoothPermission 425 @RequiresBluetoothConnectPermission 426 @RequiresPermission( 427 allOf = { 428 android.Manifest.permission.BLUETOOTH_CONNECT, 429 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 430 }) getDevicesMatchingConnectionStates(int[] states)431 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 432 if (VDBG) log("getDevicesMatchingStates()"); 433 final IBluetoothPan service = getService(); 434 if (service == null) { 435 Log.w(TAG, "Proxy not attached to service"); 436 if (DBG) log(Log.getStackTraceString(new Throwable())); 437 } else if (isEnabled()) { 438 try { 439 return Attributable.setAttributionSource( 440 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 441 mAttributionSource); 442 } catch (RemoteException e) { 443 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 444 } 445 } 446 return Collections.emptyList(); 447 } 448 449 /** 450 * {@inheritDoc} 451 * 452 * @hide 453 */ 454 @SystemApi 455 @Override 456 @RequiresBluetoothConnectPermission 457 @RequiresPermission( 458 allOf = { 459 android.Manifest.permission.BLUETOOTH_CONNECT, 460 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 461 }) getConnectionState(@onNull BluetoothDevice device)462 public int getConnectionState(@NonNull BluetoothDevice device) { 463 if (VDBG) log("getState(" + device + ")"); 464 final IBluetoothPan service = getService(); 465 if (service == null) { 466 Log.w(TAG, "Proxy not attached to service"); 467 if (DBG) log(Log.getStackTraceString(new Throwable())); 468 } else if (isEnabled() && isValidDevice(device)) { 469 try { 470 return service.getConnectionState(device, mAttributionSource); 471 } catch (RemoteException e) { 472 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 473 } 474 } 475 return BluetoothProfile.STATE_DISCONNECTED; 476 } 477 478 /** 479 * Turns on/off bluetooth tethering. 480 * 481 * @param value is whether to enable or disable bluetooth tethering 482 * @deprecated Use {@link #requestTetheredInterface} with {@link TetheredInterfaceCallback} 483 * instead. 484 * @hide 485 */ 486 @SystemApi 487 @RequiresBluetoothConnectPermission 488 @RequiresPermission( 489 allOf = { 490 android.Manifest.permission.BLUETOOTH_CONNECT, 491 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 492 android.Manifest.permission.TETHER_PRIVILEGED, 493 }) 494 @Deprecated setBluetoothTethering(boolean value)495 public void setBluetoothTethering(boolean value) { 496 String pkgName = mContext.getOpPackageName(); 497 if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); 498 final IBluetoothPan service = getService(); 499 if (service == null) { 500 Log.w(TAG, "Proxy not attached to service"); 501 if (DBG) log(Log.getStackTraceString(new Throwable())); 502 } else if (isEnabled()) { 503 try { 504 service.setBluetoothTethering(null, 0, value, mAttributionSource); 505 } catch (RemoteException e) { 506 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 507 } 508 } 509 } 510 511 /** 512 * Turns on Bluetooth tethering. 513 * 514 * <p>When one or more devices are connected, the PAN service will trigger {@link 515 * TetheredInterfaceCallback#onAvailable} to inform the caller that it is ready to tether. On 516 * the contrary, when all devices have been disconnected, the PAN service will trigger {@link 517 * TetheredInterfaceCallback#onUnavailable}. 518 * 519 * <p>To turn off Bluetooth tethering, the caller must use {@link 520 * TetheredInterfaceRequest#release} method. 521 * 522 * @param executor thread to execute callback methods 523 * @param callback is the tethering callback to indicate PAN service is ready or not to tether 524 * to one or more devices 525 * @return new instance of {@link TetheredInterfaceRequest} which can be used to turn off 526 * Bluetooth tethering or {@code null} if service is not enabled 527 * @hide 528 */ 529 @SystemApi(client = MODULE_LIBRARIES) 530 @RequiresBluetoothConnectPermission 531 @RequiresPermission( 532 allOf = { 533 android.Manifest.permission.BLUETOOTH_CONNECT, 534 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 535 android.Manifest.permission.TETHER_PRIVILEGED, 536 }) 537 @Nullable requestTetheredInterface( @onNull final Executor executor, @NonNull final TetheredInterfaceCallback callback)538 public TetheredInterfaceRequest requestTetheredInterface( 539 @NonNull final Executor executor, @NonNull final TetheredInterfaceCallback callback) { 540 Objects.requireNonNull(callback, "Callback must be non-null"); 541 Objects.requireNonNull(executor, "Executor must be non-null"); 542 final IBluetoothPan service = getService(); 543 if (service == null) { 544 Log.w(TAG, "Proxy not attached to service"); 545 if (DBG) log(Log.getStackTraceString(new Throwable())); 546 } else if (isEnabled()) { 547 final IBluetoothPanCallback panCallback = 548 new IBluetoothPanCallback.Stub() { 549 @Override 550 public void onAvailable(String iface) { 551 executor.execute(() -> callback.onAvailable(iface)); 552 } 553 554 @Override 555 public void onUnavailable() { 556 executor.execute(() -> callback.onUnavailable()); 557 } 558 }; 559 try { 560 service.setBluetoothTethering( 561 panCallback, callback.hashCode(), true, mAttributionSource); 562 return new BluetoothTetheredInterfaceRequest( 563 service, panCallback, callback.hashCode()); 564 } catch (RemoteException e) { 565 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 566 } 567 } 568 return null; 569 } 570 571 /** 572 * Determines whether tethering is enabled 573 * 574 * @return true if tethering is on, false if not or some error occurred 575 * @hide 576 */ 577 @SystemApi 578 @RequiresBluetoothConnectPermission 579 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isTetheringOn()580 public boolean isTetheringOn() { 581 if (VDBG) log("isTetheringOn()"); 582 final IBluetoothPan service = getService(); 583 if (service == null) { 584 Log.w(TAG, "Proxy not attached to service"); 585 if (DBG) log(Log.getStackTraceString(new Throwable())); 586 } else if (isEnabled()) { 587 try { 588 return service.isTetheringOn(mAttributionSource); 589 } catch (RemoteException e) { 590 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 591 } 592 } 593 return false; 594 } 595 596 @UnsupportedAppUsage isEnabled()597 private boolean isEnabled() { 598 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 599 } 600 601 @UnsupportedAppUsage isValidDevice(BluetoothDevice device)602 private static boolean isValidDevice(BluetoothDevice device) { 603 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 604 } 605 606 @UnsupportedAppUsage log(String msg)607 private static void log(String msg) { 608 Log.d(TAG, msg); 609 } 610 } 611