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