• 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     @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