1 /* 2 * Copyright (C) 2016 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.NonNull; 21 import android.annotation.RequiresPermission; 22 import android.annotation.SdkConstant; 23 import android.annotation.SuppressLint; 24 import android.annotation.SdkConstant.SdkConstantType; 25 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 26 import android.content.Attributable; 27 import android.content.AttributionSource; 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.util.ArrayList; 35 import java.util.List; 36 37 /** 38 * This class provides the APIs to control the Bluetooth PBAP Client Profile. 39 * 40 * @hide 41 */ 42 public final class BluetoothPbapClient implements BluetoothProfile { 43 44 private static final String TAG = "BluetoothPbapClient"; 45 private static final boolean DBG = false; 46 private static final boolean VDBG = false; 47 48 @RequiresBluetoothConnectPermission 49 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 50 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 51 public static final String ACTION_CONNECTION_STATE_CHANGED = 52 "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED"; 53 54 /** There was an error trying to obtain the state */ 55 public static final int STATE_ERROR = -1; 56 57 public static final int RESULT_FAILURE = 0; 58 public static final int RESULT_SUCCESS = 1; 59 /** Connection canceled before completion. */ 60 public static final int RESULT_CANCELED = 2; 61 62 private final BluetoothAdapter mAdapter; 63 private final AttributionSource mAttributionSource; 64 private final BluetoothProfileConnector<IBluetoothPbapClient> mProfileConnector = 65 new BluetoothProfileConnector(this, BluetoothProfile.PBAP_CLIENT, 66 "BluetoothPbapClient", IBluetoothPbapClient.class.getName()) { 67 @Override 68 public IBluetoothPbapClient getServiceInterface(IBinder service) { 69 return IBluetoothPbapClient.Stub.asInterface(Binder.allowBlocking(service)); 70 } 71 }; 72 73 /** 74 * Create a BluetoothPbapClient proxy object. 75 */ BluetoothPbapClient(Context context, ServiceListener listener, BluetoothAdapter adapter)76 BluetoothPbapClient(Context context, ServiceListener listener, BluetoothAdapter adapter) { 77 if (DBG) { 78 Log.d(TAG, "Create BluetoothPbapClient proxy object"); 79 } 80 mAdapter = adapter; 81 mAttributionSource = adapter.getAttributionSource(); 82 mProfileConnector.connect(context, listener); 83 } 84 finalize()85 protected void finalize() throws Throwable { 86 try { 87 close(); 88 } finally { 89 super.finalize(); 90 } 91 } 92 93 /** 94 * Close the connection to the backing service. 95 * Other public functions of BluetoothPbapClient will return default error 96 * results once close() has been called. Multiple invocations of close() 97 * are ok. 98 */ close()99 public synchronized void close() { 100 mProfileConnector.disconnect(); 101 } 102 getService()103 private IBluetoothPbapClient getService() { 104 return mProfileConnector.getService(); 105 } 106 107 /** 108 * Initiate connection. 109 * Upon successful connection to remote PBAP server the Client will 110 * attempt to automatically download the users phonebook and call log. 111 * 112 * @param device a remote device we want connect to 113 * @return <code>true</code> if command has been issued successfully; <code>false</code> 114 * otherwise; 115 * 116 * @hide 117 */ 118 @RequiresBluetoothConnectPermission 119 @RequiresPermission(allOf = { 120 android.Manifest.permission.BLUETOOTH_CONNECT, 121 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 122 }) connect(BluetoothDevice device)123 public boolean connect(BluetoothDevice device) { 124 if (DBG) { 125 log("connect(" + device + ") for PBAP Client."); 126 } 127 final IBluetoothPbapClient service = getService(); 128 if (service != null && isEnabled() && isValidDevice(device)) { 129 try { 130 return service.connect(device, mAttributionSource); 131 } catch (RemoteException e) { 132 Log.e(TAG, Log.getStackTraceString(new Throwable())); 133 return false; 134 } 135 } 136 if (service == null) { 137 Log.w(TAG, "Proxy not attached to service"); 138 } 139 return false; 140 } 141 142 /** 143 * Initiate disconnect. 144 * 145 * @param device Remote Bluetooth Device 146 * @return false on error, true otherwise 147 * 148 * @hide 149 */ 150 @RequiresBluetoothConnectPermission 151 @RequiresPermission(allOf = { 152 android.Manifest.permission.BLUETOOTH_CONNECT, 153 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 154 }) disconnect(BluetoothDevice device)155 public boolean disconnect(BluetoothDevice device) { 156 if (DBG) { 157 log("disconnect(" + device + ")" + new Exception()); 158 } 159 final IBluetoothPbapClient service = getService(); 160 if (service != null && isEnabled() && isValidDevice(device)) { 161 try { 162 service.disconnect(device, mAttributionSource); 163 return true; 164 } catch (RemoteException e) { 165 Log.e(TAG, Log.getStackTraceString(new Throwable())); 166 return false; 167 } 168 } 169 if (service == null) { 170 Log.w(TAG, "Proxy not attached to service"); 171 } 172 return false; 173 } 174 175 /** 176 * Get the list of connected devices. 177 * Currently at most one. 178 * 179 * @return list of connected devices 180 */ 181 @Override 182 @RequiresBluetoothConnectPermission 183 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()184 public List<BluetoothDevice> getConnectedDevices() { 185 if (DBG) { 186 log("getConnectedDevices()"); 187 } 188 final IBluetoothPbapClient service = getService(); 189 if (service != null && isEnabled()) { 190 try { 191 return Attributable.setAttributionSource( 192 service.getConnectedDevices(mAttributionSource), mAttributionSource); 193 } catch (RemoteException e) { 194 Log.e(TAG, Log.getStackTraceString(new Throwable())); 195 return new ArrayList<BluetoothDevice>(); 196 } 197 } 198 if (service == null) { 199 Log.w(TAG, "Proxy not attached to service"); 200 } 201 return new ArrayList<BluetoothDevice>(); 202 } 203 204 /** 205 * Get the list of devices matching specified states. Currently at most one. 206 * 207 * @return list of matching devices 208 */ 209 @Override 210 @RequiresBluetoothConnectPermission 211 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)212 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 213 if (DBG) { 214 log("getDevicesMatchingStates()"); 215 } 216 final IBluetoothPbapClient service = getService(); 217 if (service != null && isEnabled()) { 218 try { 219 return Attributable.setAttributionSource( 220 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 221 mAttributionSource); 222 } catch (RemoteException e) { 223 Log.e(TAG, Log.getStackTraceString(new Throwable())); 224 return new ArrayList<BluetoothDevice>(); 225 } 226 } 227 if (service == null) { 228 Log.w(TAG, "Proxy not attached to service"); 229 } 230 return new ArrayList<BluetoothDevice>(); 231 } 232 233 /** 234 * Get connection state of device 235 * 236 * @return device connection state 237 */ 238 @Override 239 @RequiresBluetoothConnectPermission 240 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)241 public int getConnectionState(BluetoothDevice device) { 242 if (DBG) { 243 log("getConnectionState(" + device + ")"); 244 } 245 final IBluetoothPbapClient service = getService(); 246 if (service != null && isEnabled() && isValidDevice(device)) { 247 try { 248 return service.getConnectionState(device, mAttributionSource); 249 } catch (RemoteException e) { 250 Log.e(TAG, Log.getStackTraceString(new Throwable())); 251 return BluetoothProfile.STATE_DISCONNECTED; 252 } 253 } 254 if (service == null) { 255 Log.w(TAG, "Proxy not attached to service"); 256 } 257 return BluetoothProfile.STATE_DISCONNECTED; 258 } 259 log(String msg)260 private static void log(String msg) { 261 Log.d(TAG, msg); 262 } 263 isEnabled()264 private boolean isEnabled() { 265 return mAdapter.isEnabled(); 266 } 267 isValidDevice(BluetoothDevice device)268 private static boolean isValidDevice(BluetoothDevice device) { 269 return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); 270 } 271 272 /** 273 * Set priority of the profile 274 * 275 * <p> The device should already be paired. 276 * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, 277 * 278 * @param device Paired bluetooth device 279 * @param priority 280 * @return true if priority is set, false on error 281 * @hide 282 */ 283 @RequiresBluetoothConnectPermission 284 @RequiresPermission(allOf = { 285 android.Manifest.permission.BLUETOOTH_CONNECT, 286 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 287 }) setPriority(BluetoothDevice device, int priority)288 public boolean setPriority(BluetoothDevice device, int priority) { 289 if (DBG) log("setPriority(" + device + ", " + priority + ")"); 290 return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); 291 } 292 293 /** 294 * Set connection policy of the profile 295 * 296 * <p> The device should already be paired. 297 * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, 298 * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} 299 * 300 * @param device Paired bluetooth device 301 * @param connectionPolicy is the connection policy to set to for this profile 302 * @return true if connectionPolicy is set, false on error 303 * @hide 304 */ 305 @RequiresBluetoothConnectPermission 306 @RequiresPermission(allOf = { 307 android.Manifest.permission.BLUETOOTH_CONNECT, 308 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 309 }) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)310 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 311 @ConnectionPolicy int connectionPolicy) { 312 if (DBG) { 313 log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 314 } 315 final IBluetoothPbapClient service = getService(); 316 if (service != null && isEnabled() && isValidDevice(device)) { 317 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 318 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 319 return false; 320 } 321 try { 322 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 323 } catch (RemoteException e) { 324 Log.e(TAG, Log.getStackTraceString(new Throwable())); 325 return false; 326 } 327 } 328 if (service == null) { 329 Log.w(TAG, "Proxy not attached to service"); 330 } 331 return false; 332 } 333 334 /** 335 * Get the priority of the profile. 336 * 337 * <p> The priority can be any of: 338 * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} 339 * 340 * @param device Bluetooth device 341 * @return priority of the device 342 * @hide 343 */ 344 @RequiresBluetoothConnectPermission 345 @RequiresPermission(allOf = { 346 android.Manifest.permission.BLUETOOTH_CONNECT, 347 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 348 }) getPriority(BluetoothDevice device)349 public int getPriority(BluetoothDevice device) { 350 if (VDBG) log("getPriority(" + device + ")"); 351 return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); 352 } 353 354 /** 355 * Get the connection policy of the profile. 356 * 357 * <p> The connection policy can be any of: 358 * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, 359 * {@link #CONNECTION_POLICY_UNKNOWN} 360 * 361 * @param device Bluetooth device 362 * @return connection policy of the device 363 * @hide 364 */ 365 @RequiresBluetoothConnectPermission 366 @RequiresPermission(allOf = { 367 android.Manifest.permission.BLUETOOTH_CONNECT, 368 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 369 }) getConnectionPolicy(@onNull BluetoothDevice device)370 public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { 371 if (VDBG) { 372 log("getConnectionPolicy(" + device + ")"); 373 } 374 final IBluetoothPbapClient service = getService(); 375 if (service != null && isEnabled() && isValidDevice(device)) { 376 try { 377 return service.getConnectionPolicy(device, mAttributionSource); 378 } catch (RemoteException e) { 379 Log.e(TAG, Log.getStackTraceString(new Throwable())); 380 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 381 } 382 } 383 if (service == null) { 384 Log.w(TAG, "Proxy not attached to service"); 385 } 386 return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; 387 } 388 } 389