//packages/modules/Wifi/service/java/com/android/server/wifi/WifiConnectivityManager.java
private void handleScanResults(@NonNull List<ScanDetail> scanDetails,
@NonNull String listenerName,
boolean isFullScan,
@NonNull HandleScanResultsListener handleScanResultsListener) {
//....
List<WifiCandidates.Candidate> candidates = mNetworkSelector.getCandidatesFromScan(
scanDetails, bssidBlocklist, cmmStates, mUntrustedConnectionAllowed,
mOemPaidConnectionAllowed, mOemPrivateConnectionAllowed);
mLatestCandidates = candidates;
mLatestCandidatesTimestampMs = mClock.getElapsedSinceBootMillis();
//...
if ((mOemPaidConnectionAllowed || mOemPrivateConnectionAllowed)
&& mActiveModeWarden.isStaStaConcurrencySupportedForRestrictedConnections()) {
// Split the candidates based on whether they are oem paid/oem private or not.
Map<Boolean, List<WifiCandidates.Candidate>> candidatesPartitioned =
candidates.stream()
.collect(Collectors.groupingBy(c -> c.isOemPaid() || c.isOemPrivate()));
List<WifiCandidates.Candidate> primaryCmmCandidates =
candidatesPartitioned.getOrDefault(false, Collections.emptyList());
List<WifiCandidates.Candidate> secondaryCmmCandidates =
candidatesPartitioned.getOrDefault(true, Collections.emptyList());
List<WifiCandidates.Candidate> secondaryCmmCandidates_filtered =
getSecondaryCandidatesFiltered(secondaryCmmCandidates);
// Some oem paid/private suggestions found, use secondary cmm flow.
if (!secondaryCmmCandidates.isEmpty()) {
handleCandidatesFromScanResultsUsingSecondaryCmmIfAvailable(
listenerName, primaryCmmCandidates, secondaryCmmCandidates_filtered,
handleScanResultsListener);
return;
}
// intentional fallthrough: No oem paid/private suggestions, fallback to legacy flow.
}
handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
listenerName, candidates, handleScanResultsListener);
}
扫描结果可连接,回调 handleScanResults。handleScanResult 中首先进行一下细节处理,接着通过WifiNetworkSelector 来获取扫描结果中可连接的候选网络,在获取候选网络时,会对目前每一个网络连接结合扫描结果进行判断,如果扫描结果 SSID 和当前连接的网络的 SSID 不同,则可放入候选。如果所有扫描结果和当前的网络连接都是同一个 SSID,则需要根据 RSSI 判断信号强度是否足够强,足够强的话就不需要,进行网络选择。
(选择候选网络这里,到底是如何判断需要重新连接网络,是否是根据当前网络的强度还是什么,源码中选择候选网络时调用 isNetworkSelectionNeeded 判断是否需要挑选候选网络,只要搜索到有跟当前网络 SSID 不同的网络就会需要 select,而只有所有搜到的 SSID 都和当前网络相同的时候,这时候才会判断当前的网络强度是否有需要去切换网络,很困惑。)
以上过程中会成功获取 候选网络,接着处理这些候选网络并连接。分为两种情况,存在 Secondary Network 和不存在。我们看其中一种:handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable。
//packages/modules/Wifi/service/java/com/android/server/wifi/WifiConnectivityManager.java
private void handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable(
@NonNull String listenerName, @NonNull List<WifiCandidates.Candidate> candidates,
@NonNull HandleScanResultsListener handleScanResultsListener) {
WifiConfiguration candidate = mNetworkSelector.selectNetwork(candidates);
if (candidate != null) {
localLog(listenerName + ": WNS candidate-" + candidate.SSID);
connectToNetworkForPrimaryCmmUsingMbbIfAvailable(candidate);
handleScanResultsWithCandidate(handleScanResultsListener);
} else {
localLog(listenerName + ": No candidate");
handleScanResultsWithNoCandidate(handleScanResultsListener);
}
}
以 handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable 为例,首先从 candidates 中选出最合适的一个候选网络,接下来看看连接到该网络
//packages/modules/Wifi/service/java/com/android/server/wifi/WifiConnectivityManager.java
private void connectToNetworkUsingCmm(@NonNull ClientModeManager clientModeManager,
@NonNull WifiConfiguration targetNetwork,
@NonNull ConnectHandler connectHandler) {
//...
WifiConfiguration currentNetwork = coalesce(
clientModeManager.getConnectedWifiConfiguration(),
clientModeManager.getConnectingWifiConfiguration());
String currentBssid = coalesce(
clientModeManager.getConnectedBssid(), clientModeManager.getConnectingBssid());
String currentAssociationId = getAssociationId(currentNetwork, currentBssid);
// Already on desired network id, we need to trigger roam since the device does not
// support firmware roaming (already checked in
// isClientModeManagerConnectedOrConnectingToCandidate()).
if (currentNetwork != null
&& (currentNetwork.networkId == targetNetwork.networkId
|| (mContext.getResources().getBoolean(R.bool.config_wifiEnableLinkedNetworkRoaming)
&& currentNetwork.isLinked(targetNetwork)))) {
localLog("connectToNetwork(" + clientModeManager + "): Roam to " + targetAssociationId
+ " from " + currentAssociationId);
connectHandler.triggerRoamWhenConnected(currentNetwork, targetNetwork, targetBssid);
return;
}
//...
}
两个 handle 函数继续向下都调用到 connectToNetworkUsingCmm,在满足如上代码中的条件判断:
if (currentNetwork != null
&& (currentNetwork.networkId == targetNetwork.networkId
|| (mContext.getResources().getBoolean(R.bool.config_wifiEnableLinkedNetworkRoaming)
&& currentNetwork.isLinked(targetNetwork))))
这种情况下会执行到 ConnectHandler 的 triggerRoamWhenConnected,在其中开始WiFi漫游操作。一直向下调用到 ClientModeImpl.startRoamToNetwork,向自身状态机发送一个 CMD_START_ROAM 消息。在 L3ConnectedState 状态下处理这个消息
//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java/L3ConnectedState.class
public boolean processMessage(Message message) {
switch (message.what) {
//...
case CMD_START_ROAM: {
/* Connect command coming from auto-join */
int netId = message.arg1;
String bssid = (String) message.obj;
if (bssid == null) {
bssid = SUPPLICANT_BSSID_ANY;
}
WifiConfiguration config =
mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
if (config == null) {
loge("CMD_START_ROAM and no config, bail out...");
break;
}
mLastScanRssi = mWifiConfigManager.findScanRssi(netId,
mWifiHealthMonitor.getScanRssiValidTimeMs());
mWifiScoreCard.noteConnectionAttempt(mWifiInfo, mLastScanRssi, config.SSID);
setTargetBssid(config, bssid);
mTargetNetworkId = netId;
logd("CMD_START_ROAM sup state "
+ " my state " + getCurrentState().getName()
+ " nid=" + Integer.toString(netId)
+ " config " + config.getProfileKey()
+ " targetRoamBSSID " + mTargetBssid);
reportConnectionAttemptStart(config, mTargetBssid,
WifiMetricsProto.ConnectionEvent.ROAM_ENTERPRISE);
if (mWifiNative.roamToNetwork(mInterfaceName, config)) {
mTargetWifiConfiguration = config;
mIsAutoRoaming = true;
mWifiMetrics.logStaEvent(
mInterfaceName, StaEvent.TYPE_CMD_START_ROAM, config);
transitionTo(mRoamingState);
} else {
loge("CMD_START_ROAM Failed to start roaming to network " + config);
reportConnectionAttemptEnd(
WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
WifiMetricsProto.ConnectionEvent.HLF_NONE,
WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
mMessageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
break;
}
break;
}
//...
}
}
从message中获取netId和bssid的值。
通过netId从mWifiConfigManager获取对应的WifiConfiguration对象。
设置目标BSSID为config中的值,并将目标网络ID设置为netId。
如果调用mWifiNative.roamToNetwork方法成功,则进行以下操作:
- 将mTargetWifiConfiguration设置为config。
- 将mIsAutoRoaming设置为true。
- 记录StaEvent.TYPE_CMD_START_ROAM类型的事件日志。
- 进入mRoamingState状态。
//packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
public boolean roamToNetwork(@NonNull String ifaceName, WifiConfiguration config) {
synchronized (mLock) {
if (updateOnLinkedNetworkRoaming(ifaceName, config.networkId)) {
SupplicantStaNetworkHal networkHandle = getCurrentNetworkRemoteHandle(ifaceName);
if (networkHandle == null) {
loge("Roaming config matches a linked config, but a linked network handle was"
+ " not found.");
return false;
}
return networkHandle.select();
}
if (getCurrentNetworkId(ifaceName) != config.networkId) {
Log.w(TAG, "Cannot roam to a different network, initiate new connection. "
+ "Current network ID: " + getCurrentNetworkId(ifaceName));
return connectToNetwork(ifaceName, config);
}
String bssid = config.getNetworkSelectionStatus().getNetworkSelectionBSSID();
logd("roamToNetwork" + config.getProfileKey() + " (bssid " + bssid + ")");
SupplicantStaNetworkHal networkHandle =
checkSupplicantStaNetworkAndLogFailure(ifaceName, "roamToNetwork");
if (networkHandle == null || !networkHandle.setBssid(bssid)) {
loge("Failed to set new bssid on network: " + config.getProfileKey());
return false;
}
if (!reassociate(ifaceName)) {
loge("Failed to trigger reassociate");
return false;
}
return true;
}
}
- 首先,检查是否正在尝试与已关联的网络进行连接(而不是漫游)。如果是,则选择已存在的Supplicant网络。
- 如果当前网络与提供的网络配置不匹配,则触发新的连接尝试而不是漫游。
- 如果匹配,设置新的BSSID并触发重新关联操作,以执行漫游。
//packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
public boolean reassociate(@NonNull String ifaceName) {
synchronized (mLock) {
final String methodStr = "reassociate";
ISupplicantStaIface iface = checkSupplicantStaIfaceAndLogFailure(ifaceName, methodStr);
if (iface == null) return false;
try {
SupplicantStatus status = iface.reassociate();
return checkStatusAndLogFailure(status, methodStr);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
return false;
}
}
}
reassociate 方法中调用到了 wpa_supplicant 进程中。wpa_supplicant 于 kernel driver 层交互,实现 WiFi 重新关联。