• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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