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