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