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.RequiresNoPermission; 21 import android.annotation.RequiresPermission; 22 import android.annotation.SdkConstant; 23 import android.annotation.SdkConstant.SdkConstantType; 24 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 25 import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.content.Attributable; 28 import android.content.AttributionSource; 29 import android.content.Context; 30 import android.os.Binder; 31 import android.os.Build; 32 import android.os.IBinder; 33 import android.os.RemoteException; 34 import android.util.Log; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** 40 * This class provides the APIs to control the Bluetooth SIM 41 * Access Profile (SAP). 42 * 43 * <p>BluetoothSap is a proxy object for controlling the Bluetooth 44 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get 45 * the BluetoothSap proxy object. 46 * 47 * <p>Each method is protected with its appropriate permission. 48 * 49 * @hide 50 */ 51 public final class BluetoothSap implements BluetoothProfile { 52 53 private static final String TAG = "BluetoothSap"; 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 profile. 59 * 60 * <p>This intent will have 4 extras: 61 * <ul> 62 * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> 63 * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> 64 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 65 * </ul> 66 * 67 * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of 68 * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, 69 * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. 70 * 71 * @hide 72 */ 73 @RequiresLegacyBluetoothPermission 74 @RequiresBluetoothConnectPermission 75 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 76 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 77 public static final String ACTION_CONNECTION_STATE_CHANGED = 78 "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; 79 80 /** 81 * There was an error trying to obtain the state. 82 * 83 * @hide 84 */ 85 public static final int STATE_ERROR = -1; 86 87 /** 88 * Connection state change succceeded. 89 * 90 * @hide 91 */ 92 public static final int RESULT_SUCCESS = 1; 93 94 /** 95 * Connection canceled before completion. 96 * 97 * @hide 98 */ 99 public static final int RESULT_CANCELED = 2; 100 101 private final BluetoothAdapter mAdapter; 102 private final AttributionSource mAttributionSource; 103 private final BluetoothProfileConnector<IBluetoothSap> mProfileConnector = 104 new BluetoothProfileConnector(this, BluetoothProfile.SAP, 105 "BluetoothSap", IBluetoothSap.class.getName()) { 106 @Override 107 public IBluetoothSap getServiceInterface(IBinder service) { 108 return IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service)); 109 } 110 }; 111 112 /** 113 * Create a BluetoothSap proxy object. 114 */ BluetoothSap(Context context, ServiceListener listener, BluetoothAdapter adapter)115 /* package */ BluetoothSap(Context context, ServiceListener listener, 116 BluetoothAdapter adapter) { 117 if (DBG) Log.d(TAG, "Create BluetoothSap proxy object"); 118 mAdapter = adapter; 119 mAttributionSource = adapter.getAttributionSource(); 120 mProfileConnector.connect(context, listener); 121 } 122 finalize()123 protected void finalize() throws Throwable { 124 try { 125 close(); 126 } finally { 127 super.finalize(); 128 } 129 } 130 131 /** 132 * Close the connection to the backing service. 133 * Other public functions of BluetoothSap will return default error 134 * results once close() has been called. Multiple invocations of close() 135 * are ok. 136 * 137 * @hide 138 */ close()139 public synchronized void close() { 140 mProfileConnector.disconnect(); 141 } 142 getService()143 private IBluetoothSap getService() { 144 return mProfileConnector.getService(); 145 } 146 147 /** 148 * Get the current state of the BluetoothSap service. 149 * 150 * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not 151 * connected to the Sap service. 152 * @hide 153 */ 154 @RequiresBluetoothConnectPermission 155 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getState()156 public int getState() { 157 if (VDBG) log("getState()"); 158 final IBluetoothSap service = getService(); 159 if (service != null) { 160 try { 161 return service.getState(mAttributionSource); 162 } catch (RemoteException e) { 163 Log.e(TAG, e.toString()); 164 } 165 } else { 166 Log.w(TAG, "Proxy not attached to service"); 167 if (DBG) log(Log.getStackTraceString(new Throwable())); 168 } 169 return BluetoothSap.STATE_ERROR; 170 } 171 172 /** 173 * Get the currently connected remote Bluetooth device (PCE). 174 * 175 * @return The remote Bluetooth device, or null if not in connected or connecting state, or if 176 * this proxy object is not connected to the Sap service. 177 * @hide 178 */ 179 @RequiresBluetoothConnectPermission 180 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getClient()181 public BluetoothDevice getClient() { 182 if (VDBG) log("getClient()"); 183 final IBluetoothSap service = getService(); 184 if (service != null) { 185 try { 186 return Attributable.setAttributionSource( 187 service.getClient(mAttributionSource), mAttributionSource); 188 } catch (RemoteException e) { 189 Log.e(TAG, e.toString()); 190 } 191 } else { 192 Log.w(TAG, "Proxy not attached to service"); 193 if (DBG) log(Log.getStackTraceString(new Throwable())); 194 } 195 return null; 196 } 197 198 /** 199 * Returns true if the specified Bluetooth device is connected. 200 * Returns false if not connected, or if this proxy object is not 201 * currently connected to the Sap service. 202 * 203 * @hide 204 */ 205 @RequiresBluetoothConnectPermission 206 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) isConnected(BluetoothDevice device)207 public boolean isConnected(BluetoothDevice device) { 208 if (VDBG) log("isConnected(" + device + ")"); 209 final IBluetoothSap service = getService(); 210 if (service != null) { 211 try { 212 return service.isConnected(device, mAttributionSource); 213 } catch (RemoteException e) { 214 Log.e(TAG, e.toString()); 215 } 216 } else { 217 Log.w(TAG, "Proxy not attached to service"); 218 if (DBG) log(Log.getStackTraceString(new Throwable())); 219 } 220 return false; 221 } 222 223 /** 224 * Initiate connection. Initiation of outgoing connections is not 225 * supported for SAP server. 226 * 227 * @hide 228 */ 229 @RequiresNoPermission connect(BluetoothDevice device)230 public boolean connect(BluetoothDevice device) { 231 if (DBG) log("connect(" + device + ")" + "not supported for SAPS"); 232 return false; 233 } 234 235 /** 236 * Initiate disconnect. 237 * 238 * @param device Remote Bluetooth Device 239 * @return false on error, true otherwise 240 * @hide 241 */ 242 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 243 @RequiresBluetoothConnectPermission 244 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnect(BluetoothDevice device)245 public boolean disconnect(BluetoothDevice device) { 246 if (DBG) log("disconnect(" + device + ")"); 247 final IBluetoothSap service = getService(); 248 if (service != null && isEnabled() && isValidDevice(device)) { 249 try { 250 return service.disconnect(device, mAttributionSource); 251 } catch (RemoteException e) { 252 Log.e(TAG, Log.getStackTraceString(new Throwable())); 253 return false; 254 } 255 } 256 if (service == null) Log.w(TAG, "Proxy not attached to service"); 257 return false; 258 } 259 260 /** 261 * Get the list of connected devices. Currently at most one. 262 * 263 * @return list of connected devices 264 * @hide 265 */ 266 @RequiresBluetoothConnectPermission 267 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()268 public List<BluetoothDevice> getConnectedDevices() { 269 if (DBG) log("getConnectedDevices()"); 270 final IBluetoothSap service = getService(); 271 if (service != null && isEnabled()) { 272 try { 273 return Attributable.setAttributionSource( 274 service.getConnectedDevices(mAttributionSource), mAttributionSource); 275 } catch (RemoteException e) { 276 Log.e(TAG, Log.getStackTraceString(new Throwable())); 277 return new ArrayList<BluetoothDevice>(); 278 } 279 } 280 if (service == null) Log.w(TAG, "Proxy not attached to service"); 281 return new ArrayList<BluetoothDevice>(); 282 } 283 284 /** 285 * Get the list of devices matching specified states. Currently at most one. 286 * 287 * @return list of matching devices 288 * @hide 289 */ 290 @RequiresBluetoothConnectPermission 291 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)292 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 293 if (DBG) log("getDevicesMatchingStates()"); 294 final IBluetoothSap service = getService(); 295 if (service != null && isEnabled()) { 296 try { 297 return Attributable.setAttributionSource( 298 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 299 mAttributionSource); 300 } catch (RemoteException e) { 301 Log.e(TAG, Log.getStackTraceString(new Throwable())); 302 return new ArrayList<BluetoothDevice>(); 303 } 304 } 305 if (service == null) Log.w(TAG, "Proxy not attached to service"); 306 return new ArrayList<BluetoothDevice>(); 307 } 308 309 /** 310 * Get connection state of device 311 * 312 * @return device connection state 313 * @hide 314 */ 315 @RequiresBluetoothConnectPermission 316 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)317 public int getConnectionState(BluetoothDevice device) { 318 if (DBG) log("getConnectionState(" + device + ")"); 319 final IBluetoothSap service = getService(); 320 if (service != null && isEnabled() && isValidDevice(device)) { 321 try { 322 return service.getConnectionState(device, mAttributionSource); 323 } catch (RemoteException e) { 324 Log.e(TAG, Log.getStackTraceString(new Throwable())); 325 return BluetoothProfile.STATE_DISCONNECTED; 326 } 327 } 328 if (service == null) Log.w(TAG, "Proxy not attached to service"); 329 return BluetoothProfile.STATE_DISCONNECTED; 330 } 331 332 /** 333 * Set priority of the profile 334 * 335 * <p> The device should already be paired. 336 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, 337 * 338 * @param device Paired bluetooth device 339 * @param priority 340 * @return true if priority is set, false on error 341 * @hide 342 */ 343 @RequiresBluetoothConnectPermission 344 @RequiresPermission(allOf = { 345 android.Manifest.permission.BLUETOOTH_CONNECT, 346 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 347 }) setPriority(BluetoothDevice device, int priority)348 public boolean setPriority(BluetoothDevice device, int priority) { 349 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 350 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 351 } 352 353 /** 354 * Set connection policy of the profile 355 * 356 * <p> The device should already be paired. 357 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 358 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 359 * 360 * @param device Paired bluetooth device 361 * @param connectionPolicy is the connection policy to set to for this profile 362 * @return true if connectionPolicy is set, false on error 363 * @hide 364 */ 365 @RequiresBluetoothConnectPermission 366 @RequiresPermission(allOf = { 367 android.Manifest.permission.BLUETOOTH_CONNECT, 368 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 369 }) setConnectionPolicy(BluetoothDevice device, @ConnectionPolicy int connectionPolicy)370 public boolean setConnectionPolicy(BluetoothDevice device, 371 @ConnectionPolicy int connectionPolicy) { 372 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 373 final IBluetoothSap service = getService(); 374 if (service != null && isEnabled() && isValidDevice(device)) { 375 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 376 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 377 return false; 378 } 379 try { 380 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 381 } catch (RemoteException e) { 382 Log.e(TAG, Log.getStackTraceString(new Throwable())); 383 return false; 384 } 385 } 386 if (service == null) Log.w(TAG, "Proxy not attached to service"); 387 return false; 388 } 389 390 /** 391 * Get the priority of the profile. 392 * 393 * <p> The priority can be any of: 394 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 395 * 396 * @param device Bluetooth device 397 * @return priority of the device 398 * @hide 399 */ 400 @RequiresBluetoothConnectPermission 401 @RequiresPermission(allOf = { 402 android.Manifest.permission.BLUETOOTH_CONNECT, 403 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 404 }) getPriority(BluetoothDevice device)405 public int getPriority(BluetoothDevice device) { 406 if (VDBG) log("getPriority(" + device + ")"); 407 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); 408 } 409 410 /** 411 * Get the connection policy of the profile. 412 * 413 * <p> The connection policy can be any of: 414 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 415 * {@link #CONNECTION_POLICY_UNKNOWN} 416 * 417 * @param device Bluetooth device 418 * @return connection policy of the device 419 * @hide 420 */ 421 @RequiresBluetoothConnectPermission 422 @RequiresPermission(allOf = { 423 android.Manifest.permission.BLUETOOTH_CONNECT, 424 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 425 }) getConnectionPolicy(BluetoothDevice device)426 public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) { 427 if (VDBG) log("getConnectionPolicy(" + device + ")"); 428 final IBluetoothSap service = getService(); 429 if (service != null && isEnabled() && isValidDevice(device)) { 430 try { 431 return service.getConnectionPolicy(device, mAttributionSource); 432 } catch (RemoteException e) { 433 Log.e(TAG, Log.getStackTraceString(new Throwable())); 434 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 435 } 436 } 437 if (service == null) Log.w(TAG, "Proxy not attached to service"); 438 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 439 } 440 log(String msg)441 private static void log(String msg) { 442 Log.d(TAG, msg); 443 } 444 isEnabled()445 private boolean isEnabled() { 446 return mAdapter.isEnabled(); 447 } 448 isValidDevice(BluetoothDevice device)449 private static boolean isValidDevice(BluetoothDevice device) { 450 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 451 } 452 } 453