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 * @hide 269 */ 270 @UnsupportedAppUsage 271 @Override close()272 public void close() { 273 if (VDBG) log("close()"); 274 mProfileConnector.disconnect(); 275 } 276 getService()277 private IBluetoothPan getService() { 278 return mProfileConnector.getService(); 279 } 280 281 /** @hide */ finalize()282 protected void finalize() { 283 close(); 284 } 285 286 /** 287 * Initiate connection to a profile of the remote bluetooth device. 288 * 289 * <p> This API returns false in scenarios like the profile on the 290 * device is already connected or Bluetooth is not turned on. 291 * When this API returns true, it is guaranteed that 292 * connection state intent for the profile will be broadcasted with 293 * the state. Users can get the connection state of the profile 294 * from this intent. 295 * 296 * @param device Remote Bluetooth Device 297 * @return false on immediate error, true otherwise 298 * @hide 299 */ 300 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 301 @RequiresBluetoothConnectPermission 302 @RequiresPermission(allOf = { 303 android.Manifest.permission.BLUETOOTH_CONNECT, 304 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 305 }) connect(BluetoothDevice device)306 public boolean connect(BluetoothDevice device) { 307 if (DBG) log("connect(" + device + ")"); 308 final IBluetoothPan service = getService(); 309 final boolean defaultValue = false; 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 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 316 service.connect(device, mAttributionSource, recv); 317 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 318 } catch (RemoteException | TimeoutException e) { 319 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 320 } 321 } 322 return defaultValue; 323 } 324 325 /** 326 * Initiate disconnection from a profile 327 * 328 * <p> This API will return false in scenarios like the profile on the 329 * Bluetooth device is not in connected state etc. When this API returns, 330 * true, it is guaranteed that the connection state change 331 * intent will be broadcasted with the state. Users can get the 332 * disconnection state of the profile from this intent. 333 * 334 * <p> If the disconnection is initiated by a remote device, the state 335 * will transition from {@link #STATE_CONNECTED} to 336 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 337 * host (local) device the state will transition from 338 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 339 * state {@link #STATE_DISCONNECTED}. The transition to 340 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 341 * two scenarios. 342 * 343 * @param device Remote Bluetooth Device 344 * @return false on immediate error, true otherwise 345 * @hide 346 */ 347 @UnsupportedAppUsage 348 @RequiresBluetoothConnectPermission 349 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnect(BluetoothDevice device)350 public boolean disconnect(BluetoothDevice device) { 351 if (DBG) log("disconnect(" + device + ")"); 352 final IBluetoothPan service = getService(); 353 final boolean defaultValue = false; 354 if (service == null) { 355 Log.w(TAG, "Proxy not attached to service"); 356 if (DBG) log(Log.getStackTraceString(new Throwable())); 357 } else if (isEnabled() && isValidDevice(device)) { 358 try { 359 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 360 service.disconnect(device, mAttributionSource, recv); 361 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 362 } catch (RemoteException | TimeoutException e) { 363 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 364 } 365 } 366 return defaultValue; 367 } 368 369 /** 370 * Set connection policy of the profile 371 * 372 * <p> The device should already be paired. 373 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 374 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 375 * 376 * @param device Paired bluetooth device 377 * @param connectionPolicy is the connection policy to set to for this profile 378 * @return true if connectionPolicy is set, false on error 379 * @hide 380 */ 381 @SystemApi 382 @RequiresBluetoothConnectPermission 383 @RequiresPermission(allOf = { 384 android.Manifest.permission.BLUETOOTH_CONNECT, 385 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 386 }) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)387 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 388 @ConnectionPolicy int connectionPolicy) { 389 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 390 final IBluetoothPan service = getService(); 391 final boolean defaultValue = false; 392 if (service == null) { 393 Log.w(TAG, "Proxy not attached to service"); 394 if (DBG) log(Log.getStackTraceString(new Throwable())); 395 } else if (isEnabled() && isValidDevice(device) 396 && (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 397 || connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) { 398 try { 399 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 400 service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv); 401 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 402 } catch (RemoteException | TimeoutException e) { 403 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 404 } 405 } 406 return defaultValue; 407 } 408 409 /** 410 * {@inheritDoc} 411 * @hide 412 */ 413 @SystemApi 414 @Override 415 @RequiresBluetoothConnectPermission 416 @RequiresPermission(allOf = { 417 android.Manifest.permission.BLUETOOTH_CONNECT, 418 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 419 }) getConnectedDevices()420 public @NonNull List<BluetoothDevice> getConnectedDevices() { 421 if (VDBG) log("getConnectedDevices()"); 422 final IBluetoothPan service = getService(); 423 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 424 if (service == null) { 425 Log.w(TAG, "Proxy not attached to service"); 426 if (DBG) log(Log.getStackTraceString(new Throwable())); 427 } else if (isEnabled()) { 428 try { 429 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 430 SynchronousResultReceiver.get(); 431 service.getConnectedDevices(mAttributionSource, recv); 432 return Attributable.setAttributionSource( 433 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 434 mAttributionSource); 435 } catch (RemoteException | TimeoutException e) { 436 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 437 } 438 } 439 return defaultValue; 440 } 441 442 /** 443 * {@inheritDoc} 444 * @hide 445 */ 446 @Override 447 @RequiresLegacyBluetoothPermission 448 @RequiresBluetoothConnectPermission 449 @RequiresPermission(allOf = { 450 android.Manifest.permission.BLUETOOTH_CONNECT, 451 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 452 }) getDevicesMatchingConnectionStates(int[] states)453 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 454 if (VDBG) log("getDevicesMatchingStates()"); 455 final IBluetoothPan service = getService(); 456 final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>(); 457 if (service == null) { 458 Log.w(TAG, "Proxy not attached to service"); 459 if (DBG) log(Log.getStackTraceString(new Throwable())); 460 } else if (isEnabled()) { 461 try { 462 final SynchronousResultReceiver<List<BluetoothDevice>> recv = 463 SynchronousResultReceiver.get(); 464 service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv); 465 return Attributable.setAttributionSource( 466 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue), 467 mAttributionSource); 468 } catch (RemoteException | TimeoutException e) { 469 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 470 } 471 } 472 return defaultValue; 473 } 474 475 /** 476 * {@inheritDoc} 477 * @hide 478 */ 479 @SystemApi 480 @Override 481 @RequiresBluetoothConnectPermission 482 @RequiresPermission(allOf = { 483 android.Manifest.permission.BLUETOOTH_CONNECT, 484 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 485 }) getConnectionState(@onNull BluetoothDevice device)486 public int getConnectionState(@NonNull BluetoothDevice device) { 487 if (VDBG) log("getState(" + device + ")"); 488 final IBluetoothPan service = getService(); 489 final int defaultValue = BluetoothProfile.STATE_DISCONNECTED; 490 if (service == null) { 491 Log.w(TAG, "Proxy not attached to service"); 492 if (DBG) log(Log.getStackTraceString(new Throwable())); 493 } else if (isEnabled() && isValidDevice(device)) { 494 try { 495 final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get(); 496 service.getConnectionState(device, mAttributionSource, recv); 497 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 498 } catch (RemoteException | TimeoutException e) { 499 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 500 } 501 } 502 return defaultValue; 503 } 504 505 /** 506 * Turns on/off bluetooth tethering. 507 * 508 * @param value is whether to enable or disable bluetooth tethering 509 * 510 * @deprecated Use {@link #requestTetheredInterface} with 511 * {@link TetheredInterfaceCallback} instead. 512 * @hide 513 */ 514 @SystemApi 515 @RequiresBluetoothConnectPermission 516 @RequiresPermission(allOf = { 517 android.Manifest.permission.BLUETOOTH_CONNECT, 518 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 519 android.Manifest.permission.TETHER_PRIVILEGED, 520 }) 521 @Deprecated setBluetoothTethering(boolean value)522 public void setBluetoothTethering(boolean value) { 523 String pkgName = mContext.getOpPackageName(); 524 if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); 525 final IBluetoothPan service = getService(); 526 if (service == null) { 527 Log.w(TAG, "Proxy not attached to service"); 528 if (DBG) log(Log.getStackTraceString(new Throwable())); 529 } else if (isEnabled()) { 530 try { 531 final SynchronousResultReceiver recv = SynchronousResultReceiver.get(); 532 service.setBluetoothTethering(null, 0, value, mAttributionSource, recv); 533 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); 534 } catch (RemoteException | TimeoutException e) { 535 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 536 } 537 } 538 } 539 540 /** 541 * Turns on Bluetooth tethering. 542 * 543 * <p>When one or more devices are connected, the PAN service will trigger 544 * {@link TetheredInterfaceCallback#onAvailable} to inform the caller that 545 * it is ready to tether. On the contrary, when all devices have been disconnected, 546 * the PAN service will trigger {@link TetheredInterfaceCallback#onUnavailable}. 547 * <p>To turn off Bluetooth tethering, the caller must use 548 * {@link TetheredInterfaceRequest#release} method. 549 * 550 * @param executor thread to execute callback methods 551 * @param callback is the tethering callback to indicate PAN service is ready 552 * or not to tether to one or more devices 553 * 554 * @return new instance of {@link TetheredInterfaceRequest} which can be 555 * used to turn off Bluetooth tethering or {@code null} if service 556 * is not enabled 557 * @hide 558 */ 559 @SystemApi(client = MODULE_LIBRARIES) 560 @RequiresBluetoothConnectPermission 561 @RequiresPermission(allOf = { 562 android.Manifest.permission.BLUETOOTH_CONNECT, 563 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 564 android.Manifest.permission.TETHER_PRIVILEGED, 565 }) 566 @Nullable requestTetheredInterface( @onNull final Executor executor, @NonNull final TetheredInterfaceCallback callback)567 public TetheredInterfaceRequest requestTetheredInterface( 568 @NonNull final Executor executor, 569 @NonNull final TetheredInterfaceCallback callback) { 570 Objects.requireNonNull(callback, "Callback must be non-null"); 571 Objects.requireNonNull(executor, "Executor must be non-null"); 572 final IBluetoothPan service = getService(); 573 if (service == null) { 574 Log.w(TAG, "Proxy not attached to service"); 575 if (DBG) log(Log.getStackTraceString(new Throwable())); 576 } else if (isEnabled()) { 577 final IBluetoothPanCallback panCallback = new IBluetoothPanCallback.Stub() { 578 @Override 579 public void onAvailable(String iface) { 580 executor.execute(() -> callback.onAvailable(iface)); 581 } 582 583 @Override 584 public void onUnavailable() { 585 executor.execute(() -> callback.onUnavailable()); 586 } 587 }; 588 try { 589 final SynchronousResultReceiver recv = SynchronousResultReceiver.get(); 590 service.setBluetoothTethering(panCallback, callback.hashCode(), true, 591 mAttributionSource, recv); 592 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); 593 return new BluetoothTetheredInterfaceRequest(service, panCallback, 594 callback.hashCode()); 595 } catch (RemoteException | TimeoutException e) { 596 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 597 } 598 } 599 return null; 600 } 601 602 /** 603 * Determines whether tethering is enabled 604 * 605 * @return true if tethering is on, false if not or some error occurred 606 * @hide 607 */ 608 @SystemApi 609 @RequiresBluetoothConnectPermission 610 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isTetheringOn()611 public boolean isTetheringOn() { 612 if (VDBG) log("isTetheringOn()"); 613 final IBluetoothPan service = getService(); 614 final boolean defaultValue = false; 615 if (service == null) { 616 Log.w(TAG, "Proxy not attached to service"); 617 if (DBG) log(Log.getStackTraceString(new Throwable())); 618 } else if (isEnabled()) { 619 try { 620 final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get(); 621 service.isTetheringOn(mAttributionSource, recv); 622 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue); 623 } catch (RemoteException | TimeoutException e) { 624 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 625 } 626 } 627 return defaultValue; 628 } 629 630 @UnsupportedAppUsage isEnabled()631 private boolean isEnabled() { 632 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 633 } 634 635 @UnsupportedAppUsage isValidDevice(BluetoothDevice device)636 private static boolean isValidDevice(BluetoothDevice device) { 637 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 638 } 639 640 @UnsupportedAppUsage log(String msg)641 private static void log(String msg) { 642 Log.d(TAG, msg); 643 } 644 } 645