blob: eb0f9b3899bb4d64071b9a0662de480a6f540086 [file] [log] [blame]
/*
* 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}.
* <p>
* 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<BluetoothDevice, DistanceMeasurementSession> 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.
*
* <p> 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<DistanceMeasurementMethod> getSupportedMethods() {
final ArrayList<DistanceMeasurementMethod> supportedMethods =
new ArrayList<DistanceMeasurementMethod>();
try {
IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt();
if (gatt == null) {
Log.e(TAG, "Bluetooth GATT is null");
return supportedMethods;
}
final SynchronousResultReceiver<List<DistanceMeasurementMethod>> 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);
}
};
}