1 /* 2 * Copyright (C) 2022 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.le; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 21 22 import static java.util.Objects.requireNonNull; 23 24 import android.annotation.FlaggedApi; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.RequiresNoPermission; 28 import android.annotation.RequiresPermission; 29 import android.annotation.SuppressLint; 30 import android.annotation.SystemApi; 31 import android.bluetooth.BluetoothAdapter; 32 import android.bluetooth.BluetoothDevice; 33 import android.bluetooth.IDistanceMeasurement; 34 import android.bluetooth.annotations.RequiresBluetoothConnectPermission; 35 import android.bluetooth.le.ChannelSoundingParams.CsSecurityLevel; 36 import android.content.AttributionSource; 37 import android.os.CancellationSignal; 38 import android.os.ParcelUuid; 39 import android.os.RemoteException; 40 import android.util.Log; 41 42 import com.android.bluetooth.flags.Flags; 43 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collections; 47 import java.util.List; 48 import java.util.Set; 49 import java.util.UUID; 50 import java.util.concurrent.ConcurrentHashMap; 51 import java.util.concurrent.Executor; 52 import java.util.stream.Collectors; 53 54 /** 55 * This class provides methods to perform distance measurement related operations. An application 56 * can start distance measurement by using {@link 57 * DistanceMeasurementManager#startMeasurementSession}. 58 * 59 * <p>Use {@link BluetoothAdapter#getDistanceMeasurementManager()} to get an instance of {@link 60 * DistanceMeasurementManager}. 61 * 62 * @hide 63 */ 64 @SystemApi 65 public final class DistanceMeasurementManager { 66 private static final String TAG = DistanceMeasurementManager.class.getSimpleName(); 67 68 private final ConcurrentHashMap<BluetoothDevice, DistanceMeasurementSession> mSessionMap = 69 new ConcurrentHashMap<>(); 70 private final BluetoothAdapter mBluetoothAdapter; 71 private final AttributionSource mAttributionSource; 72 private final ParcelUuid mUuid; 73 74 /** 75 * Use {@link BluetoothAdapter#getDistanceMeasurementManager()} instead. 76 * 77 * @hide 78 */ DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter)79 public DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter) { 80 mBluetoothAdapter = requireNonNull(bluetoothAdapter); 81 mAttributionSource = mBluetoothAdapter.getAttributionSource(); 82 mUuid = new ParcelUuid(UUID.randomUUID()); 83 } 84 85 /** 86 * Get the supported methods of distance measurement. 87 * 88 * <p>This can be used to check supported methods before start distance measurement. 89 * 90 * @return a list of {@link DistanceMeasurementMethod} 91 * @hide 92 */ 93 @SystemApi 94 @RequiresBluetoothConnectPermission 95 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) getSupportedMethods()96 public @NonNull List<DistanceMeasurementMethod> getSupportedMethods() { 97 final List<DistanceMeasurementMethod> supportedMethods = new ArrayList<>(); 98 try { 99 IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); 100 if (distanceMeasurement == null) { 101 Log.e(TAG, "Distance Measurement is null"); 102 return supportedMethods; 103 } 104 return distanceMeasurement.getSupportedDistanceMeasurementMethods(mAttributionSource); 105 } catch (RemoteException e) { 106 Log.e(TAG, "Failed to get supported methods - ", e); 107 } 108 return supportedMethods; 109 } 110 111 /** 112 * Start distance measurement and create a {@link DistanceMeasurementSession} for this 113 * operation. Once the session is started, a {@link DistanceMeasurementSession} object is 114 * provided through {@link 115 * DistanceMeasurementSession.Callback#onStarted(DistanceMeasurementSession)}. If starting a 116 * session fails, the failure is reported through {@link 117 * DistanceMeasurementSession.Callback#onStartFail(int)} with the failure reason. 118 * 119 * @param params parameters of this operation 120 * @param executor Executor to run callback 121 * @param callback callback to associate with the {@link DistanceMeasurementSession} that is 122 * being started. The callback is registered by this function and unregistered when {@link 123 * DistanceMeasurementSession.Callback#onStartFail(int)} or {@link 124 * DistanceMeasurementSession .Callback#onStopped(DistanceMeasurementSession, int)} 125 * @return a CancellationSignal that may be used to cancel the starting of the {@link 126 * DistanceMeasurementSession} 127 * @throws NullPointerException if any input parameter is null 128 * @throws IllegalStateException if the session is already registered 129 * @hide 130 */ 131 @SystemApi 132 @RequiresBluetoothConnectPermission 133 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) startMeasurementSession( @onNull DistanceMeasurementParams params, @NonNull Executor executor, @NonNull DistanceMeasurementSession.Callback callback)134 public @Nullable CancellationSignal startMeasurementSession( 135 @NonNull DistanceMeasurementParams params, 136 @NonNull Executor executor, 137 @NonNull DistanceMeasurementSession.Callback callback) { 138 requireNonNull(params); 139 requireNonNull(executor); 140 requireNonNull(callback); 141 try { 142 IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); 143 if (distanceMeasurement == null) { 144 Log.e(TAG, "Distance Measurement is null"); 145 return null; 146 } 147 DistanceMeasurementSession session = 148 new DistanceMeasurementSession( 149 distanceMeasurement, 150 mUuid, 151 params, 152 executor, 153 mAttributionSource, 154 callback); 155 CancellationSignal cancellationSignal = new CancellationSignal(); 156 cancellationSignal.setOnCancelListener(() -> session.stopSession()); 157 158 if (mSessionMap.containsKey(params.getDevice())) { 159 throw new IllegalStateException( 160 params.getDevice().getAnonymizedAddress() + " already registered"); 161 } 162 163 mSessionMap.put(params.getDevice(), session); 164 distanceMeasurement.startDistanceMeasurement( 165 mUuid, params, mCallbackWrapper, mAttributionSource); 166 return cancellationSignal; 167 } catch (RemoteException e) { 168 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 169 } 170 return null; 171 } 172 173 /** 174 * Get the maximum supported security level of channel sounding between the local device and a 175 * specific remote device. 176 * 177 * <p>See: Vol 3 Part C, Chapter 10.11.1 of 178 * https://bluetooth.com/specifications/specs/core60-html/ 179 * 180 * @param remoteDevice remote device of channel sounding 181 * @return max supported security level, {@link ChannelSoundingParams#CS_SECURITY_LEVEL_UNKNOWN} 182 * when Channel Sounding is not supported or encounters an internal error. 183 * @deprecated do not use it, this is meaningless, no alternative API. 184 * @hide 185 */ 186 @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING_25Q2_APIS) 187 @Deprecated 188 @SystemApi 189 @RequiresBluetoothConnectPermission 190 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) 191 @CsSecurityLevel getChannelSoundingMaxSupportedSecurityLevel(@onNull BluetoothDevice remoteDevice)192 public int getChannelSoundingMaxSupportedSecurityLevel(@NonNull BluetoothDevice remoteDevice) { 193 requireNonNull(remoteDevice); 194 final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; 195 try { 196 IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); 197 if (distanceMeasurement == null) { 198 Log.e(TAG, "Distance Measurement is null"); 199 return defaultValue; 200 } 201 return distanceMeasurement.getChannelSoundingMaxSupportedSecurityLevel( 202 remoteDevice, mAttributionSource); 203 } catch (RemoteException e) { 204 Log.e(TAG, "Failed to get supported security Level - ", e); 205 } 206 return defaultValue; 207 } 208 209 /** 210 * Get the maximum supported security level of channel sounding of the local device. 211 * 212 * <p>See: Vol 3 Part C, Chapter 10.11.1 of 213 * https://bluetooth.com/specifications/specs/core60-html/ 214 * 215 * @return max supported security level, {@link ChannelSoundingParams#CS_SECURITY_LEVEL_UNKNOWN} 216 * when Channel Sounding is not supported or encounters an internal error. 217 * @deprecated use {@link #getChannelSoundingSupportedSecurityLevels} instead. 218 * @hide 219 */ 220 @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING_25Q2_APIS) 221 @Deprecated 222 @SystemApi 223 @RequiresBluetoothConnectPermission 224 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) getLocalChannelSoundingMaxSupportedSecurityLevel()225 public @CsSecurityLevel int getLocalChannelSoundingMaxSupportedSecurityLevel() { 226 final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; 227 try { 228 IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); 229 if (distanceMeasurement == null) { 230 Log.e(TAG, "Distance Measurement is null"); 231 return defaultValue; 232 } 233 return distanceMeasurement.getLocalChannelSoundingMaxSupportedSecurityLevel( 234 mAttributionSource); 235 } catch (RemoteException e) { 236 Log.e(TAG, "Failed to get supported security Level - ", e); 237 } 238 return defaultValue; 239 } 240 241 /** 242 * Get the set of supported security levels of channel sounding. 243 * 244 * <p>See: Vol 3 Part C, Chapter 10.11.1 of 245 * https://bluetooth.com/specifications/specs/core60-html/ 246 * 247 * @return the set of supported security levels, empty when encounters an internal error. 248 * @throws UnsupportedOperationException if the {@link 249 * android.content.pm.PackageManager#FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING} is not 250 * supported. 251 * @hide 252 */ 253 @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING_25Q2_APIS) 254 @SystemApi 255 @RequiresBluetoothConnectPermission 256 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) getChannelSoundingSupportedSecurityLevels()257 public @NonNull Set<@CsSecurityLevel Integer> getChannelSoundingSupportedSecurityLevels() { 258 try { 259 IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement(); 260 if (distanceMeasurement == null) { 261 Log.e(TAG, "Distance Measurement is null"); 262 return Collections.emptySet(); 263 } 264 return Arrays.stream( 265 distanceMeasurement.getChannelSoundingSupportedSecurityLevels( 266 mAttributionSource)) 267 .boxed() 268 .collect(Collectors.toUnmodifiableSet()); 269 } catch (RemoteException e) { 270 Log.e(TAG, "Failed to get supported security Level - ", e); 271 } 272 return Collections.emptySet(); 273 } 274 275 /** 276 * Clear session map. Should be called when bluetooth is down. 277 * 278 * @hide 279 */ 280 @RequiresNoPermission cleanup()281 public void cleanup() { 282 mSessionMap.clear(); 283 } 284 285 @SuppressLint("AndroidFrameworkBluetoothPermission") 286 private final IDistanceMeasurementCallback mCallbackWrapper = 287 new IDistanceMeasurementCallback.Stub() { 288 @Override 289 public void onStarted(BluetoothDevice device) { 290 DistanceMeasurementSession session = mSessionMap.get(device); 291 session.onStarted(); 292 } 293 294 @Override 295 public void onStartFail(BluetoothDevice device, int reason) { 296 DistanceMeasurementSession session = mSessionMap.get(device); 297 session.onStartFail(reason); 298 mSessionMap.remove(device); 299 } 300 301 @Override 302 public void onStopped(BluetoothDevice device, int reason) { 303 DistanceMeasurementSession session = mSessionMap.get(device); 304 session.onStopped(reason); 305 mSessionMap.remove(device); 306 } 307 308 @Override 309 public void onResult(BluetoothDevice device, DistanceMeasurementResult result) { 310 DistanceMeasurementSession session = mSessionMap.get(device); 311 session.onResult(device, result); 312 } 313 }; 314 } 315