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 androidx.core.uwb.backend.impl.internal; 18 19 import static androidx.core.uwb.backend.impl.internal.RangingSessionCallback.REASON_FAILED_TO_START; 20 import static androidx.core.uwb.backend.impl.internal.RangingSessionCallback.REASON_STOP_RANGING_CALLED; 21 import static androidx.core.uwb.backend.impl.internal.Utils.CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR; 22 import static androidx.core.uwb.backend.impl.internal.Utils.INVALID_API_CALL; 23 import static androidx.core.uwb.backend.impl.internal.Utils.STATUS_OK; 24 import static androidx.core.uwb.backend.impl.internal.Utils.SUPPORTED_BPRF_PREAMBLE_INDEX; 25 import static androidx.core.uwb.backend.impl.internal.Utils.TAG; 26 import static androidx.core.uwb.backend.impl.internal.Utils.UWB_RECONFIGURATION_FAILURE; 27 import static androidx.core.uwb.backend.impl.internal.Utils.UWB_SYSTEM_CALLBACK_FAILURE; 28 29 import static com.google.uwb.support.fira.FiraParams.UWB_CHANNEL_9; 30 31 import static java.util.Objects.requireNonNull; 32 33 import android.annotation.SuppressLint; 34 import android.os.Build.VERSION; 35 import android.os.Build.VERSION_CODES; 36 import android.util.Log; 37 import android.uwb.UwbManager; 38 39 import androidx.annotation.Nullable; 40 import androidx.annotation.RequiresApi; 41 42 import com.google.uwb.support.fira.FiraOpenSessionParams; 43 import com.google.uwb.support.fira.FiraParams; 44 45 import java.util.ArrayList; 46 import java.util.List; 47 import java.util.Random; 48 import java.util.concurrent.Executor; 49 import java.util.concurrent.ExecutorService; 50 51 /** Represents a UWB ranging controller */ 52 @RequiresApi(api = VERSION_CODES.S) 53 public class RangingController extends RangingDevice { 54 55 private final List<UwbAddress> mDynamicallyAddedPeers = new ArrayList<>(); 56 57 @Nullable 58 private RangingSessionCallback mRangingSessionCallback; 59 RangingController(UwbManager manager, Executor executor, OpAsyncCallbackRunner<Boolean> opAsyncCallbackRunner, UwbFeatureFlags uwbFeatureFlags)60 RangingController(UwbManager manager, Executor executor, 61 OpAsyncCallbackRunner<Boolean> opAsyncCallbackRunner, UwbFeatureFlags uwbFeatureFlags) { 62 super(manager, executor, opAsyncCallbackRunner, uwbFeatureFlags); 63 } 64 65 @Override getOpenSessionParams()66 protected FiraOpenSessionParams getOpenSessionParams() { 67 requireNonNull(mRangingParameters); 68 return ConfigurationManager.createOpenSessionParams( 69 FiraParams.RANGING_DEVICE_TYPE_CONTROLLER, getLocalAddress(), mRangingParameters, 70 mUwbFeatureFlags); 71 } 72 73 /** 74 * gets complex channel. if it's the first time that this function is called, it will check the 75 * driver and try to get the best-available settings. 76 */ 77 @SuppressLint("WrongConstant") getComplexChannel()78 public UwbComplexChannel getComplexChannel() { 79 if (isForTesting()) { 80 mComplexChannel = 81 new UwbComplexChannel(Utils.channelForTesting, Utils.preambleIndexForTesting); 82 } 83 if (mComplexChannel == null) { 84 mComplexChannel = getBestAvailableComplexChannel(); 85 } 86 return mComplexChannel; 87 } 88 89 /** Sets complex channel. */ setComplexChannel(UwbComplexChannel complexChannel)90 public void setComplexChannel(UwbComplexChannel complexChannel) { 91 mComplexChannel = complexChannel; 92 } 93 94 /** 95 * Update the complex channel, even if the complex channel has been set before. Channel 9 is 96 * mandatory to all devices. Since system API hasn't implemented capability check yet, channel 9 97 * is the best guess for now. 98 * 99 * @return The complex channel most suitable for this ranging session. 100 */ getBestAvailableComplexChannel()101 public UwbComplexChannel getBestAvailableComplexChannel() { 102 int preambleIndex = 103 SUPPORTED_BPRF_PREAMBLE_INDEX.get( 104 new Random().nextInt(SUPPORTED_BPRF_PREAMBLE_INDEX.size())); 105 UwbComplexChannel availableChannel = new UwbComplexChannel(UWB_CHANNEL_9, preambleIndex); 106 Log.i(TAG, String.format("set complexChannel to %s", availableChannel)); 107 return availableChannel; 108 } 109 110 @Override hashSessionId(RangingParameters rangingParameters)111 protected int hashSessionId(RangingParameters rangingParameters) { 112 return calculateHashedSessionId(getLocalAddress(), getComplexChannel()); 113 } 114 115 @Override isKnownPeer(UwbAddress address)116 protected boolean isKnownPeer(UwbAddress address) { 117 return super.isKnownPeer(address) || mDynamicallyAddedPeers.contains(address); 118 } 119 120 @Override startRanging( RangingSessionCallback callback, ExecutorService backendCallbackExecutor)121 public synchronized int startRanging( 122 RangingSessionCallback callback, ExecutorService backendCallbackExecutor) { 123 requireNonNull(mRangingParameters); 124 if (mComplexChannel == null) { 125 Log.w(TAG, "Need to call getComplexChannel() first"); 126 return INVALID_API_CALL; 127 } 128 129 if (ConfigurationManager.isUnicast(mRangingParameters.getUwbConfigId()) 130 && mRangingParameters.getPeerAddresses().size() > 1) { 131 Log.w( 132 TAG, 133 String.format( 134 "Config ID %d doesn't support one-to-many", 135 mRangingParameters.getUwbConfigId())); 136 return INVALID_API_CALL; 137 } 138 139 int status = super.startRanging(callback, backendCallbackExecutor); 140 if (isAlive()) { 141 mRangingSessionCallback = callback; 142 } 143 return status; 144 } 145 146 @Override stopRanging()147 public synchronized int stopRanging() { 148 int status = super.stopRanging(); 149 mDynamicallyAddedPeers.clear(); 150 mRangingSessionCallback = null; 151 return status; 152 } 153 154 /** 155 * Add a new controlee to the controller. If the controleer is added successfully, {@link 156 * RangingSessionCallback#onRangingInitialized(UwbDevice)} will be called. If the adding 157 * operation failed, {@link RangingSessionCallback#onRangingSuspended(UwbDevice, int)} will be 158 * called. 159 * 160 * @return {@link Utils#INVALID_API_CALL} if this is a unicast session but multiple peers are 161 * configured. 162 */ addControlee(UwbAddress controleeAddress)163 public synchronized int addControlee(UwbAddress controleeAddress) { 164 Log.i(TAG, String.format("Add UWB peer: %s", controleeAddress)); 165 if (!isAlive()) { 166 return INVALID_API_CALL; 167 } 168 if (ConfigurationManager.isUnicast(mRangingParameters.getUwbConfigId())) { 169 return INVALID_API_CALL; 170 } 171 if (isKnownPeer(controleeAddress) || mDynamicallyAddedPeers.contains(controleeAddress)) { 172 return STATUS_OK; 173 } 174 // Reconfigure the session. 175 int[] subSessionIdList = mRangingParameters.getUwbConfigId() 176 == CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR 177 ? new int[]{mRangingParameters.getSubSessionId()} 178 : null; 179 byte[] subSessionKeyInfo = mRangingParameters.getUwbConfigId() 180 == CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR 181 ? mRangingParameters.getSubSessionKeyInfo() 182 : null; 183 boolean success = 184 addControleeAdapter( 185 new UwbAddress[] {controleeAddress}, subSessionIdList, subSessionKeyInfo); 186 187 RangingSessionCallback callback = mRangingSessionCallback; 188 if (success) { 189 if (callback != null) { 190 runOnBackendCallbackThread( 191 () -> 192 callback.onRangingInitialized( 193 UwbDevice.createForAddress(controleeAddress.toBytes()))); 194 } 195 mDynamicallyAddedPeers.add(controleeAddress); 196 } else { 197 if (callback != null) { 198 runOnBackendCallbackThread( 199 () -> 200 callback.onRangingSuspended( 201 UwbDevice.createForAddress(controleeAddress.toBytes()), 202 REASON_FAILED_TO_START)); 203 } 204 } 205 206 return STATUS_OK; 207 } 208 209 /** 210 * Add a new controlee to the controller. If the controlee is added successfully, {@link 211 * RangingSessionCallback#onRangingInitialized(UwbDevice)} will be called. If the adding 212 * operation failed, {@link RangingSessionCallback#onRangingSuspended(UwbDevice, int)} will be 213 * called. 214 * 215 * @return {@link Utils#INVALID_API_CALL} if this is a unicast session but multiple peers are 216 * configured. 217 */ addControleeWithSessionParams(RangingControleeParameters params)218 public synchronized int addControleeWithSessionParams(RangingControleeParameters params) { 219 UwbAddress controleeAddress = params.getAddress(); 220 Log.i(TAG, String.format("Add UWB peer: %s", controleeAddress)); 221 if (!isAlive()) { 222 return INVALID_API_CALL; 223 } 224 if (ConfigurationManager.isUnicast(mRangingParameters.getUwbConfigId())) { 225 return INVALID_API_CALL; 226 } 227 if (isKnownPeer(controleeAddress) || mDynamicallyAddedPeers.contains(controleeAddress)) { 228 return STATUS_OK; 229 } 230 // Reconfigure the session. 231 int[] subSessionIdList = mRangingParameters.getUwbConfigId() 232 == CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR 233 ? new int[]{params.getSubSessionId()} 234 : null; 235 byte[] subSessionKeyInfo = mRangingParameters.getUwbConfigId() 236 == CONFIG_PROVISIONED_INDIVIDUAL_MULTICAST_DS_TWR 237 ? params.getSubSessionKey() 238 : null; 239 boolean success = 240 addControleeAdapter( 241 new UwbAddress[] {controleeAddress}, subSessionIdList, subSessionKeyInfo); 242 243 RangingSessionCallback callback = mRangingSessionCallback; 244 if (success) { 245 if (callback != null) { 246 runOnBackendCallbackThread( 247 () -> 248 callback.onRangingInitialized( 249 UwbDevice.createForAddress(controleeAddress.toBytes()))); 250 } 251 mDynamicallyAddedPeers.add(controleeAddress); 252 } else { 253 if (callback != null) { 254 runOnBackendCallbackThread( 255 () -> 256 callback.onRangingSuspended( 257 UwbDevice.createForAddress(controleeAddress.toBytes()), 258 REASON_FAILED_TO_START)); 259 } 260 } 261 262 return STATUS_OK; 263 } 264 265 /** 266 * Adapter method for to add controlee, via addControlee() api call for versions T an above. 267 * 268 * @return true if addControlee() was successful. 269 */ addControleeAdapter( UwbAddress[] controleeAddress, @Nullable int[] subSessionIdList, @Nullable byte[] subSessionKeyInfo)270 private synchronized boolean addControleeAdapter( 271 UwbAddress[] controleeAddress, 272 @Nullable int[] subSessionIdList, 273 @Nullable byte[] subSessionKeyInfo) { 274 if (VERSION.SDK_INT < VERSION_CODES.TIRAMISU) { 275 return reconfigureRanging( 276 ConfigurationManager.createReconfigureParams( 277 mRangingParameters.getUwbConfigId(), 278 FiraParams.MULTICAST_LIST_UPDATE_ACTION_ADD, 279 controleeAddress, 280 subSessionIdList, 281 subSessionKeyInfo, 282 mUwbFeatureFlags) 283 .toBundle()); 284 } 285 return addControlee( 286 ConfigurationManager.createControleeParams( 287 mRangingParameters.getUwbConfigId(), 288 FiraParams.MULTICAST_LIST_UPDATE_ACTION_ADD, 289 controleeAddress, 290 subSessionIdList, 291 subSessionKeyInfo, 292 mUwbFeatureFlags) 293 .toBundle()); 294 } 295 296 /** 297 * Remove a controlee from current session. 298 * 299 * @return returns {@link Utils#STATUS_OK} if the controlee is removed successfully. returns 300 * {@link Utils#INVALID_API_CALL} if: 301 * <ul> 302 * <li>Provided address is not in the controller's peer list 303 * <li>The active profile is unicast 304 * </ul> 305 */ removeControlee(UwbAddress controleeAddress)306 public synchronized int removeControlee(UwbAddress controleeAddress) { 307 Log.i(TAG, String.format("Remove UWB peer: %s", controleeAddress)); 308 if (!isAlive()) { 309 Log.w(TAG, "Attempt to remove controlee while session is not active."); 310 return INVALID_API_CALL; 311 } 312 if (!isKnownPeer(controleeAddress)) { 313 Log.w(TAG, "Attempt to remove non-existing controlee."); 314 return INVALID_API_CALL; 315 } 316 317 // Reconfigure the session. 318 boolean success = removeControleeAdapter(new UwbAddress[] {controleeAddress}); 319 if (!success) { 320 return UWB_SYSTEM_CALLBACK_FAILURE; 321 } 322 323 RangingSessionCallback callback = mRangingSessionCallback; 324 if (callback != null) { 325 runOnBackendCallbackThread( 326 () -> 327 callback.onRangingSuspended( 328 UwbDevice.createForAddress(controleeAddress.toBytes()), 329 REASON_STOP_RANGING_CALLED)); 330 } 331 mDynamicallyAddedPeers.remove(controleeAddress); 332 return STATUS_OK; 333 } 334 335 /** 336 * Adapter method to remove controlee, via removeControlee() api call for versions T and above. 337 * 338 * @return true if removeControlee() was successful. 339 */ removeControleeAdapter(UwbAddress[] controleeAddress)340 private synchronized boolean removeControleeAdapter(UwbAddress[] controleeAddress) { 341 if (VERSION.SDK_INT < VERSION_CODES.TIRAMISU) { 342 return reconfigureRanging( 343 ConfigurationManager.createReconfigureParams( 344 mRangingParameters.getUwbConfigId(), 345 FiraParams.MULTICAST_LIST_UPDATE_ACTION_DELETE, 346 controleeAddress, 347 /* subSessionIdList= */ null, 348 /* subSessionKey= */ null, 349 mUwbFeatureFlags) 350 .toBundle()); 351 } 352 return removeControlee( 353 ConfigurationManager.createControleeParams( 354 mRangingParameters.getUwbConfigId(), 355 FiraParams.MULTICAST_LIST_UPDATE_ACTION_DELETE, 356 controleeAddress, 357 /* subSessionIdList= */ null, 358 /* subSessionKey= */ null, 359 mUwbFeatureFlags) 360 .toBundle()); 361 } 362 363 /** 364 * Reconfigures ranging interval for an ongoing session 365 * 366 * @return STATUS_OK if reconfigure was successful. 367 * @return UWB_RECONFIGURATION_FAILURE if reconfigure failed. 368 * @return INVALID_API_CALL if ranging session is not active. 369 */ setBlockStriding(int blockStridingLength)370 public synchronized int setBlockStriding(int blockStridingLength) { 371 if (!isAlive()) { 372 Log.w(TAG, "Attempt to set block striding while session is not active."); 373 return INVALID_API_CALL; 374 } 375 376 boolean success = 377 reconfigureRanging( 378 ConfigurationManager.createReconfigureParamsBlockStriding( 379 blockStridingLength) 380 .toBundle()); 381 382 if (!success) { 383 Log.w(TAG, "Reconfiguring ranging interval failed"); 384 return UWB_RECONFIGURATION_FAILURE; 385 } 386 return STATUS_OK; 387 } 388 } 389