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.SdkConstant; 23 import android.annotation.SuppressLint; 24 import android.annotation.SystemApi; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.ServiceConnection; 30 import android.os.IBinder; 31 import android.os.RemoteException; 32 import android.os.UserHandle; 33 import android.util.Log; 34 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.List; 38 39 /** 40 * Public API for controlling the Bluetooth Pbap Service. This includes 41 * Bluetooth Phone book Access profile. 42 * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap 43 * Service via IPC. 44 * 45 * Creating a BluetoothPbap object will create a binding with the 46 * BluetoothPbap service. Users of this object should call close() when they 47 * are finished with the BluetoothPbap, so that this proxy object can unbind 48 * from the service. 49 * 50 * This BluetoothPbap object is not immediately bound to the 51 * BluetoothPbap service. Use the ServiceListener interface to obtain a 52 * notification when it is bound, this is especially important if you wish to 53 * immediately call methods on BluetoothPbap after construction. 54 * 55 * To get an instance of the BluetoothPbap class, you can call 56 * {@link BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param 57 * being {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of 58 * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}. 59 * 60 * Android only supports one connected Bluetooth Pce at a time. 61 * 62 * @hide 63 */ 64 @SystemApi 65 public class BluetoothPbap implements BluetoothProfile { 66 67 private static final String TAG = "BluetoothPbap"; 68 private static final boolean DBG = false; 69 70 /** 71 * Intent used to broadcast the change in connection state of the PBAP 72 * profile. 73 * 74 * <p>This intent will have 3 extras: 75 * <ul> 76 * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li> 77 * <li> {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li> 78 * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> 79 * </ul> 80 * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE} 81 * can be any of {@link BluetoothProfile#STATE_DISCONNECTED}, 82 * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, 83 * {@link BluetoothProfile#STATE_DISCONNECTING}. 84 * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to 85 * receive. 86 * 87 * @hide 88 */ 89 @SuppressLint("ActionValue") 90 @SystemApi 91 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) 92 @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) 93 public static final String ACTION_CONNECTION_STATE_CHANGED = 94 "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; 95 96 private volatile IBluetoothPbap mService; 97 private final Context mContext; 98 private ServiceListener mServiceListener; 99 private BluetoothAdapter mAdapter; 100 101 /** @hide */ 102 public static final int RESULT_FAILURE = 0; 103 /** @hide */ 104 public static final int RESULT_SUCCESS = 1; 105 /** 106 * Connection canceled before completion. 107 * 108 * @hide 109 */ 110 public static final int RESULT_CANCELED = 2; 111 112 private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = 113 new IBluetoothStateChangeCallback.Stub() { 114 public void onBluetoothStateChange(boolean up) { 115 log("onBluetoothStateChange: up=" + up); 116 if (!up) { 117 doUnbind(); 118 } else { 119 doBind(); 120 } 121 } 122 }; 123 124 /** 125 * Create a BluetoothPbap proxy object. 126 * 127 * @hide 128 */ BluetoothPbap(Context context, ServiceListener l)129 public BluetoothPbap(Context context, ServiceListener l) { 130 mContext = context; 131 mServiceListener = l; 132 mAdapter = BluetoothAdapter.getDefaultAdapter(); 133 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 134 if (mgr != null) { 135 try { 136 mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); 137 } catch (RemoteException re) { 138 Log.e(TAG, "", re); 139 } 140 } 141 doBind(); 142 } 143 doBind()144 boolean doBind() { 145 synchronized (mConnection) { 146 try { 147 if (mService == null) { 148 log("Binding service..."); 149 Intent intent = new Intent(IBluetoothPbap.class.getName()); 150 ComponentName comp = intent.resolveSystemService( 151 mContext.getPackageManager(), 0); 152 intent.setComponent(comp); 153 if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0, 154 UserHandle.CURRENT_OR_SELF)) { 155 Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent); 156 return false; 157 } 158 } 159 } catch (SecurityException se) { 160 Log.e(TAG, "", se); 161 return false; 162 } 163 } 164 return true; 165 } 166 doUnbind()167 private void doUnbind() { 168 synchronized (mConnection) { 169 if (mService != null) { 170 log("Unbinding service..."); 171 try { 172 mContext.unbindService(mConnection); 173 } catch (IllegalArgumentException ie) { 174 Log.e(TAG, "", ie); 175 } finally { 176 mService = null; 177 } 178 } 179 } 180 } 181 182 /** @hide */ finalize()183 protected void finalize() throws Throwable { 184 try { 185 close(); 186 } finally { 187 super.finalize(); 188 } 189 } 190 191 /** 192 * Close the connection to the backing service. 193 * Other public functions of BluetoothPbap will return default error 194 * results once close() has been called. Multiple invocations of close() 195 * are ok. 196 * 197 * @hide 198 */ close()199 public synchronized void close() { 200 IBluetoothManager mgr = mAdapter.getBluetoothManager(); 201 if (mgr != null) { 202 try { 203 mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); 204 } catch (RemoteException re) { 205 Log.e(TAG, "", re); 206 } 207 } 208 doUnbind(); 209 mServiceListener = null; 210 } 211 212 /** 213 * {@inheritDoc} 214 * 215 * @hide 216 */ 217 @Override getConnectedDevices()218 public List<BluetoothDevice> getConnectedDevices() { 219 log("getConnectedDevices()"); 220 final IBluetoothPbap service = mService; 221 if (service == null) { 222 Log.w(TAG, "Proxy not attached to service"); 223 return new ArrayList<BluetoothDevice>(); 224 } 225 try { 226 return service.getConnectedDevices(); 227 } catch (RemoteException e) { 228 Log.e(TAG, e.toString()); 229 } 230 return new ArrayList<BluetoothDevice>(); 231 } 232 233 /** 234 * {@inheritDoc} 235 * 236 * @hide 237 */ 238 @SystemApi 239 @Override 240 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionState(@onNull BluetoothDevice device)241 public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { 242 log("getConnectionState: device=" + device); 243 try { 244 final IBluetoothPbap service = mService; 245 if (service != null && isEnabled() && isValidDevice(device)) { 246 return service.getConnectionState(device); 247 } 248 if (service == null) { 249 Log.w(TAG, "Proxy not attached to service"); 250 } 251 return BluetoothProfile.STATE_DISCONNECTED; 252 } catch (RemoteException e) { 253 Log.e(TAG, e.toString()); 254 } 255 return BluetoothProfile.STATE_DISCONNECTED; 256 } 257 258 /** 259 * {@inheritDoc} 260 * 261 * @hide 262 */ 263 @Override getDevicesMatchingConnectionStates(int[] states)264 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 265 log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states)); 266 final IBluetoothPbap service = mService; 267 if (service == null) { 268 Log.w(TAG, "Proxy not attached to service"); 269 return new ArrayList<BluetoothDevice>(); 270 } 271 try { 272 return service.getDevicesMatchingConnectionStates(states); 273 } catch (RemoteException e) { 274 Log.e(TAG, e.toString()); 275 } 276 return new ArrayList<BluetoothDevice>(); 277 } 278 279 /** 280 * Set connection policy of the profile and tries to disconnect it if connectionPolicy is 281 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 282 * 283 * <p> The device should already be paired. 284 * Connection policy can be one of: 285 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 286 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 287 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 288 * 289 * @param device Paired bluetooth device 290 * @param connectionPolicy is the connection policy to set to for this profile 291 * @return true if connectionPolicy is set, false on error 292 * 293 * @hide 294 */ 295 @SystemApi 296 @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(@onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)297 public boolean setConnectionPolicy(@NonNull BluetoothDevice device, 298 @ConnectionPolicy int connectionPolicy) { 299 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 300 try { 301 final IBluetoothPbap service = mService; 302 if (service != null && isEnabled() 303 && isValidDevice(device)) { 304 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 305 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 306 return false; 307 } 308 return service.setConnectionPolicy(device, connectionPolicy); 309 } 310 if (service == null) Log.w(TAG, "Proxy not attached to service"); 311 return false; 312 } catch (RemoteException e) { 313 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 314 return false; 315 } 316 } 317 318 /** 319 * Disconnects the current Pbap client (PCE). Currently this call blocks, 320 * it may soon be made asynchronous. Returns false if this proxy object is 321 * not currently connected to the Pbap service. 322 * 323 * @hide 324 */ 325 @UnsupportedAppUsage disconnect(BluetoothDevice device)326 public boolean disconnect(BluetoothDevice device) { 327 log("disconnect()"); 328 final IBluetoothPbap service = mService; 329 if (service == null) { 330 Log.w(TAG, "Proxy not attached to service"); 331 return false; 332 } 333 try { 334 service.disconnect(device); 335 return true; 336 } catch (RemoteException e) { 337 Log.e(TAG, e.toString()); 338 } 339 return false; 340 } 341 342 private final ServiceConnection mConnection = new ServiceConnection() { 343 public void onServiceConnected(ComponentName className, IBinder service) { 344 log("Proxy object connected"); 345 mService = IBluetoothPbap.Stub.asInterface(service); 346 if (mServiceListener != null) { 347 mServiceListener.onServiceConnected(BluetoothProfile.PBAP, BluetoothPbap.this); 348 } 349 } 350 351 public void onServiceDisconnected(ComponentName className) { 352 log("Proxy object disconnected"); 353 doUnbind(); 354 if (mServiceListener != null) { 355 mServiceListener.onServiceDisconnected(BluetoothProfile.PBAP); 356 } 357 } 358 }; 359 isEnabled()360 private boolean isEnabled() { 361 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 362 return false; 363 } 364 isValidDevice(BluetoothDevice device)365 private boolean isValidDevice(BluetoothDevice device) { 366 if (device == null) return false; 367 368 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 369 return false; 370 } 371 log(String msg)372 private static void log(String msg) { 373 if (DBG) { 374 Log.d(TAG, msg); 375 } 376 } 377 } 378