• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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