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