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 }