前言:之前在(一百三十一)Android O WiFi自动连接评分机制学习 学习了SavedNetworkEvaluator的筛选,现在看下PasspointNetworkEvaluator
目录
1. PasspointNetworkEvaluator
1.1 PasspointManager.sweepCache
1.2 NetworkDetail.isInterworking
1.3 PasspointManager.matchProvider
1.4 findBestNetwork
2.总结
@Override
public WifiConfiguration evaluateNetworks(List scanDetails,
WifiConfiguration currentNetwork, String currentBssid,
boolean connected, boolean untrustedNetworkAllowed,
List> connectableNetworks) {
// Sweep the ANQP cache to remove any expired ANQP entries.
mPasspointManager.sweepCache();
// Go through each ScanDetail and find the best provider for each ScanDetail.
List candidateList = new ArrayList<>();
for (ScanDetail scanDetail : scanDetails) {
// Skip non-Passpoint APs.
if (!scanDetail.getNetworkDetail().isInterworking()) {
continue;
}
// Find the best provider for this ScanDetail.
Pair bestProvider =
mPasspointManager.matchProvider(scanDetail.getScanResult());
if (bestProvider != null) {
if (bestProvider.first.isSimCredential() && !mWifiConfigManager.isSimPresent()) {
// Skip providers backed by SIM credential when SIM is not present.
continue;
}
candidateList.add(new PasspointNetworkCandidate(
bestProvider.first, bestProvider.second, scanDetail));
}
}
// Done if no candidate is found.
if (candidateList.isEmpty()) {
localLog("No suitable Passpoint network found");
return null;
}
// Find the best Passpoint network among all candidates.
PasspointNetworkCandidate bestNetwork =
findBestNetwork(candidateList, currentNetwork == null ? null : currentNetwork.SSID);
// Return the configuration for the current connected network if it is the best network.
if (currentNetwork != null && TextUtils.equals(currentNetwork.SSID,
ScanResultUtil.createQuotedSSID(bestNetwork.mScanDetail.getSSID()))) {
localLog("Staying with current Passpoint network " + currentNetwork.SSID);
// Update current network with the latest scan info.
mWifiConfigManager.setNetworkCandidateScanResult(currentNetwork.networkId,
bestNetwork.mScanDetail.getScanResult(), 0);
mWifiConfigManager.updateScanDetailForNetwork(currentNetwork.networkId,
bestNetwork.mScanDetail);
connectableNetworks.add(Pair.create(bestNetwork.mScanDetail, currentNetwork));
return currentNetwork;
}
WifiConfiguration config = createWifiConfigForProvider(bestNetwork);
if (config != null) {
connectableNetworks.add(Pair.create(bestNetwork.mScanDetail, config));
localLog("Passpoint network to connect to: " + config.SSID);
}
return config;
}
Sweep the ANQP cache to remove expired entries
The Access Network Query Protocol (ANQP)
PasspointManager.java
/**
* Sweep the ANQP cache to remove expired entries.
*/
public void sweepCache() {
mAnqpCache.sweep();
}
AnqpCache.java
/**
* Go through the cache to remove any expired entries.
*/
public void sweep() {
long now = mClock.getElapsedSinceBootMillis();
// Check if it is time to perform the sweep.
if (now < mLastSweep + CACHE_SWEEP_INTERVAL_MILLISECONDS) {
return;
}
// Get all expired keys.
List expiredKeys = new ArrayList<>();
for (Map.Entry entry : mANQPCache.entrySet()) {
if (entry.getValue().expired(now)) {
expiredKeys.add(entry.getKey());
}
}
// Remove all expired entries.
for (ANQPNetworkKey key : expiredKeys) {
mANQPCache.remove(key);
}
mLastSweep = now;
}
NetworkDetail.java
public boolean isInterworking() {
return mAnt != null;
}
构造函数
public NetworkDetail(String bssid, ScanResult.InformationElement[] infoElements,
List anqpLines, int freq) {
...
InformationElementUtil.Interworking interworking =
new InformationElementUtil.Interworking();
...
try {
for (ScanResult.InformationElement ie : infoElements) {
iesFound.add(ie.id);
switch (ie.id) {
case ScanResult.InformationElement.EID_SSID:
ssidOctets = ie.bytes;
break;
case ScanResult.InformationElement.EID_BSS_LOAD:
bssLoad.from(ie);
break;
case ScanResult.InformationElement.EID_HT_OPERATION:
htOperation.from(ie);
break;
case ScanResult.InformationElement.EID_VHT_OPERATION:
vhtOperation.from(ie);
break;
case ScanResult.InformationElement.EID_INTERWORKING:
interworking.from(ie);
break;
...
mAnt = interworking.ant;
再看下Interworking的from
InformationElementUtil.java
public static class Interworking {
public NetworkDetail.Ant ant = null;
public boolean internet = false;
public long hessid = 0L;
public void from(InformationElement ie) {
if (ie.id != InformationElement.EID_INTERWORKING) {
throw new IllegalArgumentException("Element id is not INTERWORKING, : " + ie.id);
}
ByteBuffer data = ByteBuffer.wrap(ie.bytes).order(ByteOrder.LITTLE_ENDIAN);
int anOptions = data.get() & Constants.BYTE_MASK;
ant = NetworkDetail.Ant.values()[anOptions & 0x0f];
internet = (anOptions & 0x10) != 0;
// There are only three possible lengths for the Interworking IE:
// Len 1: Access Network Options only
// Len 3: Access Network Options & Venue Info
// Len 7: Access Network Options & HESSID
// Len 9: Access Network Options, Venue Info, & HESSID
if (ie.bytes.length != 1
&& ie.bytes.length != 3
&& ie.bytes.length != 7
&& ie.bytes.length != 9) {
throw new IllegalArgumentException(
"Bad Interworking element length: " + ie.bytes.length);
}
if (ie.bytes.length == 3 || ie.bytes.length == 9) {
int venueInfo = (int) ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 2);
}
if (ie.bytes.length == 7 || ie.bytes.length == 9) {
hessid = ByteBufferReader.readInteger(data, ByteOrder.BIG_ENDIAN, 6);
}
}
}
NetworkDetail是否支持passpoint的信息是从ie中解析出来的
/**
* Find the best provider that can provide service through the given AP, which means the
* provider contained credential to authenticate with the given AP.
*
* Here is the current precedence of the matching rule in descending order:
* 1. Home Provider
* 2. Roaming Provider
*
* A {code null} will be returned if no matching is found.
*
* @param scanResult The scan result associated with the AP
* @return A pair of {@link PasspointProvider} and match status.
*/
public Pair matchProvider(ScanResult scanResult) {
List> allMatches = getAllMatchedProviders(
scanResult);
if (allMatches == null) {
return null;
}
Pair bestMatch = null;
for (Pair match : allMatches) {
if (match.second == PasspointMatch.HomeProvider) {
bestMatch = match;
break;
}
if (match.second == PasspointMatch.RoamingProvider && bestMatch == null) {
bestMatch = match;
}
}
if (bestMatch != null) {
Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
bestMatch.first.getConfig().getHomeSp().getFqdn(),
bestMatch.second == PasspointMatch.HomeProvider ? "Home Provider"
: "Roaming Provider"));
} else {
Log.d(TAG, "Match not found for " + scanResult.SSID);
}
return bestMatch;
}
看下getAllMatchedProviders
/**
* Return a list of all providers that can provide service through the given AP.
*
* @param scanResult The scan result associated with the AP
* @return a list of pairs of {@link PasspointProvider} and match status.
*/
public List> getAllMatchedProviders(
ScanResult scanResult) {
List> allMatches = new ArrayList<>();
// Retrieve the relevant information elements, mainly Roaming Consortium IE and Hotspot 2.0
// Vendor Specific IE.
InformationElementUtil.RoamingConsortium roamingConsortium =
InformationElementUtil.getRoamingConsortiumIE(scanResult.informationElements);
InformationElementUtil.Vsa vsa = InformationElementUtil.getHS2VendorSpecificIE(
scanResult.informationElements);
// Lookup ANQP data in the cache.
long bssid;
try {
bssid = Utils.parseMac(scanResult.BSSID);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Invalid BSSID provided in the scan result: " + scanResult.BSSID);
return allMatches;
}
ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(scanResult.SSID, bssid, scanResult.hessid,
vsa.anqpDomainID);
ANQPData anqpEntry = mAnqpCache.getEntry(anqpKey);
if (anqpEntry == null) {
mAnqpRequestManager.requestANQPElements(bssid, anqpKey,
roamingConsortium.anqpOICount > 0,
vsa.hsRelease == NetworkDetail.HSRelease.R2);
Log.d(TAG, "ANQP entry not found for: " + anqpKey);
return allMatches;
}
for (Map.Entry entry : mProviders.entrySet()) {
PasspointProvider provider = entry.getValue();
PasspointMatch matchStatus = provider.match(anqpEntry.getElements(), roamingConsortium);
if (matchStatus == PasspointMatch.HomeProvider
|| matchStatus == PasspointMatch.RoamingProvider) {
allMatches.add(Pair.create(provider, matchStatus));
}
}
if (allMatches.size() != 0) {
for (Pair match : allMatches) {
Log.d(TAG, String.format("Matched %s to %s as %s", scanResult.SSID,
match.first.getConfig().getHomeSp().getFqdn(),
match.second == PasspointMatch.HomeProvider ? "Home Provider"
: "Roaming Provider"));
}
} else {
Log.d(TAG, "No matches not found for " + scanResult.SSID);
}
return allMatches;
}
简单看起来mAnqpCache要缓存对应扫描结果的anqpKey才行,类似于普通WiFi的已保存网络么。。。
对应的add逻辑是在这边
private class CallbackHandler implements PasspointEventHandler.Callbacks {
private final Context mContext;
CallbackHandler(Context context) {
mContext = context;
}
@Override
public void onANQPResponse(long bssid,
Map anqpElements) {
// Notify request manager for the completion of a request.
ANQPNetworkKey anqpKey =
mAnqpRequestManager.onRequestCompleted(bssid, anqpElements != null);
if (anqpElements == null || anqpKey == null) {
// Query failed or the request wasn't originated from us (not tracked by the
// request manager). Nothing to be done.
return;
}
// Add new entry to the cache.
mAnqpCache.addEntry(anqpKey, anqpElements);
}
http://androidxref.com/9.0.0_r3/xref/frameworks/opt/net/wifi/service/java/com/android/server/wifi/hotspot2/PasspointEventHandler.java#114
/**
* Invoked when ANQP query is completed.
* TODO(zqiu): currently ANQP completion notification is through WifiMonitor,
* this shouldn't be needed once we switch over to wificond for ANQP requests.
* @param anqpEvent ANQP result data retrieved. ANQP elements could be empty in the event to
* indicate any failures.
*/
public void notifyANQPDone(AnqpEvent anqpEvent) {
if (anqpEvent == null) return;
mCallbacks.onANQPResponse(anqpEvent.getBssid(), anqpEvent.getElements());
}
http://androidxref.com/9.0.0_r3/xref/frameworks/opt/net/wifi/service/java/com/android/server/wifi/hotspot2/PasspointManager.java#468
/**
* Notify the completion of an ANQP request.
* TODO(zqiu): currently the notification is done through WifiMonitor,
* will no longer be the case once we switch over to use wificond.
*/
public void notifyANQPDone(AnqpEvent anqpEvent) {
mHandler.notifyANQPDone(anqpEvent);
}
http://androidxref.com/9.0.0_r3/xref/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java#4437
case WifiMonitor.ANQP_DONE_EVENT:
// TODO(zqiu): remove this when switch over to wificond for ANQP requests.
mPasspointManager.notifyANQPDone((AnqpEvent) message.obj);
break;
...
mWifiMonitor.registerHandler(mInterfaceName, WifiMonitor.ANQP_DONE_EVENT, getHandler());
ANQP_DONE_EVENT还是WifiMonitor报上来的
WifiMonitor
/**
* Broadcast the ANQP done event to all the handlers registered for this event.
*
* @param iface Name of iface on which this occurred.
* @param anqpEvent ANQP result retrieved.
*/
public void broadcastAnqpDoneEvent(String iface, AnqpEvent anqpEvent) {
sendMessage(iface, ANQP_DONE_EVENT, anqpEvent);
}
http://androidxref.com/9.0.0_r3/xref/frameworks/opt/net/wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java#2383
private class SupplicantStaIfaceHalCallback extends ISupplicantStaIfaceCallback.Stub {
private String mIfaceName;
private boolean mStateIsFourway = false; // Used to help check for PSK password mismatch
SupplicantStaIfaceHalCallback(@NonNull String ifaceName) {
mIfaceName = ifaceName;
}
...
@Override
public void onStateChanged(int newState, byte[/* 6 */] bssid, int id,
ArrayList ssid) {
synchronized (mLock) {
logCallback("onStateChanged");
SupplicantState newSupplicantState = supplicantHidlStateToFrameworkState(newState);
WifiSsid wifiSsid =
WifiSsid.createFromByteArray(NativeUtil.byteArrayFromArrayList(ssid));
String bssidStr = NativeUtil.macAddressFromByteArray(bssid);
mStateIsFourway = (newState == ISupplicantStaIfaceCallback.State.FOURWAY_HANDSHAKE);
if (newSupplicantState == SupplicantState.COMPLETED) {
mWifiMonitor.broadcastNetworkConnectionEvent(
mIfaceName, getCurrentNetworkId(mIfaceName), bssidStr);
}
mWifiMonitor.broadcastSupplicantStateChangeEvent(
mIfaceName, getCurrentNetworkId(mIfaceName), wifiSsid,
bssidStr, newSupplicantState);
}
}
这个后面再看吧。。。
/**
* Given a list of Passpoint networks (with both provider and scan info), find and return
* the one with highest score. The score is calculated using
* {@link PasspointNetworkScore#calculateScore}.
*
* @param networkList List of Passpoint networks
* @param currentNetworkSsid The SSID of the currently connected network, null if not connected
* @return {@link PasspointNetworkCandidate}
*/
private PasspointNetworkCandidate findBestNetwork(
List networkList, String currentNetworkSsid) {
PasspointNetworkCandidate bestCandidate = null;
int bestScore = Integer.MIN_VALUE;
for (PasspointNetworkCandidate candidate : networkList) {
ScanDetail scanDetail = candidate.mScanDetail;
PasspointMatch match = candidate.mMatchStatus;
boolean isActiveNetwork = TextUtils.equals(currentNetworkSsid,
ScanResultUtil.createQuotedSSID(scanDetail.getSSID()));
int score = PasspointNetworkScore.calculateScore(match == PasspointMatch.HomeProvider,
scanDetail, mPasspointManager.getANQPElements(scanDetail.getScanResult()),
isActiveNetwork);
if (score > bestScore) {
bestCandidate = candidate;
bestScore = score;
}
}
localLog("Best Passpoint network " + bestCandidate.mScanDetail.getSSID() + " provided by "
+ bestCandidate.mProvider.getConfig().getHomeSp().getFqdn());
return bestCandidate;
}
PasspointNetworkScore
/**
* Calculate and return a score associated with the given Passpoint network.
* The score is calculated with the following preferences:
* - Prefer home provider
* - Prefer network that provides Internet access
* - Prefer network with active WAN port with available load
* - Prefer network that provides unrestricted IP address
* - Prefer currently active network
* - Prefer AP with higher RSSI
*
* This can be expanded for additional preference in the future (e.g. AP station count, link
* speed, and etc).
*
* @param isHomeProvider Flag indicating home provider
* @param scanDetail The ScanDetail associated with the AP
* @param isActiveNetwork Flag indicating current active network
* @return integer score
*/
public static int calculateScore(boolean isHomeProvider, ScanDetail scanDetail,
Map anqpElements, boolean isActiveNetwork) {
NetworkDetail networkDetail = scanDetail.getNetworkDetail();
int score = 0;
if (isHomeProvider) {
score += HOME_PROVIDER_AWARD;
}
// Adjust score based on Internet accessibility.
score += (networkDetail.isInternet() ? 1 : -1) * INTERNET_ACCESS_AWARD;
// Adjust score based on the network type.
score += NETWORK_TYPE_SCORES.get(networkDetail.getAnt());
if (anqpElements != null) {
HSWanMetricsElement wm =
(HSWanMetricsElement) anqpElements.get(ANQPElementType.HSWANMetrics);
if (wm != null) {
if (wm.getStatus() != HSWanMetricsElement.LINK_STATUS_UP || wm.isCapped()) {
score -= WAN_PORT_DOWN_OR_CAPPED_PENALTY;
}
}
IPAddressTypeAvailabilityElement ipa = (IPAddressTypeAvailabilityElement)
anqpElements.get(ANQPElementType.ANQPIPAddrAvailability);
if (ipa != null) {
Integer v4Score = IPV4_SCORES.get(ipa.getV4Availability());
Integer v6Score = IPV6_SCORES.get(ipa.getV6Availability());
v4Score = v4Score != null ? v4Score : 0;
v6Score = v6Score != null ? v6Score : 0;
score += (v4Score + v6Score);
}
}
score += RSSI_SCORE.lookupScore(scanDetail.getScanResult().level, isActiveNetwork);
return score;
}
* - Prefer home provider
* - Prefer network that provides Internet access
* - Prefer network with active WAN port with available load
* - Prefer network that provides unrestricted IP address
* - Prefer currently active network
* - Prefer AP with higher RSSI
补充一下,还有网络是否私人公开也会影响
NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.FreePublic, PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.ChargeablePublic,
PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.PrivateWithGuest,
PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Private,
PUBLIC_OR_PRIVATE_NETWORK_AWARDS);
NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Personal, PERSONAL_OR_EMERGENCY_NETWORK_AWARDS);
NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.EmergencyOnly,
PERSONAL_OR_EMERGENCY_NETWORK_AWARDS);
NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.Wildcard, 0);
NETWORK_TYPE_SCORES.put(NetworkDetail.Ant.TestOrExperimental, 0);
/**
* Award points for public or private network.
*/
@VisibleForTesting
public static final int PUBLIC_OR_PRIVATE_NETWORK_AWARDS = 4;
/**
* Award points for personal or emergency network.
*/
@VisibleForTesting
public static final int PERSONAL_OR_EMERGENCY_NETWORK_AWARDS = 2;
passpoint自动连接
先决条件是passpoint类型的ap并且当前mAnqpCache包含
* - Prefer home provider
* - Prefer network that provides Internet access
* - Prefer network with active WAN port with available load
* - Prefer network that provides unrestricted IP address
* - Prefer currently active network
* - Prefer AP with higher RSSI
补充一下,还有网络类型,比如是否私人公开也会影响