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 android.annotation.FlaggedApi;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SuppressLint;
24 import android.annotation.SystemApi;
25 import android.bluetooth.BluetoothAdapter;
26 import android.bluetooth.BluetoothDevice;
27 import android.bluetooth.IBluetoothGatt;
28 import android.bluetooth.le.ChannelSoundingParams.CsSecurityLevel;
29 import android.content.AttributionSource;
30 import android.os.CancellationSignal;
31 import android.os.ParcelUuid;
32 import android.os.RemoteException;
33 import android.util.Log;
34 
35 import com.android.bluetooth.flags.Flags;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Objects;
40 import java.util.UUID;
41 import java.util.concurrent.ConcurrentHashMap;
42 import java.util.concurrent.Executor;
43 
44 /**
45  * This class provides methods to perform distance measurement related operations. An application
46  * can start distance measurement by using {@link
47  * DistanceMeasurementManager#startMeasurementSession}.
48  *
49  * <p>Use {@link BluetoothAdapter#getDistanceMeasurementManager()} to get an instance of {@link
50  * DistanceMeasurementManager}.
51  *
52  * @hide
53  */
54 @SystemApi
55 public final class DistanceMeasurementManager {
56     private static final String TAG = "DistanceMeasurementManager";
57 
58     private final ConcurrentHashMap<BluetoothDevice, DistanceMeasurementSession> mSessionMap =
59             new ConcurrentHashMap<>();
60     private final BluetoothAdapter mBluetoothAdapter;
61     private final AttributionSource mAttributionSource;
62     private final ParcelUuid mUuid;
63 
64     /**
65      * Use {@link BluetoothAdapter#getDistanceMeasurementManager()} instead.
66      *
67      * @hide
68      */
DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter)69     public DistanceMeasurementManager(BluetoothAdapter bluetoothAdapter) {
70         mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
71         mAttributionSource = mBluetoothAdapter.getAttributionSource();
72         mUuid = new ParcelUuid(UUID.randomUUID());
73     }
74 
75     /**
76      * Get the supported methods of distance measurement.
77      *
78      * <p>This can be used to check supported methods before start distance measurement.
79      *
80      * @return a list of {@link DistanceMeasurementMethod}
81      * @hide
82      */
83     @SystemApi
84     @RequiresPermission(
85             allOf = {
86                 android.Manifest.permission.BLUETOOTH_CONNECT,
87                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
88             })
getSupportedMethods()89     public @NonNull List<DistanceMeasurementMethod> getSupportedMethods() {
90         final List<DistanceMeasurementMethod> supportedMethods = new ArrayList<>();
91         try {
92             IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
93             if (gatt == null) {
94                 Log.e(TAG, "Bluetooth GATT is null");
95                 return supportedMethods;
96             }
97             return gatt.getSupportedDistanceMeasurementMethods(mAttributionSource);
98         } catch (RemoteException e) {
99             Log.e(TAG, "Failed to get supported methods - ", e);
100         }
101         return supportedMethods;
102     }
103 
104     /**
105      * Start distance measurement and create a {@link DistanceMeasurementSession} for this
106      * operation. Once the session is started, a {@link DistanceMeasurementSession} object is
107      * provided through {@link
108      * DistanceMeasurementSession.Callback#onStarted(DistanceMeasurementSession)}. If starting a
109      * session fails, the failure is reported through {@link
110      * DistanceMeasurementSession.Callback#onStartFail(int)} with the failure reason.
111      *
112      * @param params parameters of this operation
113      * @param executor Executor to run callback
114      * @param callback callback to associate with the {@link DistanceMeasurementSession} that is
115      *     being started. The callback is registered by this function and unregistered when {@link
116      *     DistanceMeasurementSession.Callback#onStartFail(int)} or {@link
117      *     DistanceMeasurementSession .Callback#onStopped(DistanceMeasurementSession, int)}
118      * @return a CancellationSignal that may be used to cancel the starting of the {@link
119      *     DistanceMeasurementSession}
120      * @throws NullPointerException if any input parameter is null
121      * @throws IllegalStateException if the session is already registered
122      * @hide
123      */
124     @SystemApi
125     @Nullable
126     @RequiresPermission(
127             allOf = {
128                 android.Manifest.permission.BLUETOOTH_CONNECT,
129                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
130             })
startMeasurementSession( @onNull DistanceMeasurementParams params, @NonNull Executor executor, @NonNull DistanceMeasurementSession.Callback callback)131     public CancellationSignal startMeasurementSession(
132             @NonNull DistanceMeasurementParams params,
133             @NonNull Executor executor,
134             @NonNull DistanceMeasurementSession.Callback callback) {
135         Objects.requireNonNull(params, "params is null");
136         Objects.requireNonNull(executor, "executor is null");
137         Objects.requireNonNull(callback, "callback is null");
138         try {
139             IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
140             if (gatt == null) {
141                 Log.e(TAG, "Bluetooth GATT is null");
142                 return null;
143             }
144             DistanceMeasurementSession session =
145                     new DistanceMeasurementSession(
146                             gatt, mUuid, params, executor, mAttributionSource, callback);
147             CancellationSignal cancellationSignal = new CancellationSignal();
148             cancellationSignal.setOnCancelListener(() -> session.stopSession());
149 
150             if (mSessionMap.containsKey(params.getDevice())) {
151                 throw new IllegalStateException(
152                         params.getDevice().getAnonymizedAddress() + " already registered");
153             }
154 
155             mSessionMap.put(params.getDevice(), session);
156             gatt.startDistanceMeasurement(mUuid, params, mCallbackWrapper, mAttributionSource);
157             return cancellationSignal;
158         } catch (RemoteException e) {
159             throw e.rethrowAsRuntimeException();
160         }
161     }
162 
163     /**
164      * Get the maximum supported security level of channel sounding between the local device and a
165      * specific remote device.
166      *
167      * <p>See: https://bluetooth.com/specifications/specs/channel-sounding-cr-pr/
168      *
169      * @param remoteDevice remote device of channel sounding
170      * @return max supported security level, {@link ChannelSoundingParams#CS_SECURITY_LEVEL_UNKNOWN}
171      *     when Channel Sounding is not supported or encounters an internal error.
172      * @hide
173      */
174     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
175     @SystemApi
176     @CsSecurityLevel
177     @RequiresPermission(
178             allOf = {
179                 android.Manifest.permission.BLUETOOTH_CONNECT,
180                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
181             })
getChannelSoundingMaxSupportedSecurityLevel(@onNull BluetoothDevice remoteDevice)182     public int getChannelSoundingMaxSupportedSecurityLevel(@NonNull BluetoothDevice remoteDevice) {
183         Objects.requireNonNull(remoteDevice, "remote device is null");
184         final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN;
185         try {
186             IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
187             if (gatt == null) {
188                 Log.e(TAG, "Bluetooth GATT is null");
189                 return defaultValue;
190             }
191             return gatt.getChannelSoundingMaxSupportedSecurityLevel(
192                     remoteDevice, mAttributionSource);
193         } catch (RemoteException e) {
194             Log.e(TAG, "Failed to get supported security Level - ", e);
195         }
196         return defaultValue;
197     }
198 
199     /**
200      * Get the maximum supported security level of channel sounding of the local device.
201      *
202      * <p>See: https://bluetooth.com/specifications/specs/channel-sounding-cr-pr/
203      *
204      * @return max supported security level, {@link ChannelSoundingParams#CS_SECURITY_LEVEL_UNKNOWN}
205      *     when Channel Sounding is not supported or encounters an internal error.
206      * @hide
207      */
208     @FlaggedApi(Flags.FLAG_CHANNEL_SOUNDING)
209     @SystemApi
210     @CsSecurityLevel
211     @RequiresPermission(
212             allOf = {
213                 android.Manifest.permission.BLUETOOTH_CONNECT,
214                 android.Manifest.permission.BLUETOOTH_PRIVILEGED,
215             })
getLocalChannelSoundingMaxSupportedSecurityLevel()216     public int getLocalChannelSoundingMaxSupportedSecurityLevel() {
217         final int defaultValue = ChannelSoundingParams.CS_SECURITY_LEVEL_UNKNOWN;
218         try {
219             IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
220             if (gatt == null) {
221                 Log.e(TAG, "Bluetooth GATT is null");
222                 return defaultValue;
223             }
224             return gatt.getLocalChannelSoundingMaxSupportedSecurityLevel(mAttributionSource);
225         } catch (RemoteException e) {
226             Log.e(TAG, "Failed to get supported security Level - ", e);
227         }
228         return defaultValue;
229     }
230 
231     @SuppressLint("AndroidFrameworkBluetoothPermission")
232     private final IDistanceMeasurementCallback mCallbackWrapper =
233             new IDistanceMeasurementCallback.Stub() {
234                 @Override
235                 public void onStarted(BluetoothDevice device) {
236                     DistanceMeasurementSession session = mSessionMap.get(device);
237                     session.onStarted();
238                 }
239 
240                 @Override
241                 public void onStartFail(BluetoothDevice device, int reason) {
242                     DistanceMeasurementSession session = mSessionMap.get(device);
243                     session.onStartFail(reason);
244                     mSessionMap.remove(device);
245                 }
246 
247                 @Override
248                 public void onStopped(BluetoothDevice device, int reason) {
249                     DistanceMeasurementSession session = mSessionMap.get(device);
250                     session.onStopped(reason);
251                     mSessionMap.remove(device);
252                 }
253 
254                 @Override
255                 public void onResult(BluetoothDevice device, DistanceMeasurementResult result) {
256                     DistanceMeasurementSession session = mSessionMap.get(device);
257                     session.onResult(device, result);
258                 }
259             };
260 }
261