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