• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 com.android.server.ranging;
18 
19 import static android.ranging.oob.OobInitiatorRangingConfig.RANGING_MODE_AUTO;
20 import static android.ranging.oob.OobInitiatorRangingConfig.RANGING_MODE_FUSED;
21 import static android.ranging.oob.OobInitiatorRangingConfig.RANGING_MODE_HIGH_ACCURACY;
22 import static android.ranging.oob.OobInitiatorRangingConfig.RANGING_MODE_HIGH_ACCURACY_PREFERRED;
23 
24 import android.ranging.RangingCapabilities;
25 import android.ranging.RangingDevice;
26 import android.ranging.SessionConfig;
27 import android.ranging.SessionHandle;
28 import android.ranging.oob.OobInitiatorRangingConfig;
29 import android.util.Log;
30 import android.util.Pair;
31 
32 import androidx.annotation.NonNull;
33 
34 import com.android.server.ranging.RangingUtils.InternalReason;
35 import com.android.server.ranging.blerssi.BleRssiConfigSelector;
36 import com.android.server.ranging.cs.CsConfigSelector;
37 import com.android.server.ranging.oob.CapabilityResponseMessage;
38 import com.android.server.ranging.oob.MessageType;
39 import com.android.server.ranging.oob.OobHeader;
40 import com.android.server.ranging.oob.SetConfigurationMessage;
41 import com.android.server.ranging.oob.SetConfigurationMessage.TechnologyOobConfig;
42 import com.android.server.ranging.rtt.RttConfigSelector;
43 import com.android.server.ranging.session.RangingSessionConfig.TechnologyConfig;
44 import com.android.server.ranging.uwb.UwbConfigSelector;
45 
46 import com.google.common.collect.ImmutableList;
47 import com.google.common.collect.ImmutableMap;
48 import com.google.common.collect.ImmutableSet;
49 import com.google.common.collect.Maps;
50 
51 import java.util.Arrays;
52 import java.util.EnumMap;
53 import java.util.EnumSet;
54 import java.util.HashMap;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.function.Function;
59 import java.util.stream.Collectors;
60 
61 public class RangingEngine {
62     private static final String TAG = RangingEngine.class.getSimpleName();
63 
64     private final SessionConfig mSessionConfig;
65     private final OobInitiatorRangingConfig mOobConfig;
66     private final SessionHandle mSessionHandle;
67     private final ImmutableSet<RangingTechnology> mRequestedTechnologies;
68     private final Map<RangingDevice, EnumSet<RangingTechnology>> mPeerTechnologies;
69     private final RangingInjector mInjector;
70 
71     private final EnumMap<RangingTechnology, ConfigSelector> mConfigSelectors;
72 
73     public static class ConfigSelectionException extends Exception {
74         private final @InternalReason int mReason;
75 
ConfigSelectionException(String message, @InternalReason int reason)76         public ConfigSelectionException(String message, @InternalReason int reason) {
77             super(message);
78             mReason = reason;
79         }
80 
getReason()81         public @InternalReason int getReason() {
82             return mReason;
83         }
84     }
85 
86     public interface ConfigSelector {
addPeerCapabilities(@onNull RangingDevice peer, @NonNull CapabilityResponseMessage response)87         void addPeerCapabilities(@NonNull RangingDevice peer,
88                 @NonNull CapabilityResponseMessage response) throws ConfigSelectionException;
89 
hasPeersToConfigure()90         boolean hasPeersToConfigure();
91 
92         @NonNull Pair<
93                 ImmutableSet<TechnologyConfig>,
94                 ImmutableMap<RangingDevice, TechnologyOobConfig>
selectConfigs()95         > selectConfigs() throws ConfigSelectionException;
96     }
97 
98     public static class SelectedConfig {
99         private final ImmutableSet<TechnologyConfig> mLocalConfigs;
100         private final ImmutableMap<RangingDevice, SetConfigurationMessage> mPeerConfigMessages;
101 
SelectedConfig( ImmutableSet<TechnologyConfig> localConfigs, ImmutableMap<RangingDevice, SetConfigurationMessage> peerConfigMessages )102         private SelectedConfig(
103                 ImmutableSet<TechnologyConfig> localConfigs,
104                 ImmutableMap<RangingDevice, SetConfigurationMessage> peerConfigMessages
105         ) {
106             mLocalConfigs = localConfigs;
107             mPeerConfigMessages = peerConfigMessages;
108         }
109 
getLocalConfigs()110         public ImmutableSet<TechnologyConfig> getLocalConfigs() {
111             return mLocalConfigs;
112         }
113 
getPeerConfigMessages()114         public ImmutableMap<RangingDevice, SetConfigurationMessage> getPeerConfigMessages() {
115             return mPeerConfigMessages;
116         }
117     }
118 
RangingEngine( SessionConfig sessionConfig, OobInitiatorRangingConfig oobConfig, SessionHandle sessionHandle, RangingInjector injector )119     public RangingEngine(
120             SessionConfig sessionConfig, OobInitiatorRangingConfig oobConfig,
121             SessionHandle sessionHandle, RangingInjector injector
122     ) throws ConfigSelectionException {
123         mSessionConfig = sessionConfig;
124         mOobConfig = oobConfig;
125         mSessionHandle = sessionHandle;
126         mPeerTechnologies = new HashMap<>();
127         mConfigSelectors = Maps.newEnumMap(RangingTechnology.class);
128         mInjector = injector;
129 
130         ImmutableSet.Builder<RangingTechnology> toRequest = ImmutableSet.builder();
131         if (shouldRequest(RangingTechnology.UWB)) toRequest.add(RangingTechnology.UWB);
132         if (oobConfig.getRangingMode() != RANGING_MODE_HIGH_ACCURACY) {
133             for (RangingTechnology technology :
134                     Set.of(RangingTechnology.CS, RangingTechnology.RTT, RangingTechnology.RSSI)) {
135                 if (shouldRequest(technology)) toRequest.add(technology);
136             }
137         }
138         mRequestedTechnologies = toRequest.build();
139         if (mRequestedTechnologies.isEmpty()) {
140             throw new ConfigSelectionException(
141                     "No locally supported technologies are compatible with the provided config",
142                     InternalReason.UNSUPPORTED);
143         }
144     }
145 
getRequestedTechnologies()146     public ImmutableSet<RangingTechnology> getRequestedTechnologies() {
147         return mRequestedTechnologies;
148     }
149 
addPeerCapabilities( RangingDevice device, CapabilityResponseMessage capabilities )150     public void addPeerCapabilities(
151             RangingDevice device, CapabilityResponseMessage capabilities
152     ) throws ConfigSelectionException {
153         EnumSet<RangingTechnology> selectedTechnologies =
154                 selectTechnologiesToUseWithPeer(capabilities);
155         Log.v(TAG, "Selected technologies " + selectedTechnologies + " for peer " + device);
156 
157         for (RangingTechnology technology : selectedTechnologies) {
158             ConfigSelector selector = mConfigSelectors.get(technology);
159             if (selector == null) {
160                 throw new IllegalStateException(
161                         "Expected config selector to exist for mutually supported technology "
162                                 + technology);
163             }
164             selector.addPeerCapabilities(device, capabilities);
165         }
166         mPeerTechnologies.put(device, selectedTechnologies);
167     }
168 
selectConfigs()169     public SelectedConfig selectConfigs() throws ConfigSelectionException {
170         ImmutableSet.Builder<TechnologyConfig> localConfigs = ImmutableSet.builder();
171         Map<RangingDevice, SetConfigurationMessage.Builder> peerConfigs = new HashMap<>();
172 
173         for (RangingTechnology technology : mConfigSelectors.keySet()) {
174             ConfigSelector selector = mConfigSelectors.get(technology);
175             if (!selector.hasPeersToConfigure()) continue;
176 
177             Pair<ImmutableSet<TechnologyConfig>, ImmutableMap<RangingDevice, TechnologyOobConfig>>
178                     configs = selector.selectConfigs();
179 
180             localConfigs.addAll(configs.first);
181             configs.second.forEach((peer, config) ->
182                     peerConfigs.computeIfAbsent(peer,
183                             (unused) -> {
184                                 ImmutableList<RangingTechnology> peerTechnologies =
185                                         ImmutableList.copyOf(mPeerTechnologies.get(peer));
186                                 return SetConfigurationMessage.builder()
187                                         .setHeader(OobHeader.builder()
188                                                 .setMessageType(MessageType.SET_CONFIGURATION)
189                                                 .setVersion(OobHeader.OobVersion.CURRENT)
190                                                 .build())
191                                         .setRangingTechnologiesSet(peerTechnologies)
192                                         .setStartRangingList(peerTechnologies);
193                             }
194                     )
195                     .setTechnologyConfig(configs.second.get(peer)));
196         }
197 
198         return new SelectedConfig(
199                 localConfigs.build(),
200                 peerConfigs.keySet().stream().collect(
201                         ImmutableMap.toImmutableMap(
202                                 Function.identity(),
203                                 (peer) -> peerConfigs.get(peer).build())));
204     }
205 
shouldRequest(RangingTechnology technology)206     private boolean shouldRequest(RangingTechnology technology) {
207         @RangingCapabilities.RangingTechnologyAvailability int availability = mInjector
208                 .getCapabilitiesProvider()
209                 .getCapabilities()
210                 .getTechnologyAvailability().get(technology.getValue());
211 
212         return availability == RangingCapabilities.ENABLED
213                 || availability == RangingCapabilities.DISABLED_USER;
214     }
215 
216     /**
217      * If the {@param technology} is enabled and supported, create a config selector and add it to
218      * {@code mConfigSelectors}
219      * @return true if the {@param technology} is enabled and supported, false otherwise.
220      */
createConfigSelectorIfEnabledAndSupported(RangingTechnology technology)221     private boolean createConfigSelectorIfEnabledAndSupported(RangingTechnology technology) {
222         if (mConfigSelectors.containsKey(technology)) return true;
223         try {
224             mConfigSelectors.put(technology, createConfigSelector(technology));
225             return true;
226         } catch (ConfigSelectionException ignored) {
227             return false;
228         }
229     }
230 
createConfigSelector( RangingTechnology technology )231     private ConfigSelector createConfigSelector(
232             RangingTechnology technology
233     ) throws ConfigSelectionException {
234         RangingCapabilities capabilities = mInjector.getCapabilitiesProvider().getCapabilities();
235         return switch (technology) {
236             case UWB -> new UwbConfigSelector(
237                     mSessionConfig, mOobConfig, mSessionHandle, capabilities.getUwbCapabilities());
238             case CS -> new CsConfigSelector(
239                     mSessionConfig, mOobConfig, capabilities.getCsCapabilities());
240             case RTT -> new RttConfigSelector(
241                     mSessionConfig, mOobConfig, capabilities.getRttRangingCapabilities());
242             case RSSI -> new BleRssiConfigSelector(
243                     mSessionConfig, mOobConfig, capabilities.getBleRssiCapabilities());
244         };
245     }
246 
getPreferredTechnologyList()247     private List<RangingTechnology> getPreferredTechnologyList() {
248         String[] prefTechnologiesStringArray = mInjector
249                 .getDeviceConfigFacade()
250                 .getTechnologyPreferenceList();
251         return Arrays.stream(prefTechnologiesStringArray)
252                 .map(str -> RangingTechnology.fromName(str))
253                 .collect(Collectors.toUnmodifiableList());
254     }
255 
selectTechnologiesToUseWithPeer( CapabilityResponseMessage peerCapabilities )256     private EnumSet<RangingTechnology> selectTechnologiesToUseWithPeer(
257             CapabilityResponseMessage peerCapabilities
258     ) throws ConfigSelectionException {
259         EnumSet<RangingTechnology> selectable = EnumSet.noneOf(RangingTechnology.class);
260         selectable.addAll(peerCapabilities.getSupportedRangingTechnologies());
261 
262         // Skip CS if supported by the remote device but no Bluetooth bond is established.
263         if (selectable.contains(RangingTechnology.CS)
264                 && peerCapabilities.getCsCapabilities() != null
265                 && !mInjector.isRemoteDeviceBluetoothBonded(
266                 peerCapabilities.getCsCapabilities().getBluetoothAddress())
267         ) {
268             Log.v(TAG, RangingTechnology.CS + " is mutually supported, but skipping it because no "
269                     + "Bluetooth bond exists with peer");
270             selectable.remove(RangingTechnology.CS);
271         }
272 
273         if (selectable.isEmpty()) {
274             throw new ConfigSelectionException("Peer does not support any requested technologies",
275                     InternalReason.PEER_CAPABILITIES_MISMATCH);
276         }
277 
278         selectable = selectable
279                 .stream()
280                 .filter((technology -> mInjector
281                         .getCapabilitiesProvider()
282                         .getCapabilities()
283                         .getTechnologyAvailability()
284                         .get(technology.getValue()) == RangingCapabilities.ENABLED))
285                 .collect(Collectors.toCollection(() -> EnumSet.noneOf(RangingTechnology.class)));
286 
287         if (selectable.isEmpty()) {
288             throw new ConfigSelectionException(peerCapabilities.getSupportedRangingTechnologies()
289                     + " are mutually supported, but they are all disabled by the user so we cannot "
290                     + "proceed with ranging",
291                     InternalReason.SYSTEM_POLICY);
292         }
293 
294         selectable = selectable
295                 .stream()
296                 .filter(this::createConfigSelectorIfEnabledAndSupported)
297                 .collect(Collectors.toCollection(() -> EnumSet.noneOf(RangingTechnology.class)));
298 
299         if (selectable.isEmpty()) {
300             throw new ConfigSelectionException(
301                     "No mutually supported technologies are compatible with the provided config",
302                     InternalReason.UNSUPPORTED);
303         }
304 
305         EnumSet<RangingTechnology> selected = EnumSet.noneOf(RangingTechnology.class);
306         switch (mOobConfig.getRangingMode()) {
307             case RANGING_MODE_AUTO:
308             case RANGING_MODE_HIGH_ACCURACY_PREFERRED: {
309                 getPreferredTechnologyList()
310                         .stream()
311                         .filter(selectable::contains)
312                         .findFirst()
313                         .ifPresent(selected::add);
314                 break;
315             }
316             case RANGING_MODE_HIGH_ACCURACY: {
317                 if (selectable.contains(RangingTechnology.UWB)) {
318                     selected.add(RangingTechnology.UWB);
319                 }
320                 break;
321             }
322             case RANGING_MODE_FUSED: {
323                 selected.addAll(selectable);
324                 break;
325             }
326         }
327 
328         return selected;
329     }
330 }