/* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.bluetooth.le; import static android.bluetooth.le.BluetoothLeUtils.getSyncTimeout; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.IBluetoothGatt; import android.bluetooth.IBluetoothManager; import android.content.AttributionSource; import android.os.CancellationSignal; import android.os.ParcelUuid; import android.os.RemoteException; import android.util.Log; import com.android.modules.utils.SynchronousResultReceiver; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; /** * This class provides methods to perform distance measurement related * operations. An application can start distance measurement by using * {@link DistanceMeasurementManager#startMeasurementSession}. *

* Use {@link BluetoothAdapter#getDistanceMeasurementManager()} to get an instance of * {@link DistanceMeasurementManager}. * * @hide */ @SystemApi public final class DistanceMeasurementManager { private static final String TAG = "DistanceMeasurementManager"; private final ConcurrentHashMap mSessionMap = new ConcurrentHashMap<>(); private final BluetoothAdapter mBluetoothAdapter; private final IBluetoothManager mBluetoothManager; private final AttributionSource mAttributionSource; private final ParcelUuid mUuid; /** * Use {@link BluetoothAdapter#getDistanceMeasurementManager()} instead. * * @hide */ public DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter) { mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); mAttributionSource = mBluetoothAdapter.getAttributionSource(); mUuid = new ParcelUuid(UUID.randomUUID()); } /** * Get the supported methods of distance measurement. * *

This can be used to check supported methods before start distance measurement. * * @return a list of {@link DistanceMeasurementMethod} * * @hide */ @SystemApi @RequiresPermission(allOf = { android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, }) public @NonNull List getSupportedMethods() { final ArrayList supportedMethods = new ArrayList(); try { IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt(); if (gatt == null) { Log.e(TAG, "Bluetooth GATT is null"); return supportedMethods; } final SynchronousResultReceiver> recv = SynchronousResultReceiver.get(); gatt.getSupportedDistanceMeasurementMethods(mAttributionSource, recv); return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(new ArrayList<>()); } catch (TimeoutException | RemoteException e) { Log.e(TAG, "Failed to get supported methods - ", e); } return supportedMethods; } /** * Start distance measurement and create a {@link DistanceMeasurementSession} for this * operation. Once the session is started, a {@link DistanceMeasurementSession} object is * provided through * {@link DistanceMeasurementSession.Callback#onStarted(DistanceMeasurementSession)}. * If starting a session fails, the failure is reported through * {@link DistanceMeasurementSession.Callback#onStartFail(int)} with the failure reason. * * @param params parameters of this operation * @param executor Executor to run callback * @param callback callback to associate with the * {@link DistanceMeasurementSession} that is being started. The callback is * registered by this function and unregisted when * {@link DistanceMeasurementSession.Callback#onStartFail(int)} or * {@link DistanceMeasurementSession * .Callback#onStopped(DistanceMeasurementSession, int)} * @return a CancellationSignal that may be used to cancel the starting of the * {@link DistanceMeasurementSession} * @throws NullPointerException if any input parameter is null * @throws IllegalStateException if the session is already registered * @hide */ @SystemApi @Nullable @RequiresPermission(allOf = { android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, }) public CancellationSignal startMeasurementSession( @NonNull DistanceMeasurementParams params, @NonNull Executor executor, @NonNull DistanceMeasurementSession.Callback callback) { Objects.requireNonNull(params, "params is null"); Objects.requireNonNull(executor, "executor is null"); Objects.requireNonNull(callback, "callback is null"); try { IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt(); if (gatt == null) { Log.e(TAG, "Bluetooth GATT is null"); return null; } DistanceMeasurementSession session = new DistanceMeasurementSession(gatt, mUuid, params, executor, mAttributionSource, callback); CancellationSignal cancellationSignal = new CancellationSignal(); cancellationSignal.setOnCancelListener(() -> session.stopSession()); if (mSessionMap.containsKey(params.getDevice())) { throw new IllegalStateException(params.getDevice().getAnonymizedAddress() + " already registered"); } mSessionMap.put(params.getDevice(), session); final SynchronousResultReceiver recv = SynchronousResultReceiver.get(); gatt.startDistanceMeasurement(mUuid, params, mCallbackWrapper, mAttributionSource, recv); recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null); return cancellationSignal; } catch (TimeoutException e) { Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable())); return null; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } @SuppressLint("AndroidFrameworkBluetoothPermission") private final IDistanceMeasurementCallback mCallbackWrapper = new IDistanceMeasurementCallback.Stub() { @Override public void onStarted(BluetoothDevice device) { DistanceMeasurementSession session = mSessionMap.get(device); session.onStarted(); } @Override public void onStartFail(BluetoothDevice device, int reason) { DistanceMeasurementSession session = mSessionMap.get(device); session.onStartFail(reason); mSessionMap.remove(device); } @Override public void onStopped(BluetoothDevice device, int reason) { DistanceMeasurementSession session = mSessionMap.get(device); session.onStopped(reason); mSessionMap.remove(device); } @Override public void onResult(BluetoothDevice device, DistanceMeasurementResult result) { DistanceMeasurementSession session = mSessionMap.get(device); session.onResult(device, result); } }; }