1 /* 2 * Copyright 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 android.annotation.FlaggedApi; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SuppressLint; 24 import android.annotation.SystemApi; 25 import android.bluetooth.BluetoothAdapter; 26 import android.bluetooth.BluetoothDevice; 27 import android.bluetooth.IBluetoothGatt; 28 import android.bluetooth.le.ChannelSoundingParams.CsSecurityLevel; 29 import android.content.AttributionSource; 30 import android.os.CancellationSignal; 31 import android.os.ParcelUuid; 32 import android.os.RemoteException; 33 import android.util.Log; 34 35 import com.android.bluetooth.flags.Flags; 36 37 import java.util.ArrayList; 38 import java.util.List; 39 import java.util.Objects; 40 import java.util.UUID; 41 import java.util.concurrent.ConcurrentHashMap; 42 import java.util.concurrent.Executor; 43 44 /** 45 * This class provides methods to perform distance measurement related operations. An application 46 * can start distance measurement by using {@link 47 * DistanceMeasurementManager#startMeasurementSession}. 48 * 49 * <p>Use {@link BluetoothAdapter#getDistanceMeasurementManager()} to get an instance of {@link 50 * DistanceMeasurementManager}. 51 * 52 * @hide 53 */ 54 @SystemApi 55 public final class DistanceMeasurementManager { 56 private static final String TAG = "DistanceMeasurementManager"; 57 58 private final ConcurrentHashMap<BluetoothDevice, DistanceMeasurementSession> mSessionMap = 59 new ConcurrentHashMap<>(); 60 private final BluetoothAdapter mBluetoothAdapter; 61 private final AttributionSource mAttributionSource; 62 private final ParcelUuid mUuid; 63 64 /** 65 * Use {@link BluetoothAdapter#getDistanceMeasurementManager()} instead. 66 * 67 * @hide 68 */ DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter)69 public DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter) { 70 mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); 71 mAttributionSource = mBluetoothAdapter.getAttributionSource(); 72 mUuid = new ParcelUuid(UUID.randomUUID()); 73 } 74 75 /** 76 * Get the supported methods of distance measurement. 77 * 78 * <p>This can be used to check supported methods before start distance measurement. 79 * 80 * @return a list of {@link DistanceMeasurementMethod} 81 * @hide 82 */ 83 @SystemApi 84 @RequiresPermission( 85 allOf = { 86 android.Manifest.permission.BLUETOOTH_CONNECT, 87 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 88 }) getSupportedMethods()89 public @NonNull List<DistanceMeasurementMethod> getSupportedMethods() { 90 final List<DistanceMeasurementMethod> supportedMethods = new ArrayList<>(); 91 try { 92 IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); 93 if (gatt == null) { 94 Log.e(TAG, "Bluetooth GATT is null"); 95 return supportedMethods; 96 } 97 return gatt.getSupportedDistanceMeasurementMethods(mAttributionSource); 98 } catch (RemoteException e) { 99 Log.e(TAG, "Failed to get supported methods - ", e); 100 } 101 return supportedMethods; 102 } 103 104 /** 105 * Start distance measurement and create a {@link DistanceMeasurementSession} for this 106 * operation. Once the session is started, a {@link DistanceMeasurementSession} object is 107 * provided through {@link 108 * DistanceMeasurementSession.Callback#onStarted(DistanceMeasurementSession)}. If starting a 109 * session fails, the failure is reported through {@link 110 * DistanceMeasurementSession.Callback#onStartFail(int)} with the failure reason. 111 * 112 * @param params parameters of this operation 113 * @param executor Executor to run callback 114 * @param callback callback to associate with the {@link DistanceMeasurementSession} that is 115 * being started. The callback is registered by this function and unregistered when {@link 116 * DistanceMeasurementSession.Callback#onStartFail(int)} or {@link 117 * DistanceMeasurementSession .Callback#onStopped(DistanceMeasurementSession, int)} 118 * @return a CancellationSignal that may be used to cancel the starting of the {@link 119 * DistanceMeasurementSession} 120 * @throws NullPointerException if any input parameter is null 121 * @throws IllegalStateException if the session is already registered 122 * @hide 123 */ 124 @SystemApi 125 @Nullable 126 @RequiresPermission( 127 allOf = { 128 android.Manifest.permission.BLUETOOTH_CONNECT, 129 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 130 }) startMeasurementSession( @onNull DistanceMeasurementParams params, @NonNull Executor executor, @NonNull DistanceMeasurementSession.Callback callback)131 public CancellationSignal startMeasurementSession( 132 @NonNull DistanceMeasurementParams params, 133 @NonNull Executor executor, 134 @NonNull DistanceMeasurementSession.Callback callback) { 135 Objects.requireNonNull(params, "params is null"); 136 Objects.requireNonNull(executor, "executor is null"); 137 Objects.requireNonNull(callback, "callback is null"); 138 try { 139 IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); 140 if (gatt == null) { 141 Log.e(TAG, "Bluetooth GATT is null"); 142 return null; 143 } 144 DistanceMeasurementSession session = 145 new DistanceMeasurementSession( 146 gatt, mUuid, params, executor, mAttributionSource, callback); 147 CancellationSignal cancellationSignal = new CancellationSignal(); 148 cancellationSignal.setOnCancelListener(() -> session.stopSession()); 149 150 if (mSessionMap.containsKey(params.getDevice())) { 151 throw new IllegalStateException( 152 params.getDevice().getAnonymizedAddress() + " already registered"); 153 } 154 155 mSessionMap.put(params.getDevice(), session); 156 gatt.startDistanceMeasurement(mUuid, params, mCallbackWrapper, mAttributionSource); 157 return cancellationSignal; 158 } catch (RemoteException e) { 159 throw e.rethrowAsRuntimeException(); 160 } 161 } 162 163 /** 164 * Get the maximum supported security level of channel sounding between the local device and a 165 * specific remote device. 166 * 167 * <p>See: https://bluetooth.com/specifications/specs/channel-sounding-cr-pr/ 168 * 169 * @param remoteDevice remote device of channel sounding 170 * @return max supported security level, {@link ChannelSoundingParams#CS_SECURITY_LEVEL_UNKNOWN} 171 * when Channel Sounding is not supported or encounters an internal error. 172 * @hide 173 */ 174 @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING) 175 @SystemApi 176 @CsSecurityLevel 177 @RequiresPermission( 178 allOf = { 179 android.Manifest.permission.BLUETOOTH_CONNECT, 180 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 181 }) getChannelSoundingMaxSupportedSecurityLevel(@onNull BluetoothDevice remoteDevice)182 public int getChannelSoundingMaxSupportedSecurityLevel(@NonNull BluetoothDevice remoteDevice) { 183 Objects.requireNonNull(remoteDevice, "remote device is null"); 184 final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; 185 try { 186 IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); 187 if (gatt == null) { 188 Log.e(TAG, "Bluetooth GATT is null"); 189 return defaultValue; 190 } 191 return gatt.getChannelSoundingMaxSupportedSecurityLevel( 192 remoteDevice, mAttributionSource); 193 } catch (RemoteException e) { 194 Log.e(TAG, "Failed to get supported security Level - ", e); 195 } 196 return defaultValue; 197 } 198 199 /** 200 * Get the maximum supported security level of channel sounding of the local device. 201 * 202 * <p>See: https://bluetooth.com/specifications/specs/channel-sounding-cr-pr/ 203 * 204 * @return max supported security level, {@link ChannelSoundingParams#CS_SECURITY_LEVEL_UNKNOWN} 205 * when Channel Sounding is not supported or encounters an internal error. 206 * @hide 207 */ 208 @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING) 209 @SystemApi 210 @CsSecurityLevel 211 @RequiresPermission( 212 allOf = { 213 android.Manifest.permission.BLUETOOTH_CONNECT, 214 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 215 }) getLocalChannelSoundingMaxSupportedSecurityLevel()216 public int getLocalChannelSoundingMaxSupportedSecurityLevel() { 217 final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN; 218 try { 219 IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt(); 220 if (gatt == null) { 221 Log.e(TAG, "Bluetooth GATT is null"); 222 return defaultValue; 223 } 224 return gatt.getLocalChannelSoundingMaxSupportedSecurityLevel(mAttributionSource); 225 } catch (RemoteException e) { 226 Log.e(TAG, "Failed to get supported security Level - ", e); 227 } 228 return defaultValue; 229 } 230 231 @SuppressLint("AndroidFrameworkBluetoothPermission") 232 private final IDistanceMeasurementCallback mCallbackWrapper = 233 new IDistanceMeasurementCallback.Stub() { 234 @Override 235 public void onStarted(BluetoothDevice device) { 236 DistanceMeasurementSession session = mSessionMap.get(device); 237 session.onStarted(); 238 } 239 240 @Override 241 public void onStartFail(BluetoothDevice device, int reason) { 242 DistanceMeasurementSession session = mSessionMap.get(device); 243 session.onStartFail(reason); 244 mSessionMap.remove(device); 245 } 246 247 @Override 248 public void onStopped(BluetoothDevice device, int reason) { 249 DistanceMeasurementSession session = mSessionMap.get(device); 250 session.onStopped(reason); 251 mSessionMap.remove(device); 252 } 253 254 @Override 255 public void onResult(BluetoothDevice device, DistanceMeasurementResult result) { 256 DistanceMeasurementSession session = mSessionMap.get(device); 257 session.onResult(device, result); 258 } 259 }; 260 } 261