• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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