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