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