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