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 android.Manifest;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SdkConstant;
24 import android.annotation.SdkConstant.SdkConstantType;
25 import android.annotation.SuppressLint;
26 import android.annotation.SystemApi;
27 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
28 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
29 import android.compat.annotation.UnsupportedAppUsage;
30 import android.content.Attributable;
31 import android.content.AttributionSource;
32 import android.content.Context;
33 import android.os.Binder;
34 import android.os.Build;
35 import android.os.IBinder;
36 import android.os.RemoteException;
37 import android.util.Log;
38 
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.ArrayList;
42 import java.util.List;
43 
44 /**
45  * This class provides the APIs to control the Bluetooth Pan
46  * Profile.
47  *
48  * <p>BluetoothPan is a proxy object for controlling the Bluetooth
49  * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
50  * the BluetoothPan proxy object.
51  *
52  * <p>Each method is protected with its appropriate permission.
53  *
54  * @hide
55  */
56 @SystemApi
57 public final class BluetoothPan implements BluetoothProfile {
58     private static final String TAG = "BluetoothPan";
59     private static final boolean DBG = true;
60     private static final boolean VDBG = false;
61 
62     /**
63      * Intent used to broadcast the change in connection state of the Pan
64      * profile.
65      *
66      * <p>This intent will have 4 extras:
67      * <ul>
68      * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
69      * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
70      * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
71      * <li> {@link #EXTRA_LOCAL_ROLE} - Which local role the remote device is
72      * bound to. </li>
73      * </ul>
74      *
75      * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
76      * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
77      * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
78      *
79      * <p> {@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or
80      * {@link #LOCAL_PANU_ROLE}
81      */
82     @SuppressLint("ActionValue")
83     @RequiresLegacyBluetoothPermission
84     @RequiresBluetoothConnectPermission
85     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
86     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
87     public static final String ACTION_CONNECTION_STATE_CHANGED =
88             "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
89 
90     /**
91      * Extra for {@link #ACTION_CONNECTION_STATE_CHANGED} intent
92      * The local role of the PAN profile that the remote device is bound to.
93      * It can be one of {@link #LOCAL_NAP_ROLE} or {@link #LOCAL_PANU_ROLE}.
94      */
95     @SuppressLint("ActionValue")
96     public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
97 
98     /**
99      * Intent used to broadcast the change in tethering state of the Pan
100      * Profile
101      *
102      * <p>This intent will have 1 extra:
103      * <ul>
104      * <li> {@link #EXTRA_TETHERING_STATE} - The current state of Bluetooth
105      * tethering. </li>
106      * </ul>
107      *
108      * <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or
109      * {@link #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
118      * The tethering state of the PAN profile.
119      * It can be one of {@link #TETHERING_STATE_OFF} or {@link #TETHERING_STATE_ON}.
120      */
121     public static final String EXTRA_TETHERING_STATE =
122             "android.bluetooth.extra.TETHERING_STATE";
123 
124     /** @hide */
125     @IntDef({PAN_ROLE_NONE, LOCAL_NAP_ROLE, LOCAL_PANU_ROLE})
126     @Retention(RetentionPolicy.SOURCE)
127     public @interface LocalPanRole {}
128 
129     public static final int PAN_ROLE_NONE = 0;
130     /**
131      * The local device is acting as a Network Access Point.
132      */
133     public static final int LOCAL_NAP_ROLE = 1;
134 
135     /**
136      * The local device is acting as a PAN User.
137      */
138     public static final int LOCAL_PANU_ROLE = 2;
139 
140     /** @hide */
141     @IntDef({PAN_ROLE_NONE, REMOTE_NAP_ROLE, REMOTE_PANU_ROLE})
142     @Retention(RetentionPolicy.SOURCE)
143     public @interface RemotePanRole {}
144 
145     public static final int REMOTE_NAP_ROLE = 1;
146 
147     public static final int REMOTE_PANU_ROLE = 2;
148 
149     /** @hide **/
150     @IntDef({TETHERING_STATE_OFF, TETHERING_STATE_ON})
151     @Retention(RetentionPolicy.SOURCE)
152     public @interface TetheringState{}
153 
154     public static final int TETHERING_STATE_OFF = 1;
155 
156     public static final int TETHERING_STATE_ON = 2;
157     /**
158      * Return codes for the connect and disconnect Bluez / Dbus calls.
159      *
160      * @hide
161      */
162     public static final int PAN_DISCONNECT_FAILED_NOT_CONNECTED = 1000;
163 
164     /**
165      * @hide
166      */
167     public static final int PAN_CONNECT_FAILED_ALREADY_CONNECTED = 1001;
168 
169     /**
170      * @hide
171      */
172     public static final int PAN_CONNECT_FAILED_ATTEMPT_FAILED = 1002;
173 
174     /**
175      * @hide
176      */
177     public static final int PAN_OPERATION_GENERIC_FAILURE = 1003;
178 
179     /**
180      * @hide
181      */
182     public static final int PAN_OPERATION_SUCCESS = 1004;
183 
184     private final Context mContext;
185 
186     private final BluetoothAdapter mAdapter;
187     private final AttributionSource mAttributionSource;
188     private final BluetoothProfileConnector<IBluetoothPan> mProfileConnector =
189             new BluetoothProfileConnector(this, BluetoothProfile.PAN,
190                     "BluetoothPan", IBluetoothPan.class.getName()) {
191                 @Override
192                 public IBluetoothPan getServiceInterface(IBinder service) {
193                     return IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service));
194                 }
195     };
196 
197 
198     /**
199      * Create a BluetoothPan proxy object for interacting with the local
200      * Bluetooth Service which handles the Pan profile
201      *
202      * @hide
203      */
204     @UnsupportedAppUsage
BluetoothPan(Context context, ServiceListener listener, BluetoothAdapter adapter)205     /* package */ BluetoothPan(Context context, ServiceListener listener,
206             BluetoothAdapter adapter) {
207         mAdapter = adapter;
208         mAttributionSource = adapter.getAttributionSource();
209         mContext = context;
210         mProfileConnector.connect(context, listener);
211     }
212 
213     /**
214      * Closes the connection to the service and unregisters callbacks
215      */
216     @UnsupportedAppUsage
close()217     void close() {
218         if (VDBG) log("close()");
219         mProfileConnector.disconnect();
220     }
221 
getService()222     private IBluetoothPan getService() {
223         return mProfileConnector.getService();
224     }
225 
226     /** @hide */
finalize()227     protected void finalize() {
228         close();
229     }
230 
231     /**
232      * Initiate connection to a profile of the remote bluetooth device.
233      *
234      * <p> This API returns false in scenarios like the profile on the
235      * device is already connected or Bluetooth is not turned on.
236      * When this API returns true, it is guaranteed that
237      * connection state intent for the profile will be broadcasted with
238      * the state. Users can get the connection state of the profile
239      * from this intent.
240      *
241      * @param device Remote Bluetooth Device
242      * @return false on immediate error, true otherwise
243      * @hide
244      */
245     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
246     @RequiresBluetoothConnectPermission
247     @RequiresPermission(allOf = {
248             android.Manifest.permission.BLUETOOTH_CONNECT,
249             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
250     })
connect(BluetoothDevice device)251     public boolean connect(BluetoothDevice device) {
252         if (DBG) log("connect(" + device + ")");
253         final IBluetoothPan service = getService();
254         if (service != null && isEnabled() && isValidDevice(device)) {
255             try {
256                 return service.connect(device, mAttributionSource);
257             } catch (RemoteException e) {
258                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
259                 return false;
260             }
261         }
262         if (service == null) Log.w(TAG, "Proxy not attached to service");
263         return false;
264     }
265 
266     /**
267      * Initiate disconnection from a profile
268      *
269      * <p> This API will return false in scenarios like the profile on the
270      * Bluetooth device is not in connected state etc. When this API returns,
271      * true, it is guaranteed that the connection state change
272      * intent will be broadcasted with the state. Users can get the
273      * disconnection state of the profile from this intent.
274      *
275      * <p> If the disconnection is initiated by a remote device, the state
276      * will transition from {@link #STATE_CONNECTED} to
277      * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
278      * host (local) device the state will transition from
279      * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
280      * state {@link #STATE_DISCONNECTED}. The transition to
281      * {@link #STATE_DISCONNECTING} can be used to distinguish between the
282      * two scenarios.
283      *
284      * @param device Remote Bluetooth Device
285      * @return false on immediate error, true otherwise
286      * @hide
287      */
288     @UnsupportedAppUsage
289     @RequiresBluetoothConnectPermission
290     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
disconnect(BluetoothDevice device)291     public boolean disconnect(BluetoothDevice device) {
292         if (DBG) log("disconnect(" + device + ")");
293         final IBluetoothPan service = getService();
294         if (service != null && isEnabled() && isValidDevice(device)) {
295             try {
296                 return service.disconnect(device, mAttributionSource);
297             } catch (RemoteException e) {
298                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
299                 return false;
300             }
301         }
302         if (service == null) Log.w(TAG, "Proxy not attached to service");
303         return false;
304     }
305 
306     /**
307      * Set connection policy of the profile
308      *
309      * <p> The device should already be paired.
310      * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
311      * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
312      *
313      * @param device Paired bluetooth device
314      * @param connectionPolicy is the connection policy to set to for this profile
315      * @return true if connectionPolicy is set, false on error
316      * @hide
317      */
318     @SystemApi
319     @RequiresBluetoothConnectPermission
320     @RequiresPermission(allOf = {
321             android.Manifest.permission.BLUETOOTH_CONNECT,
322             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
323     })
setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)324     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
325             @ConnectionPolicy int connectionPolicy) {
326         if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
327         try {
328             final IBluetoothPan service = getService();
329             if (service != null && isEnabled()
330                     && isValidDevice(device)) {
331                 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
332                         && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
333                     return false;
334                 }
335                 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource);
336             }
337             if (service == null) Log.w(TAG, "Proxy not attached to service");
338             return false;
339         } catch (RemoteException e) {
340             Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
341             return false;
342         }
343     }
344 
345     /**
346      * {@inheritDoc}
347      * @hide
348      */
349     @SystemApi
350     @Override
351     @RequiresBluetoothConnectPermission
352     @RequiresPermission(allOf = {
353             android.Manifest.permission.BLUETOOTH_CONNECT,
354             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
355     })
getConnectedDevices()356     public @NonNull List<BluetoothDevice> getConnectedDevices() {
357         if (VDBG) log("getConnectedDevices()");
358         final IBluetoothPan service = getService();
359         if (service != null && isEnabled()) {
360             try {
361                 return Attributable.setAttributionSource(
362                         service.getConnectedDevices(mAttributionSource), mAttributionSource);
363             } catch (RemoteException e) {
364                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
365                 return new ArrayList<BluetoothDevice>();
366             }
367         }
368         if (service == null) Log.w(TAG, "Proxy not attached to service");
369         return new ArrayList<BluetoothDevice>();
370     }
371 
372     /**
373      * {@inheritDoc}
374      * @hide
375      */
376     @Override
377     @RequiresLegacyBluetoothPermission
378     @RequiresBluetoothConnectPermission
379     @RequiresPermission(allOf = {
380             android.Manifest.permission.BLUETOOTH_CONNECT,
381             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
382     })
getDevicesMatchingConnectionStates(int[] states)383     public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
384         if (VDBG) log("getDevicesMatchingStates()");
385         final IBluetoothPan service = getService();
386         if (service != null && isEnabled()) {
387             try {
388                 return Attributable.setAttributionSource(
389                         service.getDevicesMatchingConnectionStates(states, mAttributionSource),
390                         mAttributionSource);
391             } catch (RemoteException e) {
392                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
393                 return new ArrayList<BluetoothDevice>();
394             }
395         }
396         if (service == null) Log.w(TAG, "Proxy not attached to service");
397         return new ArrayList<BluetoothDevice>();
398     }
399 
400     /**
401      * {@inheritDoc}
402      * @hide
403      */
404     @SystemApi
405     @Override
406     @RequiresBluetoothConnectPermission
407     @RequiresPermission(allOf = {
408             android.Manifest.permission.BLUETOOTH_CONNECT,
409             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
410     })
getConnectionState(@onNull BluetoothDevice device)411     public int getConnectionState(@NonNull BluetoothDevice device) {
412         if (VDBG) log("getState(" + device + ")");
413         final IBluetoothPan service = getService();
414         if (service != null && isEnabled() && isValidDevice(device)) {
415             try {
416                 return service.getConnectionState(device, mAttributionSource);
417             } catch (RemoteException e) {
418                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
419                 return BluetoothProfile.STATE_DISCONNECTED;
420             }
421         }
422         if (service == null) Log.w(TAG, "Proxy not attached to service");
423         return BluetoothProfile.STATE_DISCONNECTED;
424     }
425 
426     /**
427      * Turns on/off bluetooth tethering
428      *
429      * @param value is whether to enable or disable bluetooth tethering
430      * @hide
431      */
432     @SystemApi
433     @RequiresBluetoothConnectPermission
434     @RequiresPermission(allOf = {
435             android.Manifest.permission.BLUETOOTH_CONNECT,
436             android.Manifest.permission.BLUETOOTH_PRIVILEGED,
437             android.Manifest.permission.TETHER_PRIVILEGED,
438     })
setBluetoothTethering(boolean value)439     public void setBluetoothTethering(boolean value) {
440         String pkgName = mContext.getOpPackageName();
441         if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName);
442         final IBluetoothPan service = getService();
443         if (service != null && isEnabled()) {
444             try {
445                 service.setBluetoothTethering(value, mAttributionSource);
446             } catch (RemoteException e) {
447                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
448             }
449         }
450     }
451 
452     /**
453      * Determines whether tethering is enabled
454      *
455      * @return true if tethering is on, false if not or some error occurred
456      * @hide
457      */
458     @SystemApi
459     @RequiresBluetoothConnectPermission
460     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
isTetheringOn()461     public boolean isTetheringOn() {
462         if (VDBG) log("isTetheringOn()");
463         final IBluetoothPan service = getService();
464         if (service != null && isEnabled()) {
465             try {
466                 return service.isTetheringOn(mAttributionSource);
467             } catch (RemoteException e) {
468                 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
469             }
470         }
471         return false;
472     }
473 
474     @UnsupportedAppUsage
isEnabled()475     private boolean isEnabled() {
476         return mAdapter.getState() == BluetoothAdapter.STATE_ON;
477     }
478 
479     @UnsupportedAppUsage
isValidDevice(BluetoothDevice device)480     private static boolean isValidDevice(BluetoothDevice device) {
481         return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
482     }
483 
484     @UnsupportedAppUsage
log(String msg)485     private static void log(String msg) {
486         Log.d(TAG, msg);
487     }
488 }
489