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.bluetooth.annotations.RequiresBluetoothConnectPermission; 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.content.AttributionSource; 28 import android.content.Context; 29 import android.os.Build; 30 import android.os.IBinder; 31 import android.os.RemoteException; 32 import android.util.Log; 33 34 import java.util.Arrays; 35 import java.util.Collections; 36 import java.util.List; 37 38 /** 39 * Public API for controlling the Bluetooth Pbap Service. This includes Bluetooth Phone book Access 40 * profile. BluetoothPbap is a proxy object for controlling the Bluetooth Pbap Service via IPC. 41 * 42 * <p>Creating a BluetoothPbap object will create a binding with the BluetoothPbap service. Users of 43 * this object should call close() when they are finished with the BluetoothPbap, so that this proxy 44 * object can unbind from the service. 45 * 46 * <p>This BluetoothPbap object is not immediately bound to the BluetoothPbap service. Use the 47 * ServiceListener interface to obtain a notification when it is bound, this is especially important 48 * if you wish to immediately call methods on BluetoothPbap after construction. 49 * 50 * <p>To get an instance of the BluetoothPbap class, you can call {@link 51 * BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param being 52 * {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of 53 * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}. 54 * 55 * <p>Android only supports one connected Bluetooth Pce at a time. 56 * 57 * @hide 58 */ 59 @SystemApi 60 public class BluetoothPbap implements BluetoothProfile { 61 62 private static final String TAG = "BluetoothPbap"; 63 private static final boolean DBG = false; 64 65 /** 66 * Intent used to broadcast the change in connection state of the PBAP profile. 67 * 68 * <p>This intent will have 3 extras: 69 * 70 * <ul> 71 * <li>{@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. 72 * <li>{@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. 73 * <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device. 74 * </ul> 75 * 76 * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE} can 77 * be any of {@link BluetoothProfile#STATE_DISCONNECTED}, {@link 78 * BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, {@link 79 * BluetoothProfile#STATE_DISCONNECTING}. 80 * 81 * @hide 82 */ 83 @SuppressLint("ActionValue") 84 @SystemApi 85 @RequiresBluetoothConnectPermission 86 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) 87 @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) 88 public static final String ACTION_CONNECTION_STATE_CHANGED = 89 "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; 90 91 private final AttributionSource mAttributionSource; 92 93 /** @hide */ 94 public static final int RESULT_FAILURE = 0; 95 96 /** @hide */ 97 public static final int RESULT_SUCCESS = 1; 98 99 /** 100 * Connection canceled before completion. 101 * 102 * @hide 103 */ 104 public static final int RESULT_CANCELED = 2; 105 106 private BluetoothAdapter mAdapter; 107 108 private IBluetoothPbap mService; 109 110 /** 111 * Create a BluetoothPbap proxy object. 112 * 113 * @hide 114 */ BluetoothPbap(Context context, BluetoothAdapter adapter)115 public BluetoothPbap(Context context, BluetoothAdapter adapter) { 116 mAdapter = adapter; 117 mAttributionSource = adapter.getAttributionSource(); 118 mService = null; 119 } 120 121 /** @hide */ 122 @Override onServiceConnected(IBinder service)123 public void onServiceConnected(IBinder service) { 124 mService = IBluetoothPbap.Stub.asInterface(service); 125 } 126 127 /** @hide */ 128 @Override onServiceDisconnected()129 public void onServiceDisconnected() { 130 mService = null; 131 } 132 getService()133 private IBluetoothPbap getService() { 134 return mService; 135 } 136 137 /** @hide */ 138 @Override getAdapter()139 public BluetoothAdapter getAdapter() { 140 return mAdapter; 141 } 142 143 /** 144 * {@inheritDoc} 145 * 146 * @hide 147 */ 148 @Override 149 @RequiresBluetoothConnectPermission 150 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getConnectedDevices()151 public List<BluetoothDevice> getConnectedDevices() { 152 log("getConnectedDevices()"); 153 final IBluetoothPbap service = getService(); 154 if (service == null) { 155 Log.w(TAG, "Proxy not attached to service"); 156 return Collections.emptyList(); 157 } 158 try { 159 return Attributable.setAttributionSource( 160 service.getConnectedDevices(mAttributionSource), mAttributionSource); 161 } catch (RemoteException e) { 162 Log.e(TAG, e.toString()); 163 } 164 return Collections.emptyList(); 165 } 166 167 /** 168 * {@inheritDoc} 169 * 170 * @hide 171 */ 172 @SystemApi 173 @Override 174 @RequiresBluetoothConnectPermission 175 @RequiresPermission( 176 allOf = { 177 android.Manifest.permission.BLUETOOTH_CONNECT, 178 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 179 }) getConnectionState(@onNull BluetoothDevice device)180 public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { 181 log("getConnectionState: device=" + device); 182 try { 183 final IBluetoothPbap service = getService(); 184 if (service != null && isEnabled() && isValidDevice(device)) { 185 return service.getConnectionState(device, mAttributionSource); 186 } 187 if (service == null) { 188 Log.w(TAG, "Proxy not attached to service"); 189 } 190 return BluetoothProfile.STATE_DISCONNECTED; 191 } catch (RemoteException e) { 192 Log.e(TAG, e.toString()); 193 } 194 return BluetoothProfile.STATE_DISCONNECTED; 195 } 196 197 /** 198 * {@inheritDoc} 199 * 200 * @hide 201 */ 202 @Override 203 @RequiresBluetoothConnectPermission 204 @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) getDevicesMatchingConnectionStates(int[] states)205 public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 206 log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states)); 207 final IBluetoothPbap service = getService(); 208 if (service == null) { 209 Log.w(TAG, "Proxy not attached to service"); 210 return Collections.emptyList(); 211 } 212 try { 213 return Attributable.setAttributionSource( 214 service.getDevicesMatchingConnectionStates(states, mAttributionSource), 215 mAttributionSource); 216 } catch (RemoteException e) { 217 Log.e(TAG, e.toString()); 218 } 219 return Collections.emptyList(); 220 } 221 222 /** 223 * Set connection policy of the profile and tries to disconnect it if connectionPolicy is {@link 224 * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 225 * 226 * <p>The device should already be paired. Connection policy can be one of: {@link 227 * BluetoothProfile#CONNECTION_POLICY_ALLOWED}, {@link 228 * BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, {@link 229 * BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 230 * 231 * @param device Paired bluetooth device 232 * @param connectionPolicy is the connection policy to set to for this profile 233 * @return true if connectionPolicy is set, false on error 234 * @hide 235 */ 236 @SystemApi 237 @RequiresBluetoothConnectPermission 238 @RequiresPermission( 239 allOf = { 240 android.Manifest.permission.BLUETOOTH_CONNECT, 241 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 242 }) setConnectionPolicy( @onNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy)243 public boolean setConnectionPolicy( 244 @NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { 245 if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); 246 try { 247 final IBluetoothPbap service = getService(); 248 if (service != null && isEnabled() && isValidDevice(device)) { 249 if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN 250 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 251 return false; 252 } 253 return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); 254 } 255 if (service == null) Log.w(TAG, "Proxy not attached to service"); 256 return false; 257 } catch (RemoteException e) { 258 Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); 259 return false; 260 } 261 } 262 263 /** 264 * Disconnects the current Pbap client (PCE). Currently this call blocks, it may soon be made 265 * asynchronous. Returns false if this proxy object is not currently connected to the Pbap 266 * service. 267 * 268 * @hide 269 */ 270 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 271 @RequiresBluetoothConnectPermission 272 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) disconnect(BluetoothDevice device)273 public boolean disconnect(BluetoothDevice device) { 274 log("disconnect()"); 275 final IBluetoothPbap service = getService(); 276 if (service == null) { 277 Log.w(TAG, "Proxy not attached to service"); 278 return false; 279 } 280 try { 281 service.disconnect(device, mAttributionSource); 282 return true; 283 } catch (RemoteException e) { 284 Log.e(TAG, e.toString()); 285 } 286 return false; 287 } 288 isEnabled()289 private boolean isEnabled() { 290 if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; 291 return false; 292 } 293 isValidDevice(BluetoothDevice device)294 private boolean isValidDevice(BluetoothDevice device) { 295 if (device == null) return false; 296 297 if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; 298 return false; 299 } 300 log(String msg)301 private static void log(String msg) { 302 if (DBG) { 303 Log.d(TAG, msg); 304 } 305 } 306 } 307