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