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 static android.bluetooth.le.BluetoothLeUtils.getSyncTimeout; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SuppressLint; 25 import android.annotation.SystemApi; 26 import android.bluetooth.BluetoothAdapter; 27 import android.bluetooth.BluetoothDevice; 28 import android.bluetooth.IBluetoothGatt; 29 import android.bluetooth.IBluetoothManager; 30 import android.content.AttributionSource; 31 import android.os.CancellationSignal; 32 import android.os.ParcelUuid; 33 import android.os.RemoteException; 34 import android.util.Log; 35 36 import com.android.modules.utils.SynchronousResultReceiver; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.UUID; 42 import java.util.concurrent.ConcurrentHashMap; 43 import java.util.concurrent.Executor; 44 import java.util.concurrent.TimeoutException; 45 46 /** 47 * This class provides methods to perform distance measurement related 48 * operations. An application can start distance measurement by using 49 * {@link DistanceMeasurementManager#startMeasurementSession}. 50 * <p> 51 * Use {@link BluetoothAdapter#getDistanceMeasurementManager()} to get an instance of 52 * {@link DistanceMeasurementManager}. 53 * 54 * @hide 55 */ 56 @SystemApi 57 public final class DistanceMeasurementManager { 58 private static final String TAG = "DistanceMeasurementManager"; 59 60 private final ConcurrentHashMap<BluetoothDevice, DistanceMeasurementSession> mSessionMap = 61 new ConcurrentHashMap<>(); 62 private final BluetoothAdapter mBluetoothAdapter; 63 private final IBluetoothManager mBluetoothManager; 64 private final AttributionSource mAttributionSource; 65 private final ParcelUuid mUuid; 66 67 /** 68 * Use {@link BluetoothAdapter#getDistanceMeasurementManager()} instead. 69 * 70 * @hide 71 */ DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter)72 public DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter) { 73 mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); 74 mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); 75 mAttributionSource = mBluetoothAdapter.getAttributionSource(); 76 mUuid = new ParcelUuid(UUID.randomUUID()); 77 } 78 79 /** 80 * Get the supported methods of distance measurement. 81 * 82 * <p> This can be used to check supported methods before start distance measurement. 83 * 84 * @return a list of {@link DistanceMeasurementMethod} 85 * 86 * @hide 87 */ 88 @SystemApi 89 @RequiresPermission(allOf = { 90 android.Manifest.permission.BLUETOOTH_CONNECT, 91 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 92 }) getSupportedMethods()93 public @NonNull List<DistanceMeasurementMethod> getSupportedMethods() { 94 final ArrayList<DistanceMeasurementMethod> supportedMethods = 95 new ArrayList<DistanceMeasurementMethod>(); 96 try { 97 IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt(); 98 if (gatt == null) { 99 Log.e(TAG, "Bluetooth GATT is null"); 100 return supportedMethods; 101 } 102 final SynchronousResultReceiver<List<DistanceMeasurementMethod>> recv = 103 SynchronousResultReceiver.get(); 104 gatt.getSupportedDistanceMeasurementMethods(mAttributionSource, recv); 105 return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(new ArrayList<>()); 106 } catch (TimeoutException | RemoteException e) { 107 Log.e(TAG, "Failed to get supported methods - ", e); 108 } 109 return supportedMethods; 110 } 111 112 /** 113 * Start distance measurement and create a {@link DistanceMeasurementSession} for this 114 * operation. Once the session is started, a {@link DistanceMeasurementSession} object is 115 * provided through 116 * {@link DistanceMeasurementSession.Callback#onStarted(DistanceMeasurementSession)}. 117 * If starting a session fails, the failure is reported through 118 * {@link DistanceMeasurementSession.Callback#onStartFail(int)} with the failure reason. 119 * 120 * @param params parameters of this operation 121 * @param executor Executor to run callback 122 * @param callback callback to associate with the 123 * {@link DistanceMeasurementSession} that is being started. The callback is 124 * registered by this function and unregisted when 125 * {@link DistanceMeasurementSession.Callback#onStartFail(int)} or 126 * {@link DistanceMeasurementSession 127 * .Callback#onStopped(DistanceMeasurementSession, int)} 128 * @return a CancellationSignal that may be used to cancel the starting of the 129 * {@link DistanceMeasurementSession} 130 * @throws NullPointerException if any input parameter is null 131 * @throws IllegalStateException if the session is already registered 132 * @hide 133 */ 134 @SystemApi 135 @Nullable 136 @RequiresPermission(allOf = { 137 android.Manifest.permission.BLUETOOTH_CONNECT, 138 android.Manifest.permission.BLUETOOTH_PRIVILEGED, 139 }) startMeasurementSession( @onNull DistanceMeasurementParams params, @NonNull Executor executor, @NonNull DistanceMeasurementSession.Callback callback)140 public CancellationSignal startMeasurementSession( 141 @NonNull DistanceMeasurementParams params, 142 @NonNull Executor executor, 143 @NonNull DistanceMeasurementSession.Callback callback) { 144 Objects.requireNonNull(params, "params is null"); 145 Objects.requireNonNull(executor, "executor is null"); 146 Objects.requireNonNull(callback, "callback is null"); 147 try { 148 IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt(); 149 if (gatt == null) { 150 Log.e(TAG, "Bluetooth GATT is null"); 151 return null; 152 } 153 DistanceMeasurementSession session = new DistanceMeasurementSession(gatt, mUuid, 154 params, executor, mAttributionSource, callback); 155 CancellationSignal cancellationSignal = new CancellationSignal(); 156 cancellationSignal.setOnCancelListener(() -> session.stopSession()); 157 158 if (mSessionMap.containsKey(params.getDevice())) { 159 throw new IllegalStateException(params.getDevice().getAnonymizedAddress() 160 + " already registered"); 161 } 162 163 mSessionMap.put(params.getDevice(), session); 164 final SynchronousResultReceiver recv = SynchronousResultReceiver.get(); 165 gatt.startDistanceMeasurement(mUuid, params, mCallbackWrapper, mAttributionSource, 166 recv); 167 recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); 168 return cancellationSignal; 169 } catch (TimeoutException e) { 170 Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); 171 return null; 172 } catch (RemoteException e) { 173 throw e.rethrowFromSystemServer(); 174 } 175 } 176 177 @SuppressLint("AndroidFrameworkBluetoothPermission") 178 private final IDistanceMeasurementCallback mCallbackWrapper = 179 new IDistanceMeasurementCallback.Stub() { 180 @Override 181 public void onStarted(BluetoothDevice device) { 182 DistanceMeasurementSession session = mSessionMap.get(device); 183 session.onStarted(); 184 } 185 186 @Override 187 public void onStartFail(BluetoothDevice device, int reason) { 188 DistanceMeasurementSession session = mSessionMap.get(device); 189 session.onStartFail(reason); 190 mSessionMap.remove(device); 191 } 192 193 @Override 194 public void onStopped(BluetoothDevice device, int reason) { 195 DistanceMeasurementSession session = mSessionMap.get(device); 196 session.onStopped(reason); 197 mSessionMap.remove(device); 198 } 199 200 @Override 201 public void onResult(BluetoothDevice device, DistanceMeasurementResult result) { 202 DistanceMeasurementSession session = mSessionMap.get(device); 203 session.onResult(device, result); 204 } 205 }; 206 } 207