746 lines
25 KiB
Java
746 lines
25 KiB
Java
/*
|
|
* Copyright 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.server.wifi;
|
|
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.content.Context;
|
|
import android.net.MacAddress;
|
|
import android.net.wifi.ScanResult;
|
|
import android.net.wifi.SecurityParams;
|
|
import android.net.wifi.WifiAnnotations;
|
|
import android.net.wifi.WifiConfiguration;
|
|
import android.util.ArrayMap;
|
|
|
|
import com.android.internal.util.Preconditions;
|
|
import com.android.server.wifi.proto.WifiScoreCardProto;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.StringJoiner;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* Candidates for network selection
|
|
*/
|
|
public class WifiCandidates {
|
|
private static final String TAG = "WifiCandidates";
|
|
|
|
public WifiCandidates(@NonNull WifiScoreCard wifiScoreCard, @NonNull Context context) {
|
|
this(wifiScoreCard, context, Collections.EMPTY_LIST);
|
|
}
|
|
|
|
public WifiCandidates(@NonNull WifiScoreCard wifiScoreCard, @NonNull Context context,
|
|
@NonNull List<Candidate> candidates) {
|
|
mWifiScoreCard = Preconditions.checkNotNull(wifiScoreCard);
|
|
mContext = context;
|
|
for (Candidate c : candidates) {
|
|
mCandidates.put(c.getKey(), c);
|
|
}
|
|
}
|
|
|
|
private final WifiScoreCard mWifiScoreCard;
|
|
private final Context mContext;
|
|
|
|
/**
|
|
* Represents a connectable candidate.
|
|
*/
|
|
public interface Candidate {
|
|
/**
|
|
* Gets the Key, which contains the SSID, BSSID, security type, and config id.
|
|
*
|
|
* Generally, a CandidateScorer should not need to use this.
|
|
*/
|
|
@Nullable Key getKey();
|
|
|
|
/**
|
|
* Gets the config id.
|
|
*/
|
|
int getNetworkConfigId();
|
|
/**
|
|
* Returns true for an open network.
|
|
*/
|
|
boolean isOpenNetwork();
|
|
/**
|
|
* Returns true for a passpoint network.
|
|
*/
|
|
boolean isPasspoint();
|
|
/**
|
|
* Returns true for an ephemeral network.
|
|
*/
|
|
boolean isEphemeral();
|
|
/**
|
|
* Returns true for a trusted network.
|
|
*/
|
|
boolean isTrusted();
|
|
/**
|
|
* Returns true for a oem paid network.
|
|
*/
|
|
boolean isOemPaid();
|
|
/**
|
|
* Returns true for a oem private network.
|
|
*/
|
|
boolean isOemPrivate();
|
|
/**
|
|
* Returns true for a secondary network with internet.
|
|
*/
|
|
boolean isSecondaryInternet();
|
|
/**
|
|
* Returns true if suggestion came from a carrier or privileged app.
|
|
*/
|
|
boolean isCarrierOrPrivileged();
|
|
/**
|
|
* Returns true for a metered network.
|
|
*/
|
|
boolean isMetered();
|
|
|
|
/**
|
|
* Returns true if network doesn't have internet access during last connection
|
|
*/
|
|
boolean hasNoInternetAccess();
|
|
|
|
/**
|
|
* Returns true if network is expected not to have Internet access
|
|
* (e.g., a wireless printer, a Chromecast hotspot, etc.).
|
|
*/
|
|
boolean isNoInternetAccessExpected();
|
|
|
|
/**
|
|
* Returns the ID of the nominator that provided the candidate.
|
|
*/
|
|
@WifiNetworkSelector.NetworkNominator.NominatorId
|
|
int getNominatorId();
|
|
|
|
/**
|
|
* Returns true if the candidate is in the same network as the
|
|
* current connection.
|
|
*/
|
|
boolean isCurrentNetwork();
|
|
/**
|
|
* Return true if the candidate is currently connected.
|
|
*/
|
|
boolean isCurrentBssid();
|
|
/**
|
|
* Returns a value between 0 and 1.
|
|
*
|
|
* 1.0 means the network was recently selected by the user or an app.
|
|
* 0.0 means not recently selected by user or app.
|
|
*/
|
|
double getLastSelectionWeight();
|
|
/**
|
|
* Gets the scan RSSI.
|
|
*/
|
|
int getScanRssi();
|
|
/**
|
|
* Gets the scan frequency.
|
|
*/
|
|
int getFrequency();
|
|
|
|
/**
|
|
* Gets the channel width.
|
|
*/
|
|
@WifiAnnotations.ChannelWidth int getChannelWidth();
|
|
/**
|
|
* Gets the predicted throughput in Mbps.
|
|
*/
|
|
int getPredictedThroughputMbps();
|
|
/**
|
|
* Estimated probability of getting internet access (percent 0-100).
|
|
*/
|
|
int getEstimatedPercentInternetAvailability();
|
|
/**
|
|
* Gets statistics from the scorecard.
|
|
*/
|
|
@Nullable WifiScoreCardProto.Signal getEventStatistics(WifiScoreCardProto.Event event);
|
|
|
|
/**
|
|
* Returns true for a restricted network.
|
|
*/
|
|
boolean isRestricted();
|
|
}
|
|
|
|
/**
|
|
* Represents a connectable candidate
|
|
*/
|
|
private static class CandidateImpl implements Candidate {
|
|
private final Key mKey; // SSID/sectype/BSSID/configId
|
|
private final @WifiNetworkSelector.NetworkNominator.NominatorId int mNominatorId;
|
|
private final int mScanRssi;
|
|
private final int mFrequency;
|
|
private final int mChannelWidth;
|
|
private final double mLastSelectionWeight;
|
|
private final WifiScoreCard.PerBssid mPerBssid; // For accessing the scorecard entry
|
|
private final boolean mIsCurrentNetwork;
|
|
private final boolean mIsCurrentBssid;
|
|
private final boolean mIsMetered;
|
|
private final boolean mHasNoInternetAccess;
|
|
private final boolean mIsNoInternetAccessExpected;
|
|
private final boolean mIsOpenNetwork;
|
|
private final boolean mPasspoint;
|
|
private final boolean mEphemeral;
|
|
private final boolean mTrusted;
|
|
private final boolean mRestricted;
|
|
private final boolean mOemPaid;
|
|
private final boolean mOemPrivate;
|
|
private final boolean mSecondaryInternet;
|
|
private final boolean mCarrierOrPrivileged;
|
|
private final int mPredictedThroughputMbps;
|
|
private final int mEstimatedPercentInternetAvailability;
|
|
|
|
CandidateImpl(Key key, WifiConfiguration config,
|
|
WifiScoreCard.PerBssid perBssid,
|
|
@WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId,
|
|
int scanRssi,
|
|
int frequency,
|
|
int channelWidth,
|
|
double lastSelectionWeight,
|
|
boolean isCurrentNetwork,
|
|
boolean isCurrentBssid,
|
|
boolean isMetered,
|
|
boolean isCarrierOrPrivileged,
|
|
int predictedThroughputMbps) {
|
|
this.mKey = key;
|
|
this.mNominatorId = nominatorId;
|
|
this.mScanRssi = scanRssi;
|
|
this.mFrequency = frequency;
|
|
this.mChannelWidth = channelWidth;
|
|
this.mPerBssid = perBssid;
|
|
this.mLastSelectionWeight = lastSelectionWeight;
|
|
this.mIsCurrentNetwork = isCurrentNetwork;
|
|
this.mIsCurrentBssid = isCurrentBssid;
|
|
this.mIsMetered = isMetered;
|
|
this.mHasNoInternetAccess = config.hasNoInternetAccess();
|
|
this.mIsNoInternetAccessExpected = config.isNoInternetAccessExpected();
|
|
this.mIsOpenNetwork = WifiConfigurationUtil.isConfigForOpenNetwork(config);
|
|
this.mPasspoint = config.isPasspoint();
|
|
this.mEphemeral = config.isEphemeral();
|
|
this.mTrusted = config.trusted;
|
|
this.mOemPaid = config.oemPaid;
|
|
this.mOemPrivate = config.oemPrivate;
|
|
this.mSecondaryInternet = config.dbsSecondaryInternet;
|
|
this.mCarrierOrPrivileged = isCarrierOrPrivileged;
|
|
this.mPredictedThroughputMbps = predictedThroughputMbps;
|
|
this.mEstimatedPercentInternetAvailability = perBssid == null ? 50 :
|
|
perBssid.estimatePercentInternetAvailability();
|
|
this.mRestricted = config.restricted;
|
|
}
|
|
|
|
@Override
|
|
public Key getKey() {
|
|
return mKey;
|
|
}
|
|
|
|
@Override
|
|
public int getNetworkConfigId() {
|
|
return mKey.networkId;
|
|
}
|
|
|
|
@Override
|
|
public boolean isOpenNetwork() {
|
|
return mIsOpenNetwork;
|
|
}
|
|
|
|
@Override
|
|
public boolean isPasspoint() {
|
|
return mPasspoint;
|
|
}
|
|
|
|
@Override
|
|
public boolean isEphemeral() {
|
|
return mEphemeral;
|
|
}
|
|
|
|
@Override
|
|
public boolean isTrusted() {
|
|
return mTrusted;
|
|
}
|
|
|
|
@Override
|
|
public boolean isRestricted() {
|
|
return mRestricted;
|
|
}
|
|
|
|
@Override
|
|
public boolean isOemPaid() {
|
|
return mOemPaid;
|
|
}
|
|
|
|
@Override
|
|
public boolean isOemPrivate() {
|
|
return mOemPrivate;
|
|
}
|
|
|
|
@Override
|
|
public boolean isSecondaryInternet() {
|
|
return mSecondaryInternet;
|
|
}
|
|
|
|
@Override
|
|
public boolean isCarrierOrPrivileged() {
|
|
return mCarrierOrPrivileged;
|
|
}
|
|
|
|
@Override
|
|
public boolean isMetered() {
|
|
return mIsMetered;
|
|
}
|
|
|
|
@Override
|
|
public boolean hasNoInternetAccess() {
|
|
return mHasNoInternetAccess;
|
|
}
|
|
|
|
@Override
|
|
public boolean isNoInternetAccessExpected() {
|
|
return mIsNoInternetAccessExpected;
|
|
}
|
|
|
|
@Override
|
|
public @WifiNetworkSelector.NetworkNominator.NominatorId int getNominatorId() {
|
|
return mNominatorId;
|
|
}
|
|
|
|
@Override
|
|
public double getLastSelectionWeight() {
|
|
return mLastSelectionWeight;
|
|
}
|
|
|
|
@Override
|
|
public boolean isCurrentNetwork() {
|
|
return mIsCurrentNetwork;
|
|
}
|
|
|
|
@Override
|
|
public boolean isCurrentBssid() {
|
|
return mIsCurrentBssid;
|
|
}
|
|
|
|
@Override
|
|
public int getScanRssi() {
|
|
return mScanRssi;
|
|
}
|
|
|
|
@Override
|
|
public int getFrequency() {
|
|
return mFrequency;
|
|
}
|
|
|
|
@Override
|
|
public int getChannelWidth() {
|
|
return mChannelWidth;
|
|
}
|
|
|
|
@Override
|
|
public int getPredictedThroughputMbps() {
|
|
return mPredictedThroughputMbps;
|
|
}
|
|
|
|
@Override
|
|
public int getEstimatedPercentInternetAvailability() {
|
|
return mEstimatedPercentInternetAvailability;
|
|
}
|
|
|
|
/**
|
|
* Accesses statistical information from the score card
|
|
*/
|
|
@Override
|
|
public WifiScoreCardProto.Signal getEventStatistics(WifiScoreCardProto.Event event) {
|
|
if (mPerBssid == null) return null;
|
|
WifiScoreCard.PerSignal perSignal = mPerBssid.lookupSignal(event, getFrequency());
|
|
if (perSignal == null) return null;
|
|
return perSignal.toSignal();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
Key key = getKey();
|
|
String lastSelectionWeightString = "";
|
|
if (getLastSelectionWeight() != 0.0) {
|
|
// Round this to 3 places
|
|
lastSelectionWeightString = "lastSelectionWeight = "
|
|
+ Math.round(getLastSelectionWeight() * 1000.0) / 1000.0
|
|
+ ", ";
|
|
}
|
|
return "Candidate { "
|
|
+ "config = " + getNetworkConfigId() + ", "
|
|
+ "bssid = " + key.bssid + ", "
|
|
+ "freq = " + getFrequency() + ", "
|
|
+ "channelWidth = " + getChannelWidth() + ", "
|
|
+ "rssi = " + getScanRssi() + ", "
|
|
+ "Mbps = " + getPredictedThroughputMbps() + ", "
|
|
+ "nominator = " + getNominatorId() + ", "
|
|
+ "pInternet = " + getEstimatedPercentInternetAvailability() + ", "
|
|
+ lastSelectionWeightString
|
|
+ (isCurrentBssid() ? "connected, " : "")
|
|
+ (isCurrentNetwork() ? "current, " : "")
|
|
+ (isEphemeral() ? "ephemeral" : "saved") + ", "
|
|
+ (isTrusted() ? "trusted, " : "")
|
|
+ (isRestricted() ? "restricted, " : "")
|
|
+ (isOemPaid() ? "oemPaid, " : "")
|
|
+ (isOemPrivate() ? "oemPrivate, " : "")
|
|
+ (isSecondaryInternet() ? "secondaryInternet, " : "")
|
|
+ (isCarrierOrPrivileged() ? "priv, " : "")
|
|
+ (isMetered() ? "metered, " : "")
|
|
+ (hasNoInternetAccess() ? "noInternet, " : "")
|
|
+ (isNoInternetAccessExpected() ? "noInternetExpected, " : "")
|
|
+ (isPasspoint() ? "passpoint, " : "")
|
|
+ (isOpenNetwork() ? "open" : "secure") + " }";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Represents a scoring function
|
|
*/
|
|
public interface CandidateScorer {
|
|
/**
|
|
* The scorer's name, and perhaps important parameterization/version.
|
|
*/
|
|
String getIdentifier();
|
|
|
|
/**
|
|
* Calculates the best score for a collection of candidates.
|
|
*/
|
|
@Nullable ScoredCandidate scoreCandidates(@NonNull Collection<Candidate> candidates);
|
|
|
|
}
|
|
|
|
/**
|
|
* Represents a candidate with a real-valued score, along with an error estimate.
|
|
*
|
|
* Larger values reflect more desirable candidates. The range is arbitrary,
|
|
* because scores generated by different sources are not compared with each
|
|
* other.
|
|
*
|
|
* The error estimate is on the same scale as the value, and should
|
|
* always be strictly positive. For instance, it might be the standard deviation.
|
|
*/
|
|
public static class ScoredCandidate {
|
|
public final double value;
|
|
public final double err;
|
|
public final Key candidateKey;
|
|
public final boolean userConnectChoiceOverride;
|
|
public ScoredCandidate(double value, double err, boolean userConnectChoiceOverride,
|
|
Candidate candidate) {
|
|
this.value = value;
|
|
this.err = err;
|
|
this.candidateKey = (candidate == null) ? null : candidate.getKey();
|
|
this.userConnectChoiceOverride = userConnectChoiceOverride;
|
|
}
|
|
/**
|
|
* Represents no score
|
|
*/
|
|
public static final ScoredCandidate NONE =
|
|
new ScoredCandidate(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
|
|
false, null);
|
|
}
|
|
|
|
/**
|
|
* The key used for tracking candidates, consisting of SSID, security type, BSSID, and network
|
|
* configuration id.
|
|
*/
|
|
// TODO (b/123014687) unify with similar classes in the framework
|
|
public static class Key {
|
|
public final ScanResultMatchInfo matchInfo; // Contains the SSID and security type
|
|
public final MacAddress bssid;
|
|
public final int networkId; // network configuration id
|
|
public final @WifiConfiguration.SecurityType int securityType;
|
|
|
|
public Key(ScanResultMatchInfo matchInfo,
|
|
MacAddress bssid,
|
|
int networkId) {
|
|
this.matchInfo = matchInfo;
|
|
this.bssid = bssid;
|
|
this.networkId = networkId;
|
|
// If security type is not set, use the default security params.
|
|
this.securityType = matchInfo.getDefaultSecurityParams().getSecurityType();
|
|
}
|
|
|
|
public Key(ScanResultMatchInfo matchInfo,
|
|
MacAddress bssid,
|
|
int networkId,
|
|
int securityType) {
|
|
this.matchInfo = matchInfo;
|
|
this.bssid = bssid;
|
|
this.networkId = networkId;
|
|
this.securityType = securityType;
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object other) {
|
|
if (!(other instanceof Key)) return false;
|
|
Key that = (Key) other;
|
|
return (this.matchInfo.equals(that.matchInfo)
|
|
&& this.bssid.equals(that.bssid)
|
|
&& this.networkId == that.networkId
|
|
&& this.securityType == that.securityType);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(matchInfo, bssid, networkId, securityType);
|
|
}
|
|
}
|
|
|
|
private final Map<Key, Candidate> mCandidates = new ArrayMap<>();
|
|
|
|
private int mCurrentNetworkId = -1;
|
|
@Nullable private MacAddress mCurrentBssid = null;
|
|
|
|
/**
|
|
* Sets up information about the currently-connected network.
|
|
*/
|
|
public void setCurrent(int currentNetworkId, String currentBssid) {
|
|
mCurrentNetworkId = currentNetworkId;
|
|
mCurrentBssid = null;
|
|
if (currentBssid == null) return;
|
|
try {
|
|
mCurrentBssid = MacAddress.fromString(currentBssid);
|
|
} catch (RuntimeException e) {
|
|
failWithException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a new candidate
|
|
*
|
|
* @return true if added or replaced, false otherwise
|
|
*/
|
|
public boolean add(ScanDetail scanDetail,
|
|
WifiConfiguration config,
|
|
@WifiNetworkSelector.NetworkNominator.NominatorId int nominatorId,
|
|
double lastSelectionWeightBetweenZeroAndOne,
|
|
boolean isMetered,
|
|
int predictedThroughputMbps) {
|
|
Key key = keyFromScanDetailAndConfig(scanDetail, config);
|
|
if (key == null) return false;
|
|
return add(key, config, nominatorId,
|
|
scanDetail.getScanResult().level,
|
|
scanDetail.getScanResult().frequency,
|
|
scanDetail.getScanResult().channelWidth,
|
|
lastSelectionWeightBetweenZeroAndOne,
|
|
isMetered,
|
|
false,
|
|
predictedThroughputMbps);
|
|
}
|
|
|
|
/**
|
|
* Makes a Key from a ScanDetail and WifiConfiguration (null if error).
|
|
*/
|
|
public @Nullable Key keyFromScanDetailAndConfig(ScanDetail scanDetail,
|
|
WifiConfiguration config) {
|
|
if (!validConfigAndScanDetail(config, scanDetail)) return null;
|
|
|
|
ScanResult scanResult = scanDetail.getScanResult();
|
|
SecurityParams params = ScanResultMatchInfo.fromScanResult(scanResult)
|
|
.matchForNetworkSelection(ScanResultMatchInfo.fromWifiConfiguration(config));
|
|
if (null == params) return null;
|
|
MacAddress bssid = MacAddress.fromString(scanResult.BSSID);
|
|
return new Key(ScanResultMatchInfo.fromScanResult(scanResult), bssid, config.networkId,
|
|
params.getSecurityType());
|
|
}
|
|
|
|
/**
|
|
* Adds a new candidate
|
|
*
|
|
* @return true if added or replaced, false otherwise
|
|
*/
|
|
public boolean add(@NonNull 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) {
|
|
Candidate old = mCandidates.get(key);
|
|
if (old != null) {
|
|
// check if we want to replace this old candidate
|
|
if (nominatorId > old.getNominatorId()) return false;
|
|
remove(old);
|
|
}
|
|
WifiScoreCard.PerBssid perBssid = mWifiScoreCard.lookupBssid(
|
|
key.matchInfo.networkSsid,
|
|
key.bssid.toString());
|
|
perBssid.setSecurityType(
|
|
WifiScoreCardProto.SecurityType.forNumber(
|
|
key.matchInfo.getDefaultSecurityParams().getSecurityType()));
|
|
perBssid.setNetworkConfigId(config.networkId);
|
|
CandidateImpl candidate = new CandidateImpl(key, config, perBssid, nominatorId,
|
|
scanRssi,
|
|
frequency,
|
|
channelWidth,
|
|
Math.min(Math.max(lastSelectionWeightBetweenZeroAndOne, 0.0), 1.0),
|
|
config.networkId == mCurrentNetworkId,
|
|
key.bssid.equals(mCurrentBssid),
|
|
isMetered,
|
|
isCarrierOrPrivileged,
|
|
predictedThroughputMbps);
|
|
mCandidates.put(key, candidate);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks that the supplied config and scan detail are valid (for the parts
|
|
* we care about) and consistent with each other.
|
|
*
|
|
* @param config to be validated
|
|
* @param scanDetail to be validated
|
|
* @return true if the config and scanDetail are consistent with each other
|
|
*/
|
|
private boolean validConfigAndScanDetail(WifiConfiguration config, ScanDetail scanDetail) {
|
|
if (config == null) return failure();
|
|
if (scanDetail == null) return failure();
|
|
ScanResult scanResult = scanDetail.getScanResult();
|
|
if (scanResult == null) return failure();
|
|
MacAddress bssid;
|
|
try {
|
|
bssid = MacAddress.fromString(scanResult.BSSID);
|
|
} catch (RuntimeException e) {
|
|
return failWithException(e);
|
|
}
|
|
ScanResultMatchInfo key1 = ScanResultMatchInfo.fromScanResult(scanResult);
|
|
if (!config.isPasspoint()) {
|
|
ScanResultMatchInfo key2 = ScanResultMatchInfo.fromWifiConfiguration(config);
|
|
if (!key1.equals(key2)) {
|
|
return failure(key1, key2);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Removes a candidate
|
|
* @return true if the candidate was successfully removed
|
|
*/
|
|
public boolean remove(Candidate candidate) {
|
|
if (!(candidate instanceof CandidateImpl)) return failure();
|
|
return mCandidates.remove(candidate.getKey(), candidate);
|
|
}
|
|
|
|
/**
|
|
* Returns the number of candidates (at the BSSID level)
|
|
*/
|
|
public int size() {
|
|
return mCandidates.size();
|
|
}
|
|
|
|
/**
|
|
* Returns the candidates, grouped by network.
|
|
*/
|
|
public Collection<Collection<Candidate>> getGroupedCandidates() {
|
|
Map<Integer, Collection<Candidate>> candidatesForNetworkId = new ArrayMap<>();
|
|
for (Candidate candidate : mCandidates.values()) {
|
|
Collection<Candidate> cc = candidatesForNetworkId.get(candidate.getNetworkConfigId());
|
|
if (cc == null) {
|
|
cc = new ArrayList<>(2); // Guess 2 bssids per network
|
|
candidatesForNetworkId.put(candidate.getNetworkConfigId(), cc);
|
|
}
|
|
cc.add(candidate);
|
|
}
|
|
return candidatesForNetworkId.values();
|
|
}
|
|
|
|
/**
|
|
* Return a copy of the Candidates.
|
|
*/
|
|
public List<Candidate> getCandidates() {
|
|
return mCandidates.entrySet().stream().map(entry -> entry.getValue())
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
/**
|
|
* Make a choice from among the candidates, using the provided scorer.
|
|
*
|
|
* @return the chosen scored candidate, or ScoredCandidate.NONE.
|
|
*/
|
|
public @NonNull ScoredCandidate choose(@NonNull CandidateScorer candidateScorer) {
|
|
Preconditions.checkNotNull(candidateScorer);
|
|
Collection<Candidate> candidates = new ArrayList<>(mCandidates.values());
|
|
ScoredCandidate choice = candidateScorer.scoreCandidates(candidates);
|
|
return choice == null ? ScoredCandidate.NONE : choice;
|
|
}
|
|
|
|
/**
|
|
* After a failure indication is returned, this may be used to get details.
|
|
*/
|
|
public RuntimeException getLastFault() {
|
|
return mLastFault;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of faults we have seen
|
|
*/
|
|
public int getFaultCount() {
|
|
return mFaultCount;
|
|
}
|
|
|
|
/**
|
|
* Clears any recorded faults
|
|
*/
|
|
public void clearFaults() {
|
|
mLastFault = null;
|
|
mFaultCount = 0;
|
|
}
|
|
|
|
/**
|
|
* Controls whether to immediately raise an exception on a failure
|
|
*/
|
|
public WifiCandidates setPicky(boolean picky) {
|
|
mPicky = picky;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Records details about a failure
|
|
*
|
|
* This captures a stack trace, so don't bother to construct a string message, just
|
|
* supply any culprits (convertible to strings) that might aid diagnosis.
|
|
*
|
|
* @return false
|
|
* @throws RuntimeException (if in picky mode)
|
|
*/
|
|
private boolean failure(Object... culprits) {
|
|
StringJoiner joiner = new StringJoiner(",");
|
|
for (Object c : culprits) {
|
|
joiner.add("" + c);
|
|
}
|
|
return failWithException(new IllegalArgumentException(joiner.toString()));
|
|
}
|
|
|
|
/**
|
|
* As above, if we already have an exception.
|
|
*/
|
|
private boolean failWithException(RuntimeException e) {
|
|
mLastFault = e;
|
|
mFaultCount++;
|
|
if (mPicky) {
|
|
throw e;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private boolean mPicky = false;
|
|
private RuntimeException mLastFault = null;
|
|
private int mFaultCount = 0;
|
|
}
|