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