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 android.ranging; 18 19 import android.Manifest; 20 import android.annotation.FlaggedApi; 21 import android.annotation.IntDef; 22 import android.annotation.IntRange; 23 import android.annotation.NonNull; 24 import android.annotation.RequiresPermission; 25 import android.content.AttributionSource; 26 import android.os.CancellationSignal; 27 import android.os.RemoteException; 28 import android.ranging.oob.DeviceHandle; 29 import android.ranging.oob.OobHandle; 30 import android.ranging.oob.OobInitiatorRangingConfig; 31 import android.ranging.oob.OobResponderRangingConfig; 32 import android.ranging.oob.TransportHandle; 33 import android.ranging.raw.RawResponderRangingConfig; 34 import android.util.Log; 35 36 import com.android.ranging.flags.Flags; 37 38 import java.lang.annotation.ElementType; 39 import java.lang.annotation.Retention; 40 import java.lang.annotation.RetentionPolicy; 41 import java.lang.annotation.Target; 42 import java.util.ArrayList; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.concurrent.ConcurrentHashMap; 46 import java.util.concurrent.Executor; 47 import java.util.concurrent.Executors; 48 49 50 /** 51 * Represents a session for performing ranging operations. A {@link RangingSession} manages 52 * the lifecycle of a ranging operation, including start, stop, and event callbacks. 53 * 54 * <p>All methods are asynchronous and rely on the provided {@link Executor} to invoke 55 * callbacks on appropriate threads. 56 * 57 * <p>This class implements {@link AutoCloseable}, ensuring that resources can be 58 * automatically released when the session is closed. 59 * 60 */ 61 @FlaggedApi(Flags.FLAG_RANGING_STACK_ENABLED) 62 public final class RangingSession implements AutoCloseable { 63 private static final String TAG = "RangingSession"; 64 private final AttributionSource mAttributionSource; 65 private final SessionHandle mSessionHandle; 66 private final IRangingAdapter mRangingAdapter; 67 private final RangingSessionManager mRangingSessionManager; 68 private final Callback mCallback; 69 private final Executor mExecutor; 70 private final Map<RangingDevice, android.ranging.oob.TransportHandle> mTransportHandles = 71 new ConcurrentHashMap<>(); 72 73 74 /** 75 * @hide 76 */ RangingSession(RangingSessionManager rangingSessionManager, AttributionSource attributionSource, SessionHandle sessionHandle, IRangingAdapter rangingAdapter, Callback callback, Executor executor)77 public RangingSession(RangingSessionManager rangingSessionManager, 78 AttributionSource attributionSource, 79 SessionHandle sessionHandle, IRangingAdapter rangingAdapter, 80 Callback callback, Executor executor) { 81 mRangingSessionManager = rangingSessionManager; 82 mAttributionSource = attributionSource; 83 mSessionHandle = sessionHandle; 84 mRangingAdapter = rangingAdapter; 85 mCallback = callback; 86 mExecutor = executor; 87 } 88 89 /** 90 * Starts the ranging session with the provided ranging preferences. 91 * <p>The {@link Callback#onOpened()} will be called when the session finishes starting. 92 * 93 * <p>The provided {@link RangingPreference} determines the configuration for the session. 94 * A {@link CancellationSignal} is returned to allow the caller to cancel the session 95 * if needed. If the session is canceled, the {@link #close()} method will be invoked 96 * automatically to release resources. 97 * 98 * @param rangingPreference {@link RangingPreference} the preferences for configuring the 99 * ranging session. 100 * @return a {@link CancellationSignal} to close the session. 101 */ 102 @RequiresPermission(Manifest.permission.RANGING) 103 @NonNull start(@onNull RangingPreference rangingPreference)104 public CancellationSignal start(@NonNull RangingPreference rangingPreference) { 105 if (rangingPreference.getRangingParams().getRangingSessionType() 106 == RangingConfig.RANGING_SESSION_OOB) { 107 mRangingSessionManager.registerOobSendDataListener(); 108 setupTransportHandles(rangingPreference); 109 } 110 Log.v(TAG, "Start ranging - " + mSessionHandle); 111 try { 112 mRangingAdapter.startRanging(mAttributionSource, mSessionHandle, rangingPreference, 113 mRangingSessionManager); 114 } catch (RemoteException e) { 115 throw e.rethrowFromSystemServer(); 116 } 117 CancellationSignal cancellationSignal = new CancellationSignal(); 118 cancellationSignal.setOnCancelListener(this::close); 119 120 return cancellationSignal; 121 } 122 setupTransportHandles(RangingPreference rangingPreference)123 private void setupTransportHandles(RangingPreference rangingPreference) { 124 List<DeviceHandle> deviceHandleList = new ArrayList<>(); 125 if (rangingPreference.getRangingParams() instanceof OobInitiatorRangingConfig) { 126 deviceHandleList.addAll(((OobInitiatorRangingConfig) 127 rangingPreference.getRangingParams()).getDeviceHandles()); 128 } else if (rangingPreference.getRangingParams() instanceof OobResponderRangingConfig) { 129 deviceHandleList.add(((OobResponderRangingConfig) 130 rangingPreference.getRangingParams()).getDeviceHandle()); 131 } 132 for (DeviceHandle deviceHandle : deviceHandleList) { 133 TransportHandleReceiveCallback receiveCallback = 134 new TransportHandleReceiveCallback(deviceHandle.getRangingDevice()); 135 deviceHandle.getTransportHandle().registerReceiveCallback( 136 Executors.newCachedThreadPool(), receiveCallback); 137 mTransportHandles.put(deviceHandle.getRangingDevice(), 138 deviceHandle.getTransportHandle()); 139 } 140 } 141 142 /** 143 * Adds a new device to an ongoing ranging session. 144 * <p> 145 * This method allows for adding a new device to an active ranging session using raw ranging 146 * parameters. Only devices represented by {@link RawResponderRangingConfig} is supported. 147 * If the provided {@link RangingConfig} does not match one of these types, the addition fails 148 * and invokes {@link Callback#onOpenFailed(int)} with a reason of 149 * {@link Callback#REASON_UNSUPPORTED}. 150 * </p> 151 * 152 * @param deviceRangingParams the ranging parameters for the device to be added, 153 * which must be an instance of {@link RawResponderRangingConfig} 154 * 155 * @apiNote If the underlying ranging technology cannot support this dynamic addition, failure 156 * will be indicated via {@code Callback#onStartFailed(REASON_UNSUPPORTED, RangingDevice)} 157 * 158 */ 159 @RequiresPermission(Manifest.permission.RANGING) addDeviceToRangingSession(@onNull RangingConfig deviceRangingParams)160 public void addDeviceToRangingSession(@NonNull RangingConfig deviceRangingParams) { 161 Log.v(TAG, " Add device - " + mSessionHandle); 162 try { 163 if (deviceRangingParams instanceof RawResponderRangingConfig) { 164 mRangingAdapter.addRawDevice(mSessionHandle, 165 (RawResponderRangingConfig) deviceRangingParams); 166 } else { 167 mCallback.onOpenFailed(Callback.REASON_UNSUPPORTED); 168 } 169 } catch (RemoteException e) { 170 throw e.rethrowFromSystemServer(); 171 } 172 } 173 174 /** 175 * Removes a specific device from an ongoing ranging session. 176 * <p> 177 * This method removes a specified device from the active ranging session, stopping 178 * further ranging operations for that device. The operation is handled by the system 179 * server and may throw a {@link RemoteException} in case of server-side communication 180 * issues. 181 * </p> 182 * 183 * @param rangingDevice the device to be removed from the session. 184 * @apiNote Currently, this API is supported only for UWB multicast session if using 185 * {@link RangingConfig#RANGING_SESSION_RAW}. 186 * 187 */ 188 @RequiresPermission(Manifest.permission.RANGING) removeDeviceFromRangingSession(@onNull RangingDevice rangingDevice)189 public void removeDeviceFromRangingSession(@NonNull RangingDevice rangingDevice) { 190 Log.v(TAG, " Remove device - " + mSessionHandle); 191 try { 192 mRangingAdapter.removeDevice(mSessionHandle, rangingDevice); 193 } catch (RemoteException e) { 194 throw e.rethrowFromSystemServer(); 195 } 196 } 197 198 /** 199 * Reconfigures the ranging interval for the current session by setting the interval 200 * skip count. The {@code intervalSkipCount} defines how many intervals should be skipped 201 * between successive ranging rounds. Valid values range from 0 to 255. 202 * 203 * @param intervalSkipCount the number of intervals to skip, ranging from 0 to 255. 204 */ 205 @RequiresPermission(Manifest.permission.RANGING) reconfigureRangingInterval(@ntRangefrom = 0, to = 255) int intervalSkipCount)206 public void reconfigureRangingInterval(@IntRange(from = 0, to = 255) int intervalSkipCount) { 207 Log.v(TAG, " Reconfiguring ranging interval - " + mSessionHandle); 208 try { 209 mRangingAdapter.reconfigureRangingInterval(mSessionHandle, intervalSkipCount); 210 } catch (RemoteException e) { 211 throw e.rethrowFromSystemServer(); 212 } 213 } 214 215 /** 216 * Stops the ranging session. 217 * 218 * <p>This method releases any ongoing ranging operations. If the operation fails, 219 * it will propagate a {@link RemoteException} from the system server. 220 */ 221 @RequiresPermission(Manifest.permission.RANGING) stop()222 public void stop() { 223 Log.v(TAG, "Stop ranging - " + mSessionHandle); 224 try { 225 mRangingAdapter.stopRanging(mSessionHandle); 226 } catch (RemoteException e) { 227 throw e.rethrowFromSystemServer(); 228 } 229 } 230 231 /** 232 * @hide 233 */ onOpened()234 public void onOpened() { 235 mExecutor.execute(mCallback::onOpened); 236 } 237 238 /** 239 * @hide 240 */ onOpenFailed(@allback.Reason int reason)241 public void onOpenFailed(@Callback.Reason int reason) { 242 mExecutor.execute(() -> mCallback.onOpenFailed(reason)); 243 } 244 245 /** 246 * @hide 247 */ onStarted(RangingDevice peer, @RangingManager.RangingTechnology int technology)248 public void onStarted(RangingDevice peer, @RangingManager.RangingTechnology int technology) { 249 mExecutor.execute(() -> mCallback.onStarted(peer, technology)); 250 } 251 252 /** 253 * @hide 254 */ onResults(RangingDevice peer, RangingData data)255 public void onResults(RangingDevice peer, RangingData data) { 256 mExecutor.execute(() -> mCallback.onResults(peer, data)); 257 } 258 259 /** 260 * @hide 261 */ onStopped(RangingDevice peer, @RangingManager.RangingTechnology int technology)262 public void onStopped(RangingDevice peer, @RangingManager.RangingTechnology int technology) { 263 mExecutor.execute(() -> mCallback.onStopped(peer, technology)); 264 } 265 266 /** 267 * @hide 268 */ onClosed(@allback.Reason int reason)269 public void onClosed(@Callback.Reason int reason) { 270 mExecutor.execute(() -> mCallback.onClosed(reason)); 271 } 272 273 /** 274 * @hide 275 */ sendOobData(RangingDevice toDevice, byte[] data)276 void sendOobData(RangingDevice toDevice, byte[] data) { 277 if (!mTransportHandles.containsKey(toDevice)) { 278 Log.e(TAG, "TransportHandle not found for session: " + mSessionHandle + ", device: " 279 + toDevice); 280 } 281 mTransportHandles.get(toDevice).sendData(data); 282 } 283 284 @RequiresPermission(Manifest.permission.RANGING) 285 @Override close()286 public void close() { 287 stop(); 288 } 289 290 /** 291 * Callback interface for receiving ranging session events. 292 */ 293 public interface Callback { 294 /** 295 * @hide 296 */ 297 @Retention(RetentionPolicy.SOURCE) 298 @Target({ElementType.TYPE_USE}) 299 @IntDef(value = { 300 REASON_UNKNOWN, 301 REASON_LOCAL_REQUEST, 302 REASON_REMOTE_REQUEST, 303 REASON_UNSUPPORTED, 304 REASON_SYSTEM_POLICY, 305 REASON_NO_PEERS_FOUND, 306 }) 307 @interface Reason { 308 } 309 310 /** 311 * Indicates that the session was closed due to an unknown reason. 312 */ 313 int REASON_UNKNOWN = 0; 314 315 /** 316 * Indicates that the session was closed because {@link AutoCloseable#close()} or 317 * {@link RangingSession#stop()} was called. 318 */ 319 int REASON_LOCAL_REQUEST = 1; 320 321 /** 322 * Indicates that the session was closed at the request of a remote peer. 323 */ 324 int REASON_REMOTE_REQUEST = 2; 325 326 /** 327 * Indicates that the session closed because the provided session parameters were not 328 * supported. 329 */ 330 int REASON_UNSUPPORTED = 3; 331 332 /** 333 * Indicates that the local system policy forced the session to close, such 334 * as power management policy, airplane mode etc. 335 */ 336 int REASON_SYSTEM_POLICY = 4; 337 338 /** 339 * Indicates that the session was closed because none of the specified peers were found. 340 */ 341 int REASON_NO_PEERS_FOUND = 5; 342 343 /** 344 * Called when the ranging session opens successfully. 345 */ onOpened()346 void onOpened(); 347 348 /** 349 * Called when the ranging session failed to open. 350 * 351 * @param reason the reason for the failure, limited to values defined by 352 * {@link Reason}. 353 */ onOpenFailed(@eason int reason)354 void onOpenFailed(@Reason int reason); 355 356 /** 357 * Called when ranging has started with a particular peer using a particular technology 358 * during an ongoing session. 359 * 360 * @param peer {@link RangingDevice} the peer with which ranging has started. 361 * @param technology {@link android.ranging.RangingManager.RangingTechnology} 362 * the ranging technology that started. 363 */ onStarted( @onNull RangingDevice peer, @RangingManager.RangingTechnology int technology)364 void onStarted( 365 @NonNull RangingDevice peer, @RangingManager.RangingTechnology int technology); 366 367 /** 368 * Called when ranging data has been received from a peer. 369 * 370 * @param peer {@link RangingDevice} the peer from which ranging data was received. 371 * @param data {@link RangingData} the received. 372 */ onResults(@onNull RangingDevice peer, @NonNull RangingData data)373 void onResults(@NonNull RangingDevice peer, @NonNull RangingData data); 374 375 /** 376 * Called when ranging has stopped with a particular peer using a particular technology 377 * during an ongoing session. 378 * 379 * @param peer {@link RangingDevice} the peer with which ranging has stopped. 380 * @param technology {@link android.ranging.RangingManager.RangingTechnology} 381 * the ranging technology that stopped. 382 */ onStopped( @onNull RangingDevice peer, @RangingManager.RangingTechnology int technology)383 void onStopped( 384 @NonNull RangingDevice peer, @RangingManager.RangingTechnology int technology); 385 386 /** 387 * Called when the ranging session has closed. 388 * 389 * @param reason the reason why the session was closed, limited to values 390 * defined by {@link Reason}. 391 */ onClosed(@eason int reason)392 void onClosed(@Reason int reason); 393 394 } 395 396 class TransportHandleReceiveCallback implements TransportHandle.ReceiveCallback { 397 398 private final android.ranging.oob.OobHandle mOobHandle; 399 TransportHandleReceiveCallback(RangingDevice device)400 TransportHandleReceiveCallback(RangingDevice device) { 401 mOobHandle = new OobHandle(mSessionHandle, device); 402 } 403 404 @Override onReceiveData(byte[] data)405 public void onReceiveData(byte[] data) { 406 mRangingSessionManager.oobDataReceived(mOobHandle, data); 407 } 408 409 @Override onSendFailed()410 public void onSendFailed() { 411 } 412 413 @Override onDisconnect()414 public void onDisconnect() { 415 mRangingSessionManager.deviceOobDisconnected(mOobHandle); 416 } 417 418 @Override onReconnect()419 public void onReconnect() { 420 mRangingSessionManager.deviceOobReconnected(mOobHandle); 421 } 422 423 @Override onClose()424 public void onClose() { 425 mRangingSessionManager.deviceOobClosed(mOobHandle); 426 } 427 } 428 429 @Override toString()430 public String toString() { 431 return "RangingSession{ " 432 + "mSessionHandle=" 433 + mSessionHandle 434 + ", mRangingAdapter=" 435 + mRangingAdapter 436 + ", mRangingSessionManager=" 437 + mRangingSessionManager 438 + ", mCallback=" 439 + mCallback 440 + ", mExecutor=" 441 + mExecutor 442 + ", mTransportHandles=" 443 + mTransportHandles 444 + " }"; 445 } 446 } 447