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.Manifest; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SdkConstant; 24 import android.annotation.SdkConstant.SdkConstantType; 25 import android.annotation.SuppressLint; 26 import android.annotation.SystemApi; 27 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 28 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 29 import android.compat.annotation.UnsupportedAppUsage; 30 import android.content.Attributable; 31 import android.content.AttributionSource; 32 import android.content.Context; 33 import android.os.Binder; 34 import android.os.Build; 35 import android.os.IBinder; 36 import android.os.RemoteException; 37 import android.util.Log; 38 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * This class provides the APIs to control the Bluetooth Pan 46 * Profile. 47 * 48 * <p>BluetoothPan is a proxy object for controlling the Bluetooth 49 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 50 * the BluetoothPan proxy object. 51 * 52 * <p>Each method is protected with its appropriate permission. 53 * 54 * @hide 55 */ 56 @SystemApi 57 public final class BluetoothPan implements BluetoothProfile { 58 private static final String TAG = "BluetoothPan"; 59 private static final boolean DBG = true; 60 private static final boolean VDBG = false; 61 62 /** 63 * Intent used to broadcast the change in connection state of the Pan 64 * profile. 65 * 66 * <p>This intent will have 4 extras: 67 * <ul> 68 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 69 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 70 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 71 * <li> {@link #EXTRA_LOCAL_ROLE} - Which local role the remote device is 72 * bound to. </li> 73 * </ul> 74 * 75 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 76 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 77 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 78 * 79 * <p> {@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or 80 * {@link #LOCAL_PANU_ROLE} 81 */ 82 @SuppressLint("ActionValue") 83 @RequiresLegacyBluetoothPermission 84 @RequiresBluetoothConnectPermission 85 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 86 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 87 public static final String ACTION_CONNECTION_STATE_CHANGED = 88 "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; 89 90 /** 91 * Extra for {@link #ACTION_CONNECTION_STATE_CHANGED} intent 92 * The local role of the PAN profile that the remote device is bound to. 93 * It can be one of {@link #LOCAL_NAP_ROLE} or {@link #LOCAL_PANU_ROLE}. 94 */ 95 @SuppressLint("ActionValue") 96 public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE"; 97 98 /** 99 * Intent used to broadcast the change in tethering state of the Pan 100 * Profile 101 * 102 * <p>This intent will have 1 extra: 103 * <ul> 104 * <li> {@link #EXTRA_TETHERING_STATE} - The current state of Bluetooth 105 * tethering. </li> 106 * </ul> 107 * 108 * <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or 109 * {@link #TETHERING_STATE_ON} 110 */ 111 @RequiresLegacyBluetoothPermission 112 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 113 public static final String ACTION_TETHERING_STATE_CHANGED = 114 "android.bluetooth.action.TETHERING_STATE_CHANGED"; 115 116 /** 117 * Extra for {@link #ACTION_TETHERING_STATE_CHANGED} intent 118 * The tethering state of the PAN profile. 119 * It can be one of {@link #TETHERING_STATE_OFF} or {@link #TETHERING_STATE_ON}. 120 */ 121 public static final String EXTRA_TETHERING_STATE = 122 "android.bluetooth.extra.TETHERING_STATE"; 123 124 /** @hide */ 125 @IntDef({PAN_ROLE_NONE, LOCAL_NAP_ROLE, LOCAL_PANU_ROLE}) 126 @Retention(RetentionPolicy.SOURCE) 127 public @interface LocalPanRole {} 128 129 public static final int PAN_ROLE_NONE = 0; 130 /** 131 * The local device is acting as a Network Access Point. 132 */ 133 public static final int LOCAL_NAP_ROLE = 1; 134 135 /** 136 * The local device is acting as a PAN User. 137 */ 138 public static final int LOCAL_PANU_ROLE = 2; 139 140 /** @hide */ 141 @IntDef({PAN_ROLE_NONE, REMOTE_NAP_ROLE, REMOTE_PANU_ROLE}) 142 @Retention(RetentionPolicy.SOURCE) 143 public @interface RemotePanRole {} 144 145 public static final int REMOTE_NAP_ROLE = 1; 146 147 public static final int REMOTE_PANU_ROLE = 2; 148 149 /** @hide **/ 150 @IntDef({TETHERING_STATE_OFF, TETHERING_STATE_ON}) 151 @Retention(RetentionPolicy.SOURCE) 152 public @interface TetheringState{} 153 154 public static final int TETHERING_STATE_OFF = 1; 155 156 public static final int TETHERING_STATE_ON = 2; 157 /** 158 * Return codes for the connect and disconnect Bluez / Dbus calls. 159 * 160 * @hide 161 */ 162 public static final int PAN_DISCONNECT_FAILED_NOT_CONNECTED = 1000; 163 164 /** 165 * @hide 166 */ 167 public static final int PAN_CONNECT_FAILED_ALREADY_CONNECTED = 1001; 168 169 /** 170 * @hide 171 */ 172 public static final int PAN_CONNECT_FAILED_ATTEMPT_FAILED = 1002; 173 174 /** 175 * @hide 176 */ 177 public static final int PAN_OPERATION_GENERIC_FAILURE = 1003; 178 179 /** 180 * @hide 181 */ 182 public static final int PAN_OPERATION_SUCCESS = 1004; 183 184 private final Context mContext; 185 186 private final BluetoothAdapter mAdapter; 187 private final AttributionSource mAttributionSource; 188 private final BluetoothProfileConnector<IBluetoothPan> mProfileConnector = 189 new BluetoothProfileConnector(this, BluetoothProfile.PAN, 190 "BluetoothPan", IBluetoothPan.class.getName()) { 191 @Override 192 public IBluetoothPan getServiceInterface(IBinder service) { 193 return IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service)); 194 } 195 }; 196 197 198 /** 199 * Create a BluetoothPan proxy object for interacting with the local 200 * Bluetooth Service which handles the Pan profile 201 * 202 * @hide 203 */ 204 @UnsupportedAppUsage BluetoothPan(Context context, ServiceListener listener, BluetoothAdapter adapter)205 /* package */ BluetoothPan(Context context, ServiceListener listener, 206 BluetoothAdapter adapter) { 207 mAdapter = adapter; 208 mAttributionSource = adapter.getAttributionSource(); 209 mContext = context; 210 mProfileConnector.connect(context, listener); 211 } 212 213 /** 214 * Closes the connection to the service and unregisters callbacks 215 */ 216 @UnsupportedAppUsage close()217 void close() { 218 if (VDBG) log("close()"); 219 mProfileConnector.disconnect(); 220 } 221 getService()222 private IBluetoothPan getService() { 223 return mProfileConnector.getService(); 224 } 225 226 /** @hide */ finalize()227 protected void finalize() { 228 close(); 229 } 230 231 /** 232 * Initiate connection to a profile of the remote bluetooth device. 233 * 234 * <p> This API returns false in scenarios like the profile on the 235 * device is already connected or Bluetooth is not turned on. 236 * When this API returns true, it is guaranteed that 237 * connection state intent for the profile will be broadcasted with 238 * the state. Users can get the connection state of the profile 239 * from this intent. 240 * 241 * @param device Remote Bluetooth Device 242 * @return false on immediate error, true otherwise 243 * @hide 244 */ 245 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 246 @RequiresBluetoothConnectPermission 247 @RequiresPermission(allOf = { 248 android.Manifest.permission.BLUETOOTH_CONNECT, 249 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 250 }) connect(BluetoothDevice device)251 public boolean connect(BluetoothDevice device) { 252 if (DBG) log("connect(" + device + ")"); 253 final IBluetoothPan service = getService(); 254 if (service != null && isEnabled() && isValidDevice(device)) { 255 try { 256 return service.connect(device, mAttributionSource); 257 } catch (RemoteException e) { 258 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 259 return false; 260 } 261 } 262 if (service == null) Log.w(TAG, "Proxy not attached to service"); 263 return false; 264 } 265 266 /** 267 * Initiate disconnection from a profile 268 * 269 * <p> This API will return false in scenarios like the profile on the 270 * Bluetooth device is not in connected state etc. When this API returns, 271 * true, it is guaranteed that the connection state change 272 * intent will be broadcasted with the state. Users can get the 273 * disconnection state of the profile from this intent. 274 * 275 * <p> If the disconnection is initiated by a remote device, the state 276 * will transition from {@link #STATE_CONNECTED} to 277 * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the 278 * host (local) device the state will transition from 279 * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to 280 * state {@link #STATE_DISCONNECTED}. The transition to 281 * {@link #STATE_DISCONNECTING} can be used to distinguish between the 282 * two scenarios. 283 * 284 * @param device Remote Bluetooth Device 285 * @return false on immediate error, true otherwise 286 * @hide 287 */ 288 @UnsupportedAppUsage 289 @RequiresBluetoothConnectPermission 290 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnect(BluetoothDevice device)291 public boolean disconnect(BluetoothDevice device) { 292 if (DBG) log("disconnect(" + device + ")"); 293 final IBluetoothPan service = getService(); 294 if (service != null && isEnabled() && isValidDevice(device)) { 295 try { 296 return service.disconnect(device, mAttributionSource); 297 } catch (RemoteException e) { 298 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 299 return false; 300 } 301 } 302 if (service == null) Log.w(TAG, "Proxy not attached to service"); 303 return false; 304 } 305 306 /** 307 * Set connection policy of the profile 308 * 309 * <p> The device should already be paired. 310 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 311 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 312 * 313 * @param device Paired bluetooth device 314 * @param connectionPolicy is the connection policy to set to for this profile 315 * @return true if connectionPolicy is set, false on error 316 * @hide 317 */ 318 @SystemApi 319 @RequiresBluetoothConnectPermission 320 @RequiresPermission(allOf = { 321 android.Manifest.permission.BLUETOOTH_CONNECT, 322 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 323 }) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)324 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 325 @ConnectionPolicy int connectionPolicy) { 326 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 327 try { 328 final IBluetoothPan service = getService(); 329 if (service != null && isEnabled() 330 && isValidDevice(device)) { 331 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 332 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 333 return false; 334 } 335 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 336 } 337 if (service == null) Log.w(TAG, "Proxy not attached to service"); 338 return false; 339 } catch (RemoteException e) { 340 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 341 return false; 342 } 343 } 344 345 /** 346 * {@inheritDoc} 347 * @hide 348 */ 349 @SystemApi 350 @Override 351 @RequiresBluetoothConnectPermission 352 @RequiresPermission(allOf = { 353 android.Manifest.permission.BLUETOOTH_CONNECT, 354 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 355 }) getConnectedDevices()356 public @NonNull List<BluetoothDevice> getConnectedDevices() { 357 if (VDBG) log("getConnectedDevices()"); 358 final IBluetoothPan service = getService(); 359 if (service != null && isEnabled()) { 360 try { 361 return Attributable.setAttributionSource( 362 service.getConnectedDevices(mAttributionSource), mAttributionSource); 363 } catch (RemoteException e) { 364 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 365 return new ArrayList<BluetoothDevice>(); 366 } 367 } 368 if (service == null) Log.w(TAG, "Proxy not attached to service"); 369 return new ArrayList<BluetoothDevice>(); 370 } 371 372 /** 373 * {@inheritDoc} 374 * @hide 375 */ 376 @Override 377 @RequiresLegacyBluetoothPermission 378 @RequiresBluetoothConnectPermission 379 @RequiresPermission(allOf = { 380 android.Manifest.permission.BLUETOOTH_CONNECT, 381 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 382 }) getDevicesMatchingConnectionStates(int[] states)383 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 384 if (VDBG) log("getDevicesMatchingStates()"); 385 final IBluetoothPan service = getService(); 386 if (service != null && isEnabled()) { 387 try { 388 return Attributable.setAttributionSource( 389 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 390 mAttributionSource); 391 } catch (RemoteException e) { 392 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 393 return new ArrayList<BluetoothDevice>(); 394 } 395 } 396 if (service == null) Log.w(TAG, "Proxy not attached to service"); 397 return new ArrayList<BluetoothDevice>(); 398 } 399 400 /** 401 * {@inheritDoc} 402 * @hide 403 */ 404 @SystemApi 405 @Override 406 @RequiresBluetoothConnectPermission 407 @RequiresPermission(allOf = { 408 android.Manifest.permission.BLUETOOTH_CONNECT, 409 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 410 }) getConnectionState(@onNull BluetoothDevice device)411 public int getConnectionState(@NonNull BluetoothDevice device) { 412 if (VDBG) log("getState(" + device + ")"); 413 final IBluetoothPan service = getService(); 414 if (service != null && isEnabled() && isValidDevice(device)) { 415 try { 416 return service.getConnectionState(device, mAttributionSource); 417 } catch (RemoteException e) { 418 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 419 return BluetoothProfile.STATE_DISCONNECTED; 420 } 421 } 422 if (service == null) Log.w(TAG, "Proxy not attached to service"); 423 return BluetoothProfile.STATE_DISCONNECTED; 424 } 425 426 /** 427 * Turns on/off bluetooth tethering 428 * 429 * @param value is whether to enable or disable bluetooth tethering 430 * @hide 431 */ 432 @SystemApi 433 @RequiresBluetoothConnectPermission 434 @RequiresPermission(allOf = { 435 android.Manifest.permission.BLUETOOTH_CONNECT, 436 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 437 android.Manifest.permission.TETHER_PRIVILEGED, 438 }) setBluetoothTethering(boolean value)439 public void setBluetoothTethering(boolean value) { 440 String pkgName = mContext.getOpPackageName(); 441 if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); 442 final IBluetoothPan service = getService(); 443 if (service != null && isEnabled()) { 444 try { 445 service.setBluetoothTethering(value, mAttributionSource); 446 } catch (RemoteException e) { 447 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 448 } 449 } 450 } 451 452 /** 453 * Determines whether tethering is enabled 454 * 455 * @return true if tethering is on, false if not or some error occurred 456 * @hide 457 */ 458 @SystemApi 459 @RequiresBluetoothConnectPermission 460 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isTetheringOn()461 public boolean isTetheringOn() { 462 if (VDBG) log("isTetheringOn()"); 463 final IBluetoothPan service = getService(); 464 if (service != null && isEnabled()) { 465 try { 466 return service.isTetheringOn(mAttributionSource); 467 } catch (RemoteException e) { 468 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 469 } 470 } 471 return false; 472 } 473 474 @UnsupportedAppUsage isEnabled()475 private boolean isEnabled() { 476 return mAdapter.getState() == BluetoothAdapter.STATE_ON; 477 } 478 479 @UnsupportedAppUsage isValidDevice(BluetoothDevice device)480 private static boolean isValidDevice(BluetoothDevice device) { 481 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 482 } 483 484 @UnsupportedAppUsage log(String msg)485 private static void log(String msg) { 486 Log.d(TAG, msg); 487 } 488 } 489