1 /*
2  * Copyright 2024 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 com.android.bluetooth.channelsoundingtestapp;
18 
19 import android.annotation.SuppressLint;
20 import android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothManager;
23 import android.bluetooth.BluetoothStatusCodes;
24 import android.bluetooth.le.DistanceMeasurementManager;
25 import android.bluetooth.le.DistanceMeasurementMethod;
26 import android.bluetooth.le.DistanceMeasurementParams;
27 import android.bluetooth.le.DistanceMeasurementResult;
28 import android.bluetooth.le.DistanceMeasurementSession;
29 import android.content.Context;
30 import android.util.Pair;
31 
32 import androidx.annotation.Nullable;
33 
34 import java.util.ArrayList;
35 import java.util.List;
36 import java.util.concurrent.Executor;
37 import java.util.concurrent.Executors;
38 
39 class DistanceMeasurementInitiator {
40 
41     enum Freq {
42         HIGH(DistanceMeasurementParams.REPORT_FREQUENCY_HIGH),
43         MEDIUM(DistanceMeasurementParams.REPORT_FREQUENCY_MEDIUM),
44         LOW(DistanceMeasurementParams.REPORT_FREQUENCY_LOW);
45         private final int freq;
46 
Freq(int freq)47         Freq(int freq) {
48             this.freq = freq;
49         }
50 
getFreq()51         int getFreq() {
52             return freq;
53         }
54 
55         @Override
toString()56         public String toString() {
57             return name();
58         }
59 
fromName(String name)60         public static Freq fromName(String name) {
61             try {
62                 return Freq.valueOf(name);
63             } catch (IllegalArgumentException e) {
64                 return MEDIUM;
65             }
66         }
67     }
68 
69     private static final int DISTANCE_MEASUREMENT_DURATION_SEC = 3600;
70     private static final List<Pair<Integer, String>> mDistanceMeasurementMethodMapping =
71             List.of(
72                     new Pair<>(DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_AUTO, "AUTO"),
73                     new Pair<>(DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_RSSI, "RSSI"),
74                     new Pair<>(
75                             DistanceMeasurementMethod.DISTANCE_MEASUREMENT_METHOD_CHANNEL_SOUNDING,
76                             "Channel Sounding"));
77 
78     private final BluetoothAdapter mBluetoothAdapter;
79     private final LoggingListener mLoggingListener;
80 
81     private final Context mApplicationContext;
82     private final Executor mBtExecutor;
83     private final BtDistanceMeasurementCallback mBtDistanceMeasurementCallback;
84     @Nullable private DistanceMeasurementSession mSession = null;
85     @Nullable private BluetoothDevice mTargetDevice = null;
86 
DistanceMeasurementInitiator( Context applicationContext, BtDistanceMeasurementCallback btDistanceMeasurementCallback, LoggingListener loggingListener)87     DistanceMeasurementInitiator(
88             Context applicationContext,
89             BtDistanceMeasurementCallback btDistanceMeasurementCallback,
90             LoggingListener loggingListener) {
91         mApplicationContext = applicationContext;
92         mBtDistanceMeasurementCallback = btDistanceMeasurementCallback;
93         mLoggingListener = loggingListener;
94 
95         BluetoothManager bluetoothManager =
96                 mApplicationContext.getSystemService(BluetoothManager.class);
97         mBluetoothAdapter = bluetoothManager.getAdapter();
98 
99         mBtExecutor = Executors.newSingleThreadExecutor();
100     }
101 
setTargetDevice(BluetoothDevice targetDevice)102     void setTargetDevice(BluetoothDevice targetDevice) {
103         mTargetDevice = targetDevice;
104     }
105 
printLog(String log)106     private void printLog(String log) {
107         mLoggingListener.onLog(log);
108     }
109 
getDistanceMeasurementMethodName(int methodId)110     private String getDistanceMeasurementMethodName(int methodId) {
111         for (Pair<Integer, String> methodMapping : mDistanceMeasurementMethodMapping) {
112             if (methodMapping.first == methodId) {
113                 return methodMapping.second;
114             }
115         }
116         throw new IllegalArgumentException("unknown distance measurement method id" + methodId);
117     }
118 
getDistanceMeasurementMethodId(String methodName)119     private int getDistanceMeasurementMethodId(String methodName) {
120         for (Pair<Integer, String> methodMapping : mDistanceMeasurementMethodMapping) {
121             if (methodMapping.second.equals(methodName)) {
122                 return methodMapping.first;
123             }
124         }
125         throw new IllegalArgumentException("unknown distance measurement method name" + methodName);
126     }
127 
128     @SuppressLint("MissingPermission") // permissions are checked upfront
getDistanceMeasurementMethods()129     List<String> getDistanceMeasurementMethods() {
130         List<String> methods = new ArrayList<>();
131         if (mBluetoothAdapter.isDistanceMeasurementSupported()
132                 != BluetoothStatusCodes.FEATURE_SUPPORTED) {
133             printLog("No distance measurement is supported");
134             return methods;
135         }
136         DistanceMeasurementManager distanceMeasurementManager =
137                 mBluetoothAdapter.getDistanceMeasurementManager();
138         List<DistanceMeasurementMethod> list = distanceMeasurementManager.getSupportedMethods();
139 
140         StringBuilder dbgMessage = new StringBuilder("getDistanceMeasurementMethods: ");
141         for (DistanceMeasurementMethod method : list) {
142             String methodName = getDistanceMeasurementMethodName((int) method.getId());
143             dbgMessage.append(methodName).append(", ");
144             methods.add(methodName);
145         }
146         printLog(dbgMessage.toString());
147         return methods;
148     }
149 
getMeasurementFreqs()150     List<String> getMeasurementFreqs() {
151         return List.of(Freq.MEDIUM.toString(), Freq.HIGH.toString(), Freq.LOW.toString());
152     }
153 
154     @SuppressLint("MissingPermission") // permissions are checked upfront
startDistanceMeasurement(String distanceMeasurementMethodName, String selectedFreq)155     void startDistanceMeasurement(String distanceMeasurementMethodName, String selectedFreq) {
156 
157         if (mTargetDevice == null) {
158             printLog("do Gatt connect first");
159             return;
160         }
161 
162         printLog("start CS with device: " + mTargetDevice.getName());
163 
164         DistanceMeasurementParams params =
165                 new DistanceMeasurementParams.Builder(mTargetDevice)
166                         .setDurationSeconds(DISTANCE_MEASUREMENT_DURATION_SEC)
167                         .setFrequency(Freq.fromName(selectedFreq).getFreq())
168                         .setMethodId(getDistanceMeasurementMethodId(distanceMeasurementMethodName))
169                         .build();
170         DistanceMeasurementManager distanceMeasurementManager =
171                 mBluetoothAdapter.getDistanceMeasurementManager();
172         distanceMeasurementManager.startMeasurementSession(params, mBtExecutor, mTestcallback);
173     }
174 
stopDistanceMeasurement()175     void stopDistanceMeasurement() {
176         if (mSession == null) {
177             return;
178         }
179         mSession.stopSession();
180         mSession = null;
181     }
182 
183     private DistanceMeasurementSession.Callback mTestcallback =
184             new DistanceMeasurementSession.Callback() {
185                 public void onStarted(DistanceMeasurementSession session) {
186                     printLog("DistanceMeasurement onStarted ! ");
187                     mSession = session;
188                     mBtDistanceMeasurementCallback.onStartSuccess();
189                 }
190 
191                 public void onStartFail(int reason) {
192                     printLog("DistanceMeasurement onStartFail ! reason " + reason);
193                     mBtDistanceMeasurementCallback.onStartFail();
194                 }
195 
196                 public void onStopped(DistanceMeasurementSession session, int reason) {
197                     printLog("DistanceMeasurement onStopped ! reason " + reason);
198                     mBtDistanceMeasurementCallback.onStop();
199                     mSession = null;
200                 }
201 
202                 public void onResult(BluetoothDevice device, DistanceMeasurementResult result) {
203                     printLog(
204                             "DistanceMeasurement onResult ! "
205                                     + result.getResultMeters()
206                                     + ", "
207                                     + result.getErrorMeters());
208                     mBtDistanceMeasurementCallback.onDistanceResult(result.getResultMeters());
209                 }
210             };
211 
212     interface BtDistanceMeasurementCallback {
213 
onStartSuccess()214         void onStartSuccess();
215 
onStartFail()216         void onStartFail();
217 
onStop()218         void onStop();
219 
onDistanceResult(double distanceMeters)220         void onDistanceResult(double distanceMeters);
221     }
222 }
223