• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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.server.ranging.uwb;
18 
19 import static android.ranging.RangingPreference.DEVICE_ROLE_RESPONDER;
20 import static android.ranging.oob.OobInitiatorRangingConfig.SECURITY_LEVEL_BASIC;
21 import static android.ranging.oob.OobInitiatorRangingConfig.SECURITY_LEVEL_SECURE;
22 import static android.ranging.raw.RawRangingDevice.UPDATE_RATE_FREQUENT;
23 import static android.ranging.raw.RawRangingDevice.UPDATE_RATE_INFREQUENT;
24 import static android.ranging.raw.RawRangingDevice.UPDATE_RATE_NORMAL;
25 import static android.ranging.uwb.UwbComplexChannel.UWB_CHANNEL_5;
26 import static android.ranging.uwb.UwbComplexChannel.UWB_CHANNEL_9;
27 import static android.ranging.uwb.UwbComplexChannel.UWB_PREAMBLE_CODE_INDEX_25;
28 import static android.ranging.uwb.UwbComplexChannel.UWB_PREAMBLE_CODE_INDEX_32;
29 import static android.ranging.uwb.UwbRangingParams.CONFIG_MULTICAST_DS_TWR;
30 import static android.ranging.uwb.UwbRangingParams.CONFIG_PROVISIONED_MULTICAST_DS_TWR;
31 import static android.ranging.uwb.UwbRangingParams.CONFIG_PROVISIONED_UNICAST_DS_TWR;
32 import static android.ranging.uwb.UwbRangingParams.CONFIG_PROVISIONED_UNICAST_DS_TWR_VERY_FAST;
33 import static android.ranging.uwb.UwbRangingParams.CONFIG_UNICAST_DS_TWR;
34 
35 import android.ranging.RangingDevice;
36 import android.ranging.SessionConfig;
37 import android.ranging.SessionHandle;
38 import android.ranging.oob.OobInitiatorRangingConfig;
39 import android.ranging.raw.RawRangingDevice;
40 import android.ranging.uwb.UwbAddress;
41 import android.ranging.uwb.UwbComplexChannel;
42 import android.ranging.uwb.UwbRangingCapabilities;
43 import android.ranging.uwb.UwbRangingParams;
44 import android.util.Log;
45 import android.util.Pair;
46 import android.util.Range;
47 
48 import androidx.annotation.NonNull;
49 import androidx.annotation.Nullable;
50 
51 import com.android.ranging.uwb.backend.internal.RangingTimingParams;
52 import com.android.ranging.uwb.backend.internal.Utils;
53 import com.android.server.ranging.RangingEngine;
54 import com.android.server.ranging.RangingEngine.ConfigSelectionException;
55 import com.android.server.ranging.RangingUtils.InternalReason;
56 import com.android.server.ranging.oob.CapabilityResponseMessage;
57 import com.android.server.ranging.oob.SetConfigurationMessage.TechnologyOobConfig;
58 import com.android.server.ranging.session.RangingSessionConfig.TechnologyConfig;
59 
60 import com.google.common.collect.BiMap;
61 import com.google.common.collect.HashBiMap;
62 import com.google.common.collect.ImmutableBiMap;
63 import com.google.common.collect.ImmutableMap;
64 import com.google.common.collect.ImmutableSet;
65 import com.google.common.collect.Sets;
66 
67 import java.util.Collections;
68 import java.util.HashSet;
69 import java.util.List;
70 import java.util.Random;
71 import java.util.Set;
72 import java.util.function.Function;
73 import java.util.stream.Collectors;
74 import java.util.stream.IntStream;
75 
76 /** Selects a {@link UwbConfig} from local and peer device capabilities */
77 public class UwbConfigSelector implements RangingEngine.ConfigSelector {
78     private static final String TAG = UwbConfigSelector.class.getSimpleName();
79 
80     private static final Set<@UwbComplexChannel.UwbPreambleCodeIndex Integer> HPRF_INDEXES =
81             IntStream.rangeClosed(UWB_PREAMBLE_CODE_INDEX_25, UWB_PREAMBLE_CODE_INDEX_32)
82                         .boxed()
83                         .collect(Collectors.toSet());
84 
85     private final SessionConfig mSessionConfig;
86     private final OobInitiatorRangingConfig mOobConfig;
87     private final SessionHandle mSessionHandle;
88     private final BiMap<RangingDevice, UwbAddress> mPeerAddresses;
89 
90     private final Set<@UwbRangingParams.ConfigId Integer> mConfigIds;
91     private final Set<@UwbComplexChannel.UwbChannel Integer> mChannels;
92     private final Set<@UwbComplexChannel.UwbPreambleCodeIndex Integer> mPreambleIndexes;
93     private @UwbRangingParams.SlotDuration int mMinSlotDurationMs;
94     private long mMinRangingIntervalMs;
95     private final String mCountryCode;
96 
isCapableOfConfig( @onNull SessionConfig sessionConfig, @NonNull OobInitiatorRangingConfig oobConfig, @Nullable UwbRangingCapabilities capabilities )97     private static boolean isCapableOfConfig(
98             @NonNull SessionConfig sessionConfig, @NonNull OobInitiatorRangingConfig oobConfig,
99             @Nullable UwbRangingCapabilities capabilities
100     ) {
101         if (capabilities == null) return false;
102 
103         boolean isMulticast = oobConfig.getDeviceHandles().size() > 1;
104 
105         if (oobConfig.getSecurityLevel() == SECURITY_LEVEL_BASIC
106                 && isMulticast
107                 && !capabilities.getSupportedConfigIds().contains(CONFIG_MULTICAST_DS_TWR)
108         ) return false;
109 
110         if (oobConfig.getSecurityLevel() == SECURITY_LEVEL_BASIC
111                 && !isMulticast
112                 && !capabilities.getSupportedConfigIds().contains(CONFIG_UNICAST_DS_TWR)
113         ) return false;
114 
115         if (oobConfig.getSecurityLevel() == SECURITY_LEVEL_SECURE
116                 && isMulticast
117                 && !capabilities
118                         .getSupportedConfigIds().contains(CONFIG_PROVISIONED_MULTICAST_DS_TWR)
119         ) return false;
120 
121         if (oobConfig.getSecurityLevel() == SECURITY_LEVEL_SECURE
122                 && !isMulticast
123                 && !capabilities
124                         .getSupportedConfigIds().contains(CONFIG_PROVISIONED_UNICAST_DS_TWR)
125         ) return false;
126 
127         // TODO: If we add support for AoA via ARCore in the future, this will need to be changed.
128         if (sessionConfig.isAngleOfArrivalNeeded() && !capabilities.isAzimuthalAngleSupported())
129             return false;
130 
131         return true;
132     }
133 
UwbConfigSelector( @onNull SessionConfig sessionConfig, @NonNull OobInitiatorRangingConfig oobConfig, @NonNull SessionHandle sessionHandle, @Nullable UwbRangingCapabilities capabilities )134     public UwbConfigSelector(
135             @NonNull SessionConfig sessionConfig,
136             @NonNull OobInitiatorRangingConfig oobConfig,
137             @NonNull SessionHandle sessionHandle,
138             @Nullable UwbRangingCapabilities capabilities
139     ) throws ConfigSelectionException {
140         if (!isCapableOfConfig(sessionConfig, oobConfig, capabilities)) {
141             throw new ConfigSelectionException("Local device is incapable of provided UWB config",
142                     InternalReason.UNSUPPORTED);
143         }
144 
145         mSessionConfig = sessionConfig;
146         mOobConfig = oobConfig;
147         mSessionHandle = sessionHandle;
148         mPeerAddresses = HashBiMap.create();
149         mConfigIds = new HashSet<>(capabilities.getSupportedConfigIds());
150         // TODO(406020462): Temporary debug log
151         Log.d(TAG, "Locally supported channels: " + capabilities.getSupportedChannels());
152         mChannels = new HashSet<>(capabilities.getSupportedChannels());
153         mPreambleIndexes = new HashSet<>(capabilities.getSupportedPreambleIndexes());
154         mMinSlotDurationMs = Collections.min(capabilities.getSupportedSlotDurations());
155         mMinRangingIntervalMs = capabilities.getMinimumRangingInterval().toMillis();
156         mCountryCode = capabilities.getCountryCode();
157     }
158 
159     /**
160      * @throws ConfigSelectionException if the provided capabilities are incompatible with the
161      *                                  configuration
162      */
163     @Override
addPeerCapabilities( @onNull RangingDevice peer, @NonNull CapabilityResponseMessage response )164     public void addPeerCapabilities(
165             @NonNull RangingDevice peer, @NonNull CapabilityResponseMessage response
166     ) throws ConfigSelectionException {
167         UwbOobCapabilities capabilities = response.getUwbCapabilities();
168         if (capabilities == null) {
169             throw new ConfigSelectionException("Peer " + peer + " does not support UWB",
170                     InternalReason.PEER_CAPABILITIES_MISMATCH);
171         }
172         if (!capabilities.getSupportedDeviceRole().contains(UwbOobConfig.OobDeviceRole.INITIATOR)) {
173             throw new ConfigSelectionException("Peer does not support initiator role",
174                     InternalReason.PEER_CAPABILITIES_MISMATCH);
175         }
176 
177         mPeerAddresses.put(peer, capabilities.getUwbAddress());
178         mConfigIds.retainAll(capabilities.getSupportedConfigIds());
179         mChannels.retainAll(capabilities.getSupportedChannels());
180         // TODO(406020462): Temporary debug log
181         Log.d(TAG, "Add peer with supported channels " + capabilities.getSupportedChannels()
182                 + " set of selectable channels updated to " + mChannels);
183         mPreambleIndexes.retainAll(capabilities.getSupportedPreambleIndexes());
184         mMinSlotDurationMs = Math.max(
185                 mMinSlotDurationMs, capabilities.getMinimumSlotDurationMs());
186         mMinRangingIntervalMs = Math.max(
187                 mMinRangingIntervalMs, capabilities.getMinimumRangingIntervalMs());
188     }
189 
190     @Override
hasPeersToConfigure()191     public boolean hasPeersToConfigure() {
192         return !mPeerAddresses.isEmpty();
193     }
194 
195     @Override
196     public @NonNull Pair<
197             ImmutableSet<TechnologyConfig>,
198             ImmutableMap<RangingDevice, TechnologyOobConfig>
selectConfigs()199     > selectConfigs() throws ConfigSelectionException {
200         SelectedUwbConfig configs = new SelectedUwbConfig();
201         return Pair.create(configs.getLocalConfigs(), configs.getPeerConfigs());
202     }
203 
204     private class SelectedUwbConfig {
205         private final int mSessionId;
206         private final UwbAddress mLocalAddress;
207         private final @UwbRangingParams.ConfigId int mConfigId;
208         private final @UwbComplexChannel.UwbChannel int mChannel;
209         private final @UwbComplexChannel.UwbPreambleCodeIndex int mPreambleIndex;
210         private final @RawRangingDevice.RangingUpdateRate int mRangingUpdateRate;
211         private final byte[] mSessionKeyInfo;
212 
SelectedUwbConfig()213         SelectedUwbConfig() throws ConfigSelectionException {
214             mSessionId = mSessionHandle.hashCode();
215             mLocalAddress = UwbAddress.createRandomShortAddress();
216             mConfigId = selectConfigId();
217             mChannel = selectChannel();
218             mPreambleIndex = selectPreambleIndex();
219             mRangingUpdateRate = selectRangingUpdateRate();
220             mSessionKeyInfo = selectSessionKeyInfo();
221         }
222 
223         // For now, each GRAPI responder will be a UWB initiator for a unicast session. In the
224         // future we can look into combining these into a single multicast session somehow.
225 
getLocalConfigs()226         public @NonNull ImmutableSet<TechnologyConfig> getLocalConfigs() {
227             return mPeerAddresses.keySet().stream().map(
228                     (device) -> new UwbConfig.Builder(
229                             new UwbRangingParams.Builder(
230                                     mSessionId, mConfigId, mLocalAddress,
231                                     mPeerAddresses.get(device))
232                                     .setSessionKeyInfo(mSessionKeyInfo)
233                                     .setComplexChannel(new UwbComplexChannel.Builder()
234                                             .setChannel(mChannel)
235                                             .setPreambleIndex(mPreambleIndex)
236                                             .build())
237                                     .setRangingUpdateRate(mRangingUpdateRate)
238                                     .setSlotDuration(mMinSlotDurationMs)
239                                     .build())
240                             .setSessionConfig(mSessionConfig)
241                             .setDeviceRole(DEVICE_ROLE_RESPONDER)
242                             .setPeerAddresses(ImmutableBiMap.of(device, mPeerAddresses.get(device)))
243                             .build())
244                     .collect(ImmutableSet.toImmutableSet());
245         }
246 
getPeerConfigs()247         public @NonNull ImmutableMap<RangingDevice, TechnologyOobConfig> getPeerConfigs() {
248             UwbOobConfig config = UwbOobConfig.builder()
249                     .setUwbAddress(mLocalAddress)
250                     .setSessionId(mSessionId)
251                     .setSelectedConfigId(mConfigId)
252                     .setSelectedChannel(mChannel)
253                     .setSelectedPreambleIndex(mPreambleIndex)
254                     .setSelectedRangingIntervalMs(Utils.getRangingTimingParams((int) mConfigId)
255                             .getRangingInterval((int) mRangingUpdateRate))
256                     .setSelectedSlotDurationMs(mMinSlotDurationMs)
257                     .setSessionKey(mSessionKeyInfo)
258                     .setCountryCode(mCountryCode)
259                     .setDeviceRole(UwbOobConfig.OobDeviceRole.INITIATOR)
260                     .setDeviceMode(UwbOobConfig.OobDeviceMode.CONTROLLER)
261                     .build();
262 
263             return mPeerAddresses.keySet().stream()
264                     .collect(ImmutableMap.toImmutableMap(Function.identity(), (unused) -> config));
265         }
266     }
267 
selectConfigId()268     private @UwbRangingParams.ConfigId int selectConfigId() throws ConfigSelectionException {
269         if (mOobConfig.getSecurityLevel() == SECURITY_LEVEL_BASIC) {
270             if (mConfigIds.contains(CONFIG_UNICAST_DS_TWR)) {
271                 return CONFIG_UNICAST_DS_TWR;
272             }
273         } else if (mOobConfig.getSecurityLevel() == SECURITY_LEVEL_SECURE) {
274             if (mConfigIds.contains(CONFIG_PROVISIONED_UNICAST_DS_TWR_VERY_FAST)) {
275                 return CONFIG_PROVISIONED_UNICAST_DS_TWR_VERY_FAST;
276             } else if (mConfigIds.contains(CONFIG_PROVISIONED_UNICAST_DS_TWR)) {
277                 return CONFIG_PROVISIONED_UNICAST_DS_TWR;
278             }
279         }
280 
281         throw new ConfigSelectionException("Failed to find agreeable config id",
282                 InternalReason.PEER_CAPABILITIES_MISMATCH);
283     }
284 
selectSessionKeyInfo()285     private byte[] selectSessionKeyInfo() {
286         byte[] sessionKeyInfo;
287         if (mOobConfig.getSecurityLevel() == SECURITY_LEVEL_BASIC) {
288             sessionKeyInfo = new byte[8];
289         } else {
290             sessionKeyInfo = new byte[16];
291         }
292         new Random().nextBytes(sessionKeyInfo);
293         return sessionKeyInfo;
294     }
295 
selectChannel()296     private @UwbComplexChannel.UwbChannel int selectChannel() throws ConfigSelectionException {
297         if (mChannels.contains(UWB_CHANNEL_9)) {
298             return UWB_CHANNEL_9;
299         } else if (mChannels.contains(UWB_CHANNEL_5)) {
300             return UWB_CHANNEL_5;
301         } else {
302             // TODO(b/406020462): This is a temporary workaround for the 25Q2 release. Undo this
303             //  change once release snapshot is taken so we can continue to debug the failure.
304             Log.e(TAG, "Not all peers support uwb channel 9 or 5, if you see this, please attach "
305                     + "logcat to b/406020462");
306             return UWB_CHANNEL_9;
307             // throw new ConfigSelectionException("Not all peers support uwb channel 9 or 5",
308             //         InternalReason.PEER_CAPABILITIES_MISMATCH);
309         }
310     }
311 
selectPreambleIndex()312     private @UwbComplexChannel.UwbPreambleCodeIndex int selectPreambleIndex()
313             throws ConfigSelectionException {
314 
315         if (mPreambleIndexes.isEmpty()) {
316             throw new ConfigSelectionException(
317                     "Peers do not share support for any uwb preamble indexes",
318                     InternalReason.PEER_CAPABILITIES_MISMATCH);
319         }
320         Set<@UwbComplexChannel.UwbPreambleCodeIndex Integer> supportedHprfIndexes =
321                 Sets.intersection(mPreambleIndexes, HPRF_INDEXES);
322 
323         // Prioritize HPRF indexes
324         if (!supportedHprfIndexes.isEmpty()) {
325             return List.copyOf(supportedHprfIndexes).get(
326                     new Random().nextInt(supportedHprfIndexes.size()));
327         } else {
328             return List.copyOf(mPreambleIndexes).get(
329                     new Random().nextInt(mPreambleIndexes.size()));
330         }
331     }
332 
selectRangingUpdateRate()333     private @RawRangingDevice.RangingUpdateRate int selectRangingUpdateRate()
334             throws ConfigSelectionException {
335 
336         @UwbRangingParams.ConfigId int configId = selectConfigId();
337         RangingTimingParams timings = Utils.getRangingTimingParams((int) configId);
338 
339         Range<Long> intervalsMs;
340         try {
341             intervalsMs = Range.create(
342                     Math.max(mMinRangingIntervalMs, timings.getRangingIntervalFast()),
343                     (long) timings.getRangingIntervalInfrequent());
344         } catch (IllegalArgumentException unused) {
345             throw new ConfigSelectionException("Timings supported by selected config id " + configId
346                     + " are incompatible with local or peer ranging interval capabilities",
347                     InternalReason.PEER_CAPABILITIES_MISMATCH);
348         }
349 
350         // The code below is a little hard to read, but there are 3 cases:
351         //   1. If configured range overlaps with the intervals that devices are capable of, select
352         //      fastest supported.
353         //   2. If configured range lies entirely above the intervals that devices are capable of,
354         //      select UPDATE_RATE_INFREQUENT.
355         //   3. If configured range lies entirely below the intervals that devices are capable of,
356         //      select fastest supported.
357         try {
358             intervalsMs = intervalsMs.intersect(
359                     mOobConfig.getFastestRangingInterval().toMillis(),
360                     mOobConfig.getSlowestRangingInterval().toMillis());
361         } catch (IllegalArgumentException ignored) {
362             if (mOobConfig.getFastestRangingInterval().toMillis() > intervalsMs.getUpper()) {
363                 return UPDATE_RATE_INFREQUENT;
364             }
365         }
366 
367         return getFastestUpdateRateInRange(intervalsMs, timings);
368     }
369 
getFastestUpdateRateInRange( Range<Long> rangeMs, RangingTimingParams timings )370     private @RawRangingDevice.RangingUpdateRate int getFastestUpdateRateInRange(
371             Range<Long> rangeMs, RangingTimingParams timings
372     ) throws ConfigSelectionException {
373         if (rangeMs.contains((long) timings.getRangingIntervalFast())) {
374             return UPDATE_RATE_FREQUENT;
375         } else if (rangeMs.contains((long) timings.getRangingIntervalNormal())) {
376             return UPDATE_RATE_NORMAL;
377         } else if (rangeMs.contains((long) timings.getRangingIntervalInfrequent())) {
378             return UPDATE_RATE_INFREQUENT;
379         } else {
380             throw new ConfigSelectionException(
381                     "Could not find update rate within the "
382                             + "requested range that satisfies all peer capabilities",
383                     InternalReason.PEER_CAPABILITIES_MISMATCH);
384         }
385     }
386 }
387