• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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.Manifest.permission.BLUETOOTH_CONNECT;
20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
21 
22 import static java.util.Objects.requireNonNull;
23 
24 import android.annotation.FlaggedApi;
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.annotation.RequiresNoPermission;
28 import android.annotation.RequiresPermission;
29 import android.annotation.SuppressLint;
30 import android.annotation.SystemApi;
31 import android.bluetooth.BluetoothAdapter;
32 import android.bluetooth.BluetoothDevice;
33 import android.bluetooth.IDistanceMeasurement;
34 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
35 import android.bluetooth.le.ChannelSoundingParams.CsSecurityLevel;
36 import android.content.AttributionSource;
37 import android.os.CancellationSignal;
38 import android.os.ParcelUuid;
39 import android.os.RemoteException;
40 import android.util.Log;
41 
42 import com.android.bluetooth.flags.Flags;
43 
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collections;
47 import java.util.List;
48 import java.util.Set;
49 import java.util.UUID;
50 import java.util.concurrent.ConcurrentHashMap;
51 import java.util.concurrent.Executor;
52 import java.util.stream.Collectors;
53 
54 /**
55  * This class provides methods to perform distance measurement related operations. An application
56  * can start distance measurement by using {@link
57  * DistanceMeasurementManager#startMeasurementSession}.
58  *
59  * <p>Use {@link BluetoothAdapter#getDistanceMeasurementManager()} to get an instance of {@link
60  * DistanceMeasurementManager}.
61  *
62  * @hide
63  */
64 @SystemApi
65 public final class DistanceMeasurementManager {
66     private static final String TAG = DistanceMeasurementManager.class.getSimpleName();
67 
68     private final ConcurrentHashMap<BluetoothDevice, DistanceMeasurementSession> mSessionMap =
69             new ConcurrentHashMap<>();
70     private final BluetoothAdapter mBluetoothAdapter;
71     private final AttributionSource mAttributionSource;
72     private final ParcelUuid mUuid;
73 
74     /**
75      * Use {@link BluetoothAdapter#getDistanceMeasurementManager()} instead.
76      *
77      * @hide
78      */
DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter)79     public DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter) {
80         mBluetoothAdapter = requireNonNull(bluetoothAdapter);
81         mAttributionSource = mBluetoothAdapter.getAttributionSource();
82         mUuid = new ParcelUuid(UUID.randomUUID());
83     }
84 
85     /**
86      * Get the supported methods of distance measurement.
87      *
88      * <p>This can be used to check supported methods before start distance measurement.
89      *
90      * @return a list of {@link DistanceMeasurementMethod}
91      * @hide
92      */
93     @SystemApi
94     @RequiresBluetoothConnectPermission
95     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
getSupportedMethods()96     public @NonNull List<DistanceMeasurementMethod> getSupportedMethods() {
97         final List<DistanceMeasurementMethod> supportedMethods = new ArrayList<>();
98         try {
99             IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement();
100             if (distanceMeasurement == null) {
101                 Log.e(TAG, "Distance Measurement is null");
102                 return supportedMethods;
103             }
104             return distanceMeasurement.getSupportedDistanceMeasurementMethods(mAttributionSource);
105         } catch (RemoteException e) {
106             Log.e(TAG, "Failed to get supported methods - ", e);
107         }
108         return supportedMethods;
109     }
110 
111     /**
112      * Start distance measurement and create a {@link DistanceMeasurementSession} for this
113      * operation. Once the session is started, a {@link DistanceMeasurementSession} object is
114      * provided through {@link
115      * DistanceMeasurementSession.Callback#onStarted(DistanceMeasurementSession)}. If starting a
116      * session fails, the failure is reported through {@link
117      * DistanceMeasurementSession.Callback#onStartFail(int)} with the failure reason.
118      *
119      * @param params parameters of this operation
120      * @param executor Executor to run callback
121      * @param callback callback to associate with the {@link DistanceMeasurementSession} that is
122      *     being started. The callback is registered by this function and unregistered when {@link
123      *     DistanceMeasurementSession.Callback#onStartFail(int)} or {@link
124      *     DistanceMeasurementSession .Callback#onStopped(DistanceMeasurementSession, int)}
125      * @return a CancellationSignal that may be used to cancel the starting of the {@link
126      *     DistanceMeasurementSession}
127      * @throws NullPointerException if any input parameter is null
128      * @throws IllegalStateException if the session is already registered
129      * @hide
130      */
131     @SystemApi
132     @RequiresBluetoothConnectPermission
133     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
startMeasurementSession( @onNull DistanceMeasurementParams params, @NonNull Executor executor, @NonNull DistanceMeasurementSession.Callback callback)134     public @Nullable CancellationSignal startMeasurementSession(
135             @NonNull DistanceMeasurementParams params,
136             @NonNull Executor executor,
137             @NonNull DistanceMeasurementSession.Callback callback) {
138         requireNonNull(params);
139         requireNonNull(executor);
140         requireNonNull(callback);
141         try {
142             IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement();
143             if (distanceMeasurement == null) {
144                 Log.e(TAG, "Distance Measurement is null");
145                 return null;
146             }
147             DistanceMeasurementSession session =
148                     new DistanceMeasurementSession(
149                             distanceMeasurement,
150                             mUuid,
151                             params,
152                             executor,
153                             mAttributionSource,
154                             callback);
155             CancellationSignal cancellationSignal = new CancellationSignal();
156             cancellationSignal.setOnCancelListener(() -> session.stopSession());
157 
158             if (mSessionMap.containsKey(params.getDevice())) {
159                 throw new IllegalStateException(
160                         params.getDevice().getAnonymizedAddress() + " already registered");
161             }
162 
163             mSessionMap.put(params.getDevice(), session);
164             distanceMeasurement.startDistanceMeasurement(
165                     mUuid, params, mCallbackWrapper, mAttributionSource);
166             return cancellationSignal;
167         } catch (RemoteException e) {
168             Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
169         }
170         return null;
171     }
172 
173     /**
174      * Get the maximum supported security level of channel sounding between the local device and a
175      * specific remote device.
176      *
177      * <p>See: Vol 3 Part C, Chapter 10.11.1 of
178      * https://bluetooth.com/specifications/specs/core60-html/
179      *
180      * @param remoteDevice remote device of channel sounding
181      * @return max supported security level, {@link ChannelSoundingParams#CS_SECURITY_LEVEL_UNKNOWN}
182      *     when Channel Sounding is not supported or encounters an internal error.
183      * @deprecated do not use it, this is meaningless, no alternative API.
184      * @hide
185      */
186     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING_25Q2_APIS)
187     @Deprecated
188     @SystemApi
189     @RequiresBluetoothConnectPermission
190     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
191     @CsSecurityLevel
getChannelSoundingMaxSupportedSecurityLevel(@onNull BluetoothDevice remoteDevice)192     public int getChannelSoundingMaxSupportedSecurityLevel(@NonNull BluetoothDevice remoteDevice) {
193         requireNonNull(remoteDevice);
194         final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN;
195         try {
196             IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement();
197             if (distanceMeasurement == null) {
198                 Log.e(TAG, "Distance Measurement is null");
199                 return defaultValue;
200             }
201             return distanceMeasurement.getChannelSoundingMaxSupportedSecurityLevel(
202                     remoteDevice, mAttributionSource);
203         } catch (RemoteException e) {
204             Log.e(TAG, "Failed to get supported security Level - ", e);
205         }
206         return defaultValue;
207     }
208 
209     /**
210      * Get the maximum supported security level of channel sounding of the local device.
211      *
212      * <p>See: Vol 3 Part C, Chapter 10.11.1 of
213      * https://bluetooth.com/specifications/specs/core60-html/
214      *
215      * @return max supported security level, {@link ChannelSoundingParams#CS_SECURITY_LEVEL_UNKNOWN}
216      *     when Channel Sounding is not supported or encounters an internal error.
217      * @deprecated use {@link #getChannelSoundingSupportedSecurityLevels} instead.
218      * @hide
219      */
220     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING_25Q2_APIS)
221     @Deprecated
222     @SystemApi
223     @RequiresBluetoothConnectPermission
224     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
getLocalChannelSoundingMaxSupportedSecurityLevel()225     public @CsSecurityLevel int getLocalChannelSoundingMaxSupportedSecurityLevel() {
226         final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN;
227         try {
228             IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement();
229             if (distanceMeasurement == null) {
230                 Log.e(TAG, "Distance Measurement is null");
231                 return defaultValue;
232             }
233             return distanceMeasurement.getLocalChannelSoundingMaxSupportedSecurityLevel(
234                     mAttributionSource);
235         } catch (RemoteException e) {
236             Log.e(TAG, "Failed to get supported security Level - ", e);
237         }
238         return defaultValue;
239     }
240 
241     /**
242      * Get the set of supported security levels of channel sounding.
243      *
244      * <p>See: Vol 3 Part C, Chapter 10.11.1 of
245      * https://bluetooth.com/specifications/specs/core60-html/
246      *
247      * @return the set of supported security levels, empty when encounters an internal error.
248      * @throws UnsupportedOperationException if the {@link
249      *     android.content.pm.PackageManager#FEATURE_BLUETOOTH_LE_CHANNEL_SOUNDING} is not
250      *     supported.
251      * @hide
252      */
253     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING_25Q2_APIS)
254     @SystemApi
255     @RequiresBluetoothConnectPermission
256     @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
getChannelSoundingSupportedSecurityLevels()257     public @NonNull Set<@CsSecurityLevel Integer> getChannelSoundingSupportedSecurityLevels() {
258         try {
259             IDistanceMeasurement distanceMeasurement = mBluetoothAdapter.getDistanceMeasurement();
260             if (distanceMeasurement == null) {
261                 Log.e(TAG, "Distance Measurement is null");
262                 return Collections.emptySet();
263             }
264             return Arrays.stream(
265                             distanceMeasurement.getChannelSoundingSupportedSecurityLevels(
266                                     mAttributionSource))
267                     .boxed()
268                     .collect(Collectors.toUnmodifiableSet());
269         } catch (RemoteException e) {
270             Log.e(TAG, "Failed to get supported security Level - ", e);
271         }
272         return Collections.emptySet();
273     }
274 
275     /**
276      * Clear session map. Should be called when bluetooth is down.
277      *
278      * @hide
279      */
280     @RequiresNoPermission
cleanup()281     public void cleanup() {
282         mSessionMap.clear();
283     }
284 
285     @SuppressLint("AndroidFrameworkBluetoothPermission")
286     private final IDistanceMeasurementCallback mCallbackWrapper =
287             new IDistanceMeasurementCallback.Stub() {
288                 @Override
289                 public void onStarted(BluetoothDevice device) {
290                     DistanceMeasurementSession session = mSessionMap.get(device);
291                     session.onStarted();
292                 }
293 
294                 @Override
295                 public void onStartFail(BluetoothDevice device, int reason) {
296                     DistanceMeasurementSession session = mSessionMap.get(device);
297                     session.onStartFail(reason);
298                     mSessionMap.remove(device);
299                 }
300 
301                 @Override
302                 public void onStopped(BluetoothDevice device, int reason) {
303                     DistanceMeasurementSession session = mSessionMap.get(device);
304                     session.onStopped(reason);
305                     mSessionMap.remove(device);
306                 }
307 
308                 @Override
309                 public void onResult(BluetoothDevice device, DistanceMeasurementResult result) {
310                     DistanceMeasurementSession session = mSessionMap.get(device);
311                     session.onResult(device, result);
312                 }
313             };
314 }
315