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