1 /* 2 * Copyright 2018 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.wifi; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.net.MacAddress; 23 import android.net.wifi.ScanResult; 24 import android.net.wifi.SecurityParams; 25 import android.net.wifi.WifiAnnotations; 26 import android.net.wifi.WifiConfiguration; 27 import android.util.ArrayMap; 28 import android.util.Log; 29 30 import com.android.internal.util.Preconditions; 31 import com.android.server.wifi.proto.WifiScoreCardProto; 32 33 import java.util.ArrayList; 34 import java.util.Collection; 35 import java.util.Collections; 36 import java.util.List; 37 import java.util.Map; 38 import java.util.Objects; 39 import java.util.StringJoiner; 40 import java.util.stream.Collectors; 41 42 /** 43 * Candidates for network selection 44 */ 45 public class WifiCandidates { 46 private static final String TAG = "WifiCandidates"; 47 WifiCandidates(@onNull WifiScoreCard wifiScoreCard, @NonNull Context context)48 public WifiCandidates(@NonNull WifiScoreCard wifiScoreCard, @NonNull Context context) { 49 this(wifiScoreCard, context, Collections.EMPTY_LIST); 50 } 51 WifiCandidates(@onNull WifiScoreCard wifiScoreCard, @NonNull Context context, @NonNull List<Candidate> candidates)52 public WifiCandidates(@NonNull WifiScoreCard wifiScoreCard, @NonNull Context context, 53 @NonNull List<Candidate> candidates) { 54 mWifiScoreCard = Preconditions.checkNotNull(wifiScoreCard); 55 mContext = context; 56 for (Candidate c : candidates) { 57 mCandidates.put(c.getKey(), c); 58 } 59 } 60 61 private final WifiScoreCard mWifiScoreCard; 62 private final Context mContext; 63 64 /** 65 * Represents a connectable candidate. 66 */ 67 public interface Candidate { 68 /** 69 * Gets the Key, which contains the SSID, BSSID, security type, and config id. 70 * 71 * Generally, a CandidateScorer should not need to use this. 72 */ getKey()73 @Nullable Key getKey(); 74 75 /** 76 * Gets the config id. 77 */ getNetworkConfigId()78 int getNetworkConfigId(); 79 /** 80 * Returns true for an open network. 81 */ isOpenNetwork()82 boolean isOpenNetwork(); 83 /** 84 * Returns true for a passpoint network. 85 */ isPasspoint()86 boolean isPasspoint(); 87 /** 88 * Returns true for an ephemeral network. 89 */ isEphemeral()90 boolean isEphemeral(); 91 /** 92 * Returns true for a trusted network. 93 */ isTrusted()94 boolean isTrusted(); 95 /** 96 * Returns true for a oem paid network. 97 */ isOemPaid()98 boolean isOemPaid(); 99 /** 100 * Returns true for a oem private network. 101 */ isOemPrivate()102 boolean isOemPrivate(); 103 104 /** 105 * Returns true if suggestion came from a carrier or privileged app. 106 */ isCarrierOrPrivileged()107 boolean isCarrierOrPrivileged(); 108 /** 109 * Returns true for a metered network. 110 */ isMetered()111 boolean isMetered(); 112 113 /** 114 * Returns true if network doesn't have internet access during last connection 115 */ hasNoInternetAccess()116 boolean hasNoInternetAccess(); 117 118 /** 119 * Returns true if network is expected not to have Internet access 120 * (e.g., a wireless printer, a Chromecast hotspot, etc.). 121 */ isNoInternetAccessExpected()122 boolean isNoInternetAccessExpected(); 123 124 /** 125 * Returns the ID of the nominator that provided the candidate. 126 */ 127 @WifiNetworkSelector.NetworkNominator.NominatorId getNominatorId()128 int getNominatorId(); 129 130 /** 131 * Returns true if the candidate is in the same network as the 132 * current connection. 133 */ isCurrentNetwork()134 boolean isCurrentNetwork(); 135 /** 136 * Return true if the candidate is currently connected. 137 */ isCurrentBssid()138 boolean isCurrentBssid(); 139 /** 140 * Returns a value between 0 and 1. 141 * 142 * 1.0 means the network was recently selected by the user or an app. 143 * 0.0 means not recently selected by user or app. 144 */ getLastSelectionWeight()145 double getLastSelectionWeight(); 146 /** 147 * Returns true if the network was selected by the user. 148 */ isUserSelected()149 boolean isUserSelected(); 150 /** 151 * Gets the scan RSSI. 152 */ getScanRssi()153 int getScanRssi(); 154 /** 155 * Gets the scan frequency. 156 */ getFrequency()157 int getFrequency(); 158 159 /** 160 * Gets the channel width. 161 */ getChannelWidth()162 @WifiAnnotations.ChannelWidth int getChannelWidth(); 163 /** 164 * Gets the predicted throughput in Mbps. 165 */ getPredictedThroughputMbps()166 int getPredictedThroughputMbps(); 167 168 /** 169 * Gets the predicted multi-link throughput in Mbps. 170 */ getPredictedMultiLinkThroughputMbps()171 int getPredictedMultiLinkThroughputMbps(); 172 173 /** 174 * Sets the predicted multi-link throughput in Mbps. 175 */ setPredictedMultiLinkThroughputMbps(int throughput)176 void setPredictedMultiLinkThroughputMbps(int throughput); 177 178 /** 179 * Estimated probability of getting internet access (percent 0-100). 180 */ getEstimatedPercentInternetAvailability()181 int getEstimatedPercentInternetAvailability(); 182 183 /** 184 * If the candidate is MLO capable, return the AP MLD MAC address. 185 * 186 * @return Mac address of the AP MLD. 187 */ getApMldMacAddress()188 MacAddress getApMldMacAddress(); 189 190 /** 191 * Gets the number of reboots since the WifiConfiguration is last connected or updated. 192 */ getNumRebootsSinceLastUse()193 int getNumRebootsSinceLastUse(); 194 195 /** 196 * Gets statistics from the scorecard. 197 */ getEventStatistics(WifiScoreCardProto.Event event)198 @Nullable WifiScoreCardProto.Signal getEventStatistics(WifiScoreCardProto.Event event); 199 200 /** 201 * Returns true for a restricted network. 202 */ isRestricted()203 boolean isRestricted(); 204 205 /** 206 * Returns true if the candidate is a multi-link capable. 207 * 208 * @return true or false. 209 */ isMultiLinkCapable()210 boolean isMultiLinkCapable(); 211 212 /** 213 * Returns true if the candidate is Local-Only due to Ip Provisioning Timeout. 214 * 215 * @return true or false. 216 */ isIpProvisioningTimedOut()217 boolean isIpProvisioningTimedOut(); 218 } 219 220 /** 221 * Represents a connectable candidate 222 */ 223 private static class CandidateImpl implements Candidate { 224 private final Key mKey; // SSID/sectype/BSSID/configId 225 private final @WifiNetworkSelector.NetworkNominator.NominatorId int mNominatorId; 226 private final int mScanRssi; 227 private final int mFrequency; 228 private final int mChannelWidth; 229 private final double mLastSelectionWeight; 230 private final WifiScoreCard.PerBssid mPerBssid; // For accessing the scorecard entry 231 private final boolean mIsUserSelected; 232 private final boolean mIsCurrentNetwork; 233 private final boolean mIsCurrentBssid; 234 private final boolean mIsMetered; 235 private final boolean mHasNoInternetAccess; 236 private final boolean mIsNoInternetAccessExpected; 237 private final boolean mIsOpenNetwork; 238 private final boolean mPasspoint; 239 private final boolean mEphemeral; 240 private final boolean mTrusted; 241 private final boolean mRestricted; 242 private final boolean mOemPaid; 243 private final boolean mOemPrivate; 244 private final boolean mCarrierOrPrivileged; 245 private final int mPredictedThroughputMbps; 246 private int mPredictedMultiLinkThroughputMbps; 247 private final int mNumRebootsSinceLastUse; 248 private final int mEstimatedPercentInternetAvailability; 249 private final MacAddress mApMldMacAddress; 250 private final boolean mIpProvisioningTimedOut; 251 CandidateImpl(Key key, WifiConfiguration config, WifiScoreCard.PerBssid perBssid, @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId, int scanRssi, int frequency, int channelWidth, double lastSelectionWeight, boolean isUserSelected, boolean isCurrentNetwork, boolean isCurrentBssid, boolean isMetered, boolean isCarrierOrPrivileged, int predictedThroughputMbps, MacAddress apMldMacAddress)252 CandidateImpl(Key key, WifiConfiguration config, 253 WifiScoreCard.PerBssid perBssid, 254 @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId, 255 int scanRssi, 256 int frequency, 257 int channelWidth, 258 double lastSelectionWeight, 259 boolean isUserSelected, 260 boolean isCurrentNetwork, 261 boolean isCurrentBssid, 262 boolean isMetered, 263 boolean isCarrierOrPrivileged, 264 int predictedThroughputMbps, 265 MacAddress apMldMacAddress) { 266 this.mKey = key; 267 this.mNominatorId = nominatorId; 268 this.mScanRssi = scanRssi; 269 this.mFrequency = frequency; 270 this.mChannelWidth = channelWidth; 271 this.mPerBssid = perBssid; 272 this.mLastSelectionWeight = lastSelectionWeight; 273 this.mIsUserSelected = isUserSelected; 274 this.mIsCurrentNetwork = isCurrentNetwork; 275 this.mIsCurrentBssid = isCurrentBssid; 276 this.mIsMetered = isMetered; 277 this.mHasNoInternetAccess = config.hasNoInternetAccess(); 278 this.mIsNoInternetAccessExpected = config.isNoInternetAccessExpected(); 279 this.mIsOpenNetwork = WifiConfigurationUtil.isConfigForOpenNetwork(config); 280 this.mPasspoint = config.isPasspoint(); 281 this.mEphemeral = config.isEphemeral(); 282 this.mTrusted = config.trusted; 283 this.mOemPaid = config.oemPaid; 284 this.mOemPrivate = config.oemPrivate; 285 this.mCarrierOrPrivileged = isCarrierOrPrivileged; 286 this.mPredictedThroughputMbps = predictedThroughputMbps; 287 this.mNumRebootsSinceLastUse = config.numRebootsSinceLastUse; 288 this.mEstimatedPercentInternetAvailability = perBssid == null ? 50 : 289 perBssid.estimatePercentInternetAvailability(); 290 this.mRestricted = config.restricted; 291 this.mPredictedMultiLinkThroughputMbps = 0; 292 this.mApMldMacAddress = apMldMacAddress; 293 this.mIpProvisioningTimedOut = config.isIpProvisioningTimedOut(); 294 } 295 296 @Override getKey()297 public Key getKey() { 298 return mKey; 299 } 300 301 @Override getNetworkConfigId()302 public int getNetworkConfigId() { 303 return mKey.networkId; 304 } 305 306 @Override isOpenNetwork()307 public boolean isOpenNetwork() { 308 return mIsOpenNetwork; 309 } 310 311 @Override isPasspoint()312 public boolean isPasspoint() { 313 return mPasspoint; 314 } 315 316 @Override isEphemeral()317 public boolean isEphemeral() { 318 return mEphemeral; 319 } 320 321 @Override isTrusted()322 public boolean isTrusted() { 323 return mTrusted; 324 } 325 326 @Override isRestricted()327 public boolean isRestricted() { 328 return mRestricted; 329 } 330 331 @Override isMultiLinkCapable()332 public boolean isMultiLinkCapable() { 333 return (mApMldMacAddress != null); 334 } 335 336 @Override isOemPaid()337 public boolean isOemPaid() { 338 return mOemPaid; 339 } 340 341 @Override isOemPrivate()342 public boolean isOemPrivate() { 343 return mOemPrivate; 344 } 345 346 @Override isCarrierOrPrivileged()347 public boolean isCarrierOrPrivileged() { 348 return mCarrierOrPrivileged; 349 } 350 351 @Override isMetered()352 public boolean isMetered() { 353 return mIsMetered; 354 } 355 356 @Override hasNoInternetAccess()357 public boolean hasNoInternetAccess() { 358 return mHasNoInternetAccess; 359 } 360 361 @Override isNoInternetAccessExpected()362 public boolean isNoInternetAccessExpected() { 363 return mIsNoInternetAccessExpected; 364 } 365 366 @Override getNominatorId()367 public @WifiNetworkSelector.NetworkNominator.NominatorId int getNominatorId() { 368 return mNominatorId; 369 } 370 371 @Override getLastSelectionWeight()372 public double getLastSelectionWeight() { 373 return mLastSelectionWeight; 374 } 375 376 @Override isUserSelected()377 public boolean isUserSelected() { 378 return mIsUserSelected; 379 } 380 381 @Override isCurrentNetwork()382 public boolean isCurrentNetwork() { 383 return mIsCurrentNetwork; 384 } 385 386 @Override isCurrentBssid()387 public boolean isCurrentBssid() { 388 return mIsCurrentBssid; 389 } 390 391 @Override getScanRssi()392 public int getScanRssi() { 393 return mScanRssi; 394 } 395 396 @Override getFrequency()397 public int getFrequency() { 398 return mFrequency; 399 } 400 401 @Override getChannelWidth()402 public int getChannelWidth() { 403 return mChannelWidth; 404 } 405 406 @Override getPredictedThroughputMbps()407 public int getPredictedThroughputMbps() { 408 return mPredictedThroughputMbps; 409 } 410 411 @Override getPredictedMultiLinkThroughputMbps()412 public int getPredictedMultiLinkThroughputMbps() { 413 return mPredictedMultiLinkThroughputMbps; 414 } 415 416 @Override setPredictedMultiLinkThroughputMbps(int throughput)417 public void setPredictedMultiLinkThroughputMbps(int throughput) { 418 mPredictedMultiLinkThroughputMbps = throughput; 419 } 420 421 @Override getNumRebootsSinceLastUse()422 public int getNumRebootsSinceLastUse() { 423 return mNumRebootsSinceLastUse; 424 } 425 426 @Override getEstimatedPercentInternetAvailability()427 public int getEstimatedPercentInternetAvailability() { 428 return mEstimatedPercentInternetAvailability; 429 } 430 431 @Override getApMldMacAddress()432 public MacAddress getApMldMacAddress() { 433 return mApMldMacAddress; 434 } 435 436 @Override isIpProvisioningTimedOut()437 public boolean isIpProvisioningTimedOut() { 438 return mIpProvisioningTimedOut; 439 } 440 441 /** 442 * Accesses statistical information from the score card 443 */ 444 @Override getEventStatistics(WifiScoreCardProto.Event event)445 public WifiScoreCardProto.Signal getEventStatistics(WifiScoreCardProto.Event event) { 446 if (mPerBssid == null) return null; 447 WifiScoreCard.PerSignal perSignal = mPerBssid.lookupSignal(event, getFrequency()); 448 if (perSignal == null) return null; 449 return perSignal.toSignal(); 450 } 451 452 @Override toString()453 public String toString() { 454 Key key = getKey(); 455 String lastSelectionWeightString = ""; 456 if (getLastSelectionWeight() != 0.0) { 457 // Round this to 3 places 458 lastSelectionWeightString = "lastSelectionWeight = " 459 + Math.round(getLastSelectionWeight() * 1000.0) / 1000.0 460 + ", "; 461 } 462 return "Candidate { " 463 + "config = " + getNetworkConfigId() + ", " 464 + "bssid = " + key.bssid + ", " 465 + "freq = " + getFrequency() + ", " 466 + "channelWidth = " + getChannelWidth() + ", " 467 + "rssi = " + getScanRssi() + ", " 468 + "Mbps = " + getPredictedThroughputMbps() + ", " 469 + "nominator = " + getNominatorId() + ", " 470 + "pInternet = " + getEstimatedPercentInternetAvailability() + ", " 471 + "numRebootsSinceLastUse = " + getNumRebootsSinceLastUse() + ", " 472 + lastSelectionWeightString 473 + (isCurrentBssid() ? "connected, " : "") 474 + (isCurrentNetwork() ? "current, " : "") 475 + (isEphemeral() ? "ephemeral" : "saved") + ", " 476 + (isTrusted() ? "trusted, " : "") 477 + (isRestricted() ? "restricted, " : "") 478 + (isOemPaid() ? "oemPaid, " : "") 479 + (isOemPrivate() ? "oemPrivate, " : "") 480 + (isCarrierOrPrivileged() ? "priv, " : "") 481 + (isMetered() ? "metered, " : "") 482 + (hasNoInternetAccess() ? "noInternet, " : "") 483 + (isNoInternetAccessExpected() ? "noInternetExpected, " : "") 484 + (isPasspoint() ? "passpoint, " : "") 485 + (isOpenNetwork() ? "open" : "secure") 486 + " }"; 487 } 488 } 489 490 /** 491 * Represents a scoring function 492 */ 493 public interface CandidateScorer { 494 /** 495 * The scorer's name, and perhaps important parameterization/version. 496 */ getIdentifier()497 String getIdentifier(); 498 499 /** 500 * Calculates the best score for a collection of candidates. 501 */ scoreCandidates(@onNull Collection<Candidate> candidates)502 @Nullable ScoredCandidate scoreCandidates(@NonNull Collection<Candidate> candidates); 503 504 } 505 506 /** 507 * Represents a candidate with a real-valued score, along with an error estimate. 508 * 509 * Larger values reflect more desirable candidates. The range is arbitrary, 510 * because scores generated by different sources are not compared with each 511 * other. 512 * 513 * The error estimate is on the same scale as the value, and should 514 * always be strictly positive. For instance, it might be the standard deviation. 515 */ 516 public static class ScoredCandidate { 517 public final double value; 518 public final double err; 519 public final Key candidateKey; 520 public final boolean userConnectChoiceOverride; ScoredCandidate(double value, double err, boolean userConnectChoiceOverride, Candidate candidate)521 public ScoredCandidate(double value, double err, boolean userConnectChoiceOverride, 522 Candidate candidate) { 523 this.value = value; 524 this.err = err; 525 this.candidateKey = (candidate == null) ? null : candidate.getKey(); 526 this.userConnectChoiceOverride = userConnectChoiceOverride; 527 } 528 /** 529 * Represents no score 530 */ 531 public static final ScoredCandidate NONE = 532 new ScoredCandidate(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 533 false, null); 534 } 535 536 /** 537 * The key used for tracking candidates, consisting of SSID, security type, BSSID, and network 538 * configuration id. 539 */ 540 // TODO (b/123014687) unify with similar classes in the framework 541 public static class Key { 542 public final ScanResultMatchInfo matchInfo; // Contains the SSID and security type 543 public final MacAddress bssid; 544 public final int networkId; // network configuration id 545 public final @WifiConfiguration.SecurityType int securityType; 546 Key(ScanResultMatchInfo matchInfo, MacAddress bssid, int networkId)547 public Key(ScanResultMatchInfo matchInfo, 548 MacAddress bssid, 549 int networkId) { 550 this.matchInfo = matchInfo; 551 this.bssid = bssid; 552 this.networkId = networkId; 553 // If security type is not set, use the default security params. 554 this.securityType = matchInfo.getDefaultSecurityParams().getSecurityType(); 555 } 556 Key(ScanResultMatchInfo matchInfo, MacAddress bssid, int networkId, int securityType)557 public Key(ScanResultMatchInfo matchInfo, 558 MacAddress bssid, 559 int networkId, 560 int securityType) { 561 this.matchInfo = matchInfo; 562 this.bssid = bssid; 563 this.networkId = networkId; 564 this.securityType = securityType; 565 } 566 567 @Override equals(Object other)568 public boolean equals(Object other) { 569 if (!(other instanceof Key)) return false; 570 Key that = (Key) other; 571 return (this.matchInfo.equals(that.matchInfo) 572 && this.bssid.equals(that.bssid) 573 && this.networkId == that.networkId 574 && this.securityType == that.securityType); 575 } 576 577 @Override hashCode()578 public int hashCode() { 579 return Objects.hash(matchInfo, bssid, networkId, securityType); 580 } 581 } 582 583 private final Map<Key, Candidate> mCandidates = new ArrayMap<>(); 584 585 /** 586 * Lists of multi-link candidates mapped with MLD mac address. 587 * 588 * e.g. let's say we have 10 candidates starting from Candidate_1 to Candidate_10. 589 * mMultiLinkCandidates has a mapping, 590 * BSSID_MLD_AP1 -> [Candidate_1, Candidate_3] 591 * BSSID_MLD_AP2 -> [Candidate_4, Candidate_6, Candidate_7] 592 * Here, Candidate_1 and _3 are the affiliated to MLD_AP1. 593 * Candidate_4, _6, _7 are affiliated to MLD_AP2 594 * All remaining candidates are not affiliated to any MLD AP's. 595 */ 596 private final Map<MacAddress, List<Candidate>> mMultiLinkCandidates = new ArrayMap<>(); 597 598 /** 599 * Get a list of multi-link candidates as a collection. 600 * 601 * @return List of candidates or empty Collection if none present. 602 */ getMultiLinkCandidates()603 public Collection<List<Candidate>> getMultiLinkCandidates() { 604 return mMultiLinkCandidates.values(); 605 } 606 607 /** 608 * Get a list of multi-link candidates for a particular MLD AP. 609 * 610 * @param mldMacAddr AP MLD address. 611 * @return List of candidates or null if none present. 612 */ 613 @Nullable getMultiLinkCandidates(@onNull MacAddress mldMacAddr)614 public List<Candidate> getMultiLinkCandidates(@NonNull MacAddress mldMacAddr) { 615 return mMultiLinkCandidates.get(mldMacAddr); 616 } 617 618 private int mCurrentNetworkId = -1; 619 @Nullable private MacAddress mCurrentBssid = null; 620 621 /** 622 * Sets up information about the currently-connected network. 623 */ setCurrent(int currentNetworkId, String currentBssid)624 public void setCurrent(int currentNetworkId, String currentBssid) { 625 mCurrentNetworkId = currentNetworkId; 626 mCurrentBssid = null; 627 if (currentBssid == null) return; 628 try { 629 mCurrentBssid = MacAddress.fromString(currentBssid); 630 } catch (RuntimeException e) { 631 failWithException(e); 632 } 633 } 634 635 /** 636 * Adds a new candidate 637 * 638 * @return true if added or replaced, false otherwise 639 */ add(ScanDetail scanDetail, WifiConfiguration config, @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId, double lastSelectionWeightBetweenZeroAndOne, boolean isMetered, int predictedThroughputMbps)640 public boolean add(ScanDetail scanDetail, 641 WifiConfiguration config, 642 @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId, 643 double lastSelectionWeightBetweenZeroAndOne, 644 boolean isMetered, 645 int predictedThroughputMbps) { 646 Key key = keyFromScanDetailAndConfig(scanDetail, config); 647 if (key == null) return false; 648 return add(key, config, nominatorId, 649 scanDetail.getScanResult().level, 650 scanDetail.getScanResult().frequency, 651 scanDetail.getScanResult().channelWidth, 652 lastSelectionWeightBetweenZeroAndOne, 653 isMetered, 654 false, 655 predictedThroughputMbps, 656 scanDetail.getScanResult().getApMldMacAddress()); 657 } 658 659 /** 660 * Makes a Key from a ScanDetail and WifiConfiguration (null if error). 661 */ keyFromScanDetailAndConfig(ScanDetail scanDetail, WifiConfiguration config)662 public @Nullable Key keyFromScanDetailAndConfig(ScanDetail scanDetail, 663 WifiConfiguration config) { 664 if (!validConfigAndScanDetail(config, scanDetail)) { 665 Log.e( 666 TAG, 667 "validConfigAndScanDetail failed! ScanDetail: " 668 + scanDetail 669 + " WifiConfig: " 670 + config); 671 return null; 672 } 673 674 ScanResult scanResult = scanDetail.getScanResult(); 675 SecurityParams params = ScanResultMatchInfo.fromScanResult(scanResult) 676 .matchForNetworkSelection(ScanResultMatchInfo.fromWifiConfiguration(config)); 677 if (null == params) { 678 Log.e( 679 TAG, 680 "matchForNetworkSelection failed! ScanResult: " 681 + ScanResultMatchInfo.fromScanResult(scanResult) 682 + " WifiConfig: " 683 + ScanResultMatchInfo.fromWifiConfiguration(config)); 684 return null; 685 } 686 MacAddress bssid = MacAddress.fromString(scanResult.BSSID); 687 return new Key(ScanResultMatchInfo.fromScanResult(scanResult), bssid, config.networkId, 688 params.getSecurityType()); 689 } 690 691 /** 692 * Adds a new candidate 693 * 694 * @return true if added or replaced, false otherwise 695 */ add(@onNull Key key, WifiConfiguration config, @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId, int scanRssi, int frequency, @WifiAnnotations.ChannelWidth int channelWidth, double lastSelectionWeightBetweenZeroAndOne, boolean isMetered, boolean isCarrierOrPrivileged, int predictedThroughputMbps, MacAddress apMldMacAddress)696 public boolean add(@NonNull Key key, 697 WifiConfiguration config, 698 @WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId, 699 int scanRssi, 700 int frequency, 701 @WifiAnnotations.ChannelWidth int channelWidth, 702 double lastSelectionWeightBetweenZeroAndOne, 703 boolean isMetered, 704 boolean isCarrierOrPrivileged, 705 int predictedThroughputMbps, 706 MacAddress apMldMacAddress) { 707 Candidate old = mCandidates.get(key); 708 if (old != null) { 709 // check if we want to replace this old candidate 710 if (nominatorId > old.getNominatorId()) return false; 711 remove(old); 712 } 713 WifiScoreCard.PerBssid perBssid = mWifiScoreCard.lookupBssid( 714 key.matchInfo.networkSsid, 715 key.bssid.toString()); 716 perBssid.setSecurityType( 717 WifiScoreCardProto.SecurityType.forNumber( 718 key.matchInfo.getDefaultSecurityParams().getSecurityType())); 719 perBssid.setNetworkConfigId(config.networkId); 720 CandidateImpl candidate = new CandidateImpl(key, config, perBssid, nominatorId, 721 scanRssi, 722 frequency, 723 channelWidth, 724 Math.min(Math.max(lastSelectionWeightBetweenZeroAndOne, 0.0), 1.0), 725 config.isUserSelected(), 726 config.networkId == mCurrentNetworkId, 727 key.bssid.equals(mCurrentBssid), 728 isMetered, 729 isCarrierOrPrivileged, 730 predictedThroughputMbps, 731 apMldMacAddress); 732 mCandidates.put(key, candidate); 733 if (apMldMacAddress != null) { 734 List<Candidate> mlCandidates = mMultiLinkCandidates.computeIfAbsent(apMldMacAddress, 735 k -> new ArrayList<>()); 736 mlCandidates.add(candidate); 737 } 738 return true; 739 } 740 741 /** 742 * Checks that the supplied config and scan detail are valid (for the parts 743 * we care about) and consistent with each other. 744 * 745 * @param config to be validated 746 * @param scanDetail to be validated 747 * @return true if the config and scanDetail are consistent with each other 748 */ validConfigAndScanDetail(WifiConfiguration config, ScanDetail scanDetail)749 private boolean validConfigAndScanDetail(WifiConfiguration config, ScanDetail scanDetail) { 750 if (config == null) return failure(); 751 if (scanDetail == null) return failure(); 752 ScanResult scanResult = scanDetail.getScanResult(); 753 if (scanResult == null) return failure(); 754 MacAddress bssid; 755 try { 756 bssid = MacAddress.fromString(scanResult.BSSID); 757 } catch (RuntimeException e) { 758 return failWithException(e); 759 } 760 ScanResultMatchInfo key1 = ScanResultMatchInfo.fromScanResult(scanResult); 761 if (!config.isPasspoint()) { 762 ScanResultMatchInfo key2 = ScanResultMatchInfo.fromWifiConfiguration(config); 763 if (!key1.equals(key2)) { 764 return failure(key1, key2); 765 } 766 } 767 return true; 768 } 769 770 /** 771 * Removes a candidate 772 * @return true if the candidate was successfully removed 773 */ remove(Candidate candidate)774 public boolean remove(Candidate candidate) { 775 if (!(candidate instanceof CandidateImpl)) return failure(); 776 return mCandidates.remove(candidate.getKey(), candidate); 777 } 778 779 /** 780 * Returns the number of candidates (at the BSSID level) 781 */ size()782 public int size() { 783 return mCandidates.size(); 784 } 785 786 /** 787 * Returns the candidates, grouped by network. 788 */ getGroupedCandidates()789 public Collection<Collection<Candidate>> getGroupedCandidates() { 790 Map<Integer, Collection<Candidate>> candidatesForNetworkId = new ArrayMap<>(); 791 for (Candidate candidate : mCandidates.values()) { 792 Collection<Candidate> cc = candidatesForNetworkId.get(candidate.getNetworkConfigId()); 793 if (cc == null) { 794 cc = new ArrayList<>(2); // Guess 2 bssids per network 795 candidatesForNetworkId.put(candidate.getNetworkConfigId(), cc); 796 } 797 cc.add(candidate); 798 } 799 return candidatesForNetworkId.values(); 800 } 801 802 /** 803 * Return a copy of the Candidates. 804 */ getCandidates()805 public List<Candidate> getCandidates() { 806 return mCandidates.entrySet().stream().map(entry -> entry.getValue()) 807 .collect(Collectors.toList()); 808 } 809 810 /** 811 * Make a choice from among the candidates, using the provided scorer. 812 * 813 * @return the chosen scored candidate, or ScoredCandidate.NONE. 814 */ choose(@onNull CandidateScorer candidateScorer)815 public @NonNull ScoredCandidate choose(@NonNull CandidateScorer candidateScorer) { 816 Preconditions.checkNotNull(candidateScorer); 817 Collection<Candidate> candidates = new ArrayList<>(mCandidates.values()); 818 ScoredCandidate choice = candidateScorer.scoreCandidates(candidates); 819 return choice == null ? ScoredCandidate.NONE : choice; 820 } 821 822 /** 823 * After a failure indication is returned, this may be used to get details. 824 */ getLastFault()825 public RuntimeException getLastFault() { 826 return mLastFault; 827 } 828 829 /** 830 * Returns the number of faults we have seen 831 */ getFaultCount()832 public int getFaultCount() { 833 return mFaultCount; 834 } 835 836 /** 837 * Clears any recorded faults 838 */ clearFaults()839 public void clearFaults() { 840 mLastFault = null; 841 mFaultCount = 0; 842 } 843 844 /** 845 * Controls whether to immediately raise an exception on a failure 846 */ setPicky(boolean picky)847 public WifiCandidates setPicky(boolean picky) { 848 mPicky = picky; 849 return this; 850 } 851 852 /** 853 * Records details about a failure 854 * 855 * This captures a stack trace, so don't bother to construct a string message, just 856 * supply any culprits (convertible to strings) that might aid diagnosis. 857 * 858 * @return false 859 * @throws RuntimeException (if in picky mode) 860 */ failure(Object... culprits)861 private boolean failure(Object... culprits) { 862 StringJoiner joiner = new StringJoiner(","); 863 for (Object c : culprits) { 864 joiner.add("" + c); 865 } 866 return failWithException(new IllegalArgumentException(joiner.toString())); 867 } 868 869 /** 870 * As above, if we already have an exception. 871 */ failWithException(RuntimeException e)872 private boolean failWithException(RuntimeException e) { 873 mLastFault = e; 874 mFaultCount++; 875 if (mPicky) { 876 throw e; 877 } 878 return false; 879 } 880 881 private boolean mPicky = false; 882 private RuntimeException mLastFault = null; 883 private int mFaultCount = 0; 884 } 885