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