Station 模式下的连接的实现主要是在 ClientModeImpl 中。ClientModeImpl 继承自一个状态机,它的状态转换模型大如下图所示:
1 扫描结果可连接,自动回调连接
//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<WifiNetworkSelector.ClientModeManagerState> cmmStates = new ArrayList<>();
Set<String> connectedSsids = new HashSet<>();
boolean hasExistingSecondaryCmm = false;
for (ClientModeManager clientModeManager :
mActiveModeWarden.getInternetConnectivityClientModeManagers()) {
Log.d(TAG, "getInternetConnectivityClientModeManagers = " + clientModeManager.getRole());
if (clientModeManager.getRole() == ROLE_CLIENT_SECONDARY_LONG_LIVED) {
hasExistingSecondaryCmm = true;
Log.d(TAG, "hasExistingSecondaryCmm = " + hasExistingSecondaryCmm);
}
mWifiChannelUtilization.refreshChannelStatsAndChannelUtilization(
clientModeManager.getWifiLinkLayerStats(),
WifiChannelUtilization.UNKNOWN_FREQ);
WifiInfo wifiInfo = clientModeManager.syncRequestConnectionInfo();
if (clientModeManager.isConnected()) {
connectedSsids.add(wifiInfo.getSSID());
}
cmmStates.add(new WifiNetworkSelector.ClientModeManagerState(clientModeManager));
}
// We don't have any existing secondary CMM, but are we allowed to create a secondary CMM
// and do we have a request for OEM_PAID/OEM_PRIVATE request? If yes, we need to perform
// network selection to check if we have any potential candidate for the secondary CMM
// creation.
if (!hasExistingSecondaryCmm
&& (mOemPaidConnectionAllowed || mOemPrivateConnectionAllowed)) {
// prefer OEM PAID requestor if it exists.
WorkSource oemPaidOrOemPrivateRequestorWs =
mOemPaidConnectionRequestorWs != null
? mOemPaidConnectionRequestorWs
: mOemPrivateConnectionRequestorWs;
if (oemPaidOrOemPrivateRequestorWs == null) {
Log.e(TAG, "Both mOemPaidConnectionRequestorWs & mOemPrivateConnectionRequestorWs "
+ "are null!");
}
if (oemPaidOrOemPrivateRequestorWs != null
&& mActiveModeWarden.canRequestMoreClientModeManagersInRole(
oemPaidOrOemPrivateRequestorWs,
ROLE_CLIENT_SECONDARY_LONG_LIVED)) {
// Add a placeholder CMM state to ensure network selection is performed for a
// potential second STA creation.
cmmStates.add(new WifiNetworkSelector.ClientModeManagerState());
}
}
for (ScanDetail scanDetail : scanDetails) {
Log.d(TAG, "handleScanResults:" + scanDetail.getScanResult());
}
// Check if any blocklisted BSSIDs can be freed.
mWifiBlocklistMonitor.tryEnablingBlockedBssids(scanDetails);
for (ScanDetail scanDetail : scanDetails) {
Log.d(TAG, "handleScanResults After mWifiBlocklistMonitor :" + scanDetail.getScanResult());
}
Set<String> bssidBlocklist = mWifiBlocklistMonitor.updateAndGetBssidBlocklistForSsids(
connectedSsids);
updateUserDisabledList(scanDetails);
for (ScanDetail scanDetail : scanDetails) {
Log.d(TAG, "handleScanResults After:" + scanDetail.getScanResult());
}
// Clear expired recent failure statuses
mConfigManager.cleanupExpiredRecentFailureReasons();
localLog(listenerName + " onResults: start network selection");
List<WifiCandidates.Candidate> candidates = mNetworkSelector.getCandidatesFromScan(
scanDetails, bssidBlocklist, cmmStates, mUntrustedConnectionAllowed,
mOemPaidConnectionAllowed, mOemPrivateConnectionAllowed);
mLatestCandidates = candidates;
mLatestCandidatesTimestampMs = mClock.getElapsedSinceBootMillis();
if (mDeviceMobilityState == WifiManager.DEVICE_MOBILITY_STATE_HIGH_MVMT
&& mContext.getResources().getBoolean(
R.bool.config_wifiHighMovementNetworkSelectionOptimizationEnabled)) {
candidates = filterCandidatesHighMovement(candidates, listenerName, isFullScan);
}
mLastNetworkSelectionTimeStamp = mClock.getElapsedSinceBootMillis();
mWifiLastResortWatchdog.updateAvailableNetworks(
mNetworkSelector.getConnectableScanDetails());
mWifiMetrics.countScanResults(scanDetails);
// No candidates, return early.
if (candidates == null || candidates.size() == 0) {
localLog(listenerName + ": No candidates");
handleScanResultsWithNoCandidate(handleScanResultsListener);
return;
}
// We have an oem paid/private network request and device supports STA + STA, check if there
// are oem paid/private suggestions.
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);
}
startScan 完成以后回调到 handleScanResults 函数来连接到扫描到的网络中。ClientModeImpl 的初始状态 DisconnectedState 的父状态 ConnectableState 的 enter 中创建了 IpClient。
当处理 Wi-Fi 扫描结果时,这段代码涉及到对不同状态和候选网络的处理。下面逐行进行分析:
创建一个空的 WifiNetworkSelector.ClientModeManagerState 列表,用于存储各个客户端模式管理器的状态。
创建一个空的 Set,用于存储连接的 SSID(Service Set Identifier)。
初始化一个标志变量 hasExistingSecondaryCmm,用于检测是否存在次要的客户端模式管理器。
对于每个活动的客户端模式管理器,执行以下操作:
- 检查客户端模式管理器的角色,如果是 ROLE_CLIENT_SECONDARY_LONG_LIVED,则设置 hasExistingSecondaryCmm 为 true。
- 刷新 Wi-Fi 通道统计信息和通道利用率。
- 同步获取 Wi-Fi 连接信息。
- 如果客户端模式管理器已连接,则将连接的 SSID 添加到集合中。
- 创建并添加客户端模式管理器的状态到 cmmStates 列表中。
在没有现有次要客户端模式管理器的情况下,检查是否允许创建次要客户端模式管理器,如果允许则执行以下操作:
- 根据是否存在 OEM_PAID 或 OEM_PRIVATE 请求,选择合适的请求器。
- 如果请求器存在且可以在 ROLE_CLIENT_SECONDARY_LONG_LIVED 角色中请求更多的客户端模式管理器,则添加一个占位的客户端模式管理器状态。
尝试启用被阻止的 BSSID,更新并获取被阻止的 BSSID 列表。更新用户禁用的网络列表。清除过期的失败连接。从扫描结果中获取候选网络。如果设备的移动性状态为高移动性且启用了高移动性网络选择优化,则过滤候选网络。更新看门狗的可用网络列表。记录扫描结果计数。如果没有候选网络,记录日志并通过 handleScanResultsWithNoCandidate 处理没有候选网络的情况。
如果存在 OEM_PAID 或 OEM_PRIVATE 请求并且设备支持 STA + STA 并发连接,执行以下操作:
- 将候选网络分成两组:oem paid/oem private 和非 oem paid/oem private。
- 如果存在次要候选网络,则使用 handleCandidatesFromScanResultsUsingSecondaryCmmIfAvailable 处理。
- 否则,使用 handleCandidatesFromScanResultsForPrimaryCmmUsingMbbIfAvailable 处理。
两个 handle 函数继续向下都调用到 connectToNetworkUsingCmm,在正常情况下函数执行到最后,调用 ConnectHandler 的 triggerConnectWhenDisconnected -> …-> ClientModeImpl 的 startConnectToNetwork。startConnectToNetwork 向 ClientModeImpl 的内部状态机发送一个 CMD_START_CONNECT 消息,处理过程在手动连接过程也会有。
2 手动连接
//packages/apps/Settings/src/com/android/settings/wifi/WifiSettings.java
public void onSubmit(WifiDialog2 dialog) {
final int dialogMode = dialog.getMode();
final WifiConfiguration config = dialog.getController().getConfig();
final WifiEntry wifiEntry = dialog.getWifiEntry();
if (dialogMode == WifiConfigUiBase2.MODE_MODIFY) {
if (config == null) {
Toast.makeText(getContext(), R.string.wifi_failed_save_message,
Toast.LENGTH_SHORT).show();
} else {
mWifiManager.save(config, mSaveListener);
}
} else if (dialogMode == WifiConfigUiBase2.MODE_CONNECT
|| (dialogMode == WifiConfigUiBase2.MODE_VIEW && wifiEntry.canConnect())) {
if (config == null) {
connect(wifiEntry, false /* editIfNoConfig */,
false /* fullScreenEdit*/);
} else {
mWifiManager.connect(config, new WifiConnectActionListener());
}
}
}
- 获取对话框模式 (dialogMode)、config 和 WifiEntry。
- 如果对话框模式是修改模式,则检查配置是否为空。如果为空,显示一个短暂的 Toast 提示用户保存失败;如果不为空,使用 WifiManager 保存配置,并传入一个 SaveListener。
- 如果对话框模式是连接模式,或者是查看模式且可以连接,则继续执行下面的操作。
- 如果配置为空,调用connect方法,传入WifiEntry,编辑标志位false,全屏编辑标志位false。
- 如果配置不为空,则使用WifiManager连接配置,并传入一个连接操作监听器(WifiConnectActionListener)。
我们主要考虑连接模式。
//packages/modules/Wifi/framework/java/android/net/wifi/WifiManager.java
public void connect(@NonNull WifiConfiguration config, @Nullable ActionListener listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
connectInternal(config, WifiConfiguration.INVALID_NETWORK_ID, listener);
}
private void connectInternal(@Nullable WifiConfiguration config, int networkId,
@Nullable ActionListener listener) {
ActionListenerProxy listenerProxy = null;
if (listener != null) {
listenerProxy = new ActionListenerProxy("connect", mLooper, listener);
}
mService.connect(config, networkId, listenerProxy);
}
通过 Binder 机制调用到 WifiServiceImpl 的 connect 方法。
//packages/modules/Wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
public void connect(WifiConfiguration config, int netId, @Nullable IActionListener callback) {
int uid = Binder.getCallingUid();
if (!isPrivileged(Binder.getCallingPid(), uid)) {
throw new SecurityException(TAG + ": Permission denied");
}
if (config != null) {
config.networkId = removeSecurityTypeFromNetworkId(config.networkId);
}
final int netIdArg = removeSecurityTypeFromNetworkId(netId);
mLog.info("connect uid=%").c(uid).flush();
mWifiThreadRunner.post(() -> {
ActionListenerWrapper wrapper = new ActionListenerWrapper(callback);
final NetworkUpdateResult result;
// if connecting using WifiConfiguration, save the network first
if (config != null) {
if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
mWifiMetrics.logUserActionEvent(
UserActionEvent.EVENT_ADD_OR_UPDATE_NETWORK, config.networkId);
}
result = mWifiConfigManager.addOrUpdateNetwork(config, uid);
if (!result.isSuccess()) {
Log.e(TAG, "connect adding/updating config=" + config + " failed");
wrapper.sendFailure(WifiManager.ERROR);
return;
}
broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
} else {
if (mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
mWifiMetrics.logUserActionEvent(
UserActionEvent.EVENT_MANUAL_CONNECT, netIdArg);
}
result = new NetworkUpdateResult(netIdArg);
}
WifiConfiguration configuration = mWifiConfigManager
.getConfiguredNetwork(result.getNetworkId());
if (configuration == null) {
Log.e(TAG, "connect to Invalid network Id=" + netIdArg);
wrapper.sendFailure(WifiManager.ERROR);
return;
}
if (configuration.enterpriseConfig != null
&& configuration.enterpriseConfig.isAuthenticationSimBased()) {
int subId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(configuration);
if (!mWifiCarrierInfoManager.isSimReady(subId)) {
Log.e(TAG, "connect to SIM-based config=" + configuration
+ "while SIM is absent");
wrapper.sendFailure(WifiManager.ERROR);
return;
}
if (mWifiCarrierInfoManager.requiresImsiEncryption(subId)
&& !mWifiCarrierInfoManager.isImsiEncryptionInfoAvailable(subId)) {
Log.e(TAG, "Imsi protection required but not available for Network="
+ configuration);
wrapper.sendFailure(WifiManager.ERROR);
return;
}
}
mMakeBeforeBreakManager.stopAllSecondaryTransientClientModeManagers(() ->
mConnectHelper.connectToNetwork(result, wrapper, uid));
});
}
它首先检查调用进程是否具有特权权限(privileged),如果没有则抛出 SecurityException 异常。
接下来,它将从网络 ID 和 WifiConfiguration 对象中删除安全类型信息,然后记录连接尝试的用户ID(uid)。
然后,它将在 WifiThreadRunner 线程上运行一个 Runnable,该 Runnable 执行以下操作:
- 如果使用 WifiConfiguration 连接,会先保存网络配置。它首先检查调用进程是否具有网络设置权限,并记录用户操作事件。然后调用 WifiConfigManager 的 addOrUpdateNetwork 方法添加或更新网络配置,并检查操作结果。如果操作失败,将发送一个失败回调并返回。
- 如果不是使用 WifiConfiguration 连接,而是使用网络 ID 连接,它会继续进行连接操作。
- 根据网络 ID 获取配置的网络对象,如果获取失败,则发送一个失败回调并返回。
- 如果配置的网络是SIM卡认证类型的网络,并且 SIM 卡不可用,则发送一个失败回调并返回。
- 如果使用 IMSI 加密保护,并且 IMSI 加密信息不可用,则发送一个失败回调并返回。
- 最后,调用 MakeBeforeBreakManager 的 stopAllSecondaryTransientClientModeManagers 方法,停止所有辅助短暂客户端模式管理器。然后调用 ConnectHelper的connectToNetwork 方法来连接到网络。
//packages/modules/Wifi/service/java/com/android/server/wifi/ConnectHelper.java
public void connectToNetwork(
@NonNull NetworkUpdateResult result,
@NonNull ActionListenerWrapper wrapper,
int callingUid) {
mActiveModeWarden.disconnectSecondaryClientIfNecessary(
mWifiConfigManager.getConfiguredNetwork(result.getNetworkId()));
connectToNetwork(
mActiveModeWarden.getPrimaryClientModeManager(), result, wrapper, callingUid);
}
public void connectToNetwork(
@NonNull ClientModeManager clientModeManager,
@NonNull NetworkUpdateResult result,
@NonNull ActionListenerWrapper wrapper,
int callingUid) {
int netId = result.getNetworkId();
if (mWifiConfigManager.getConfiguredNetwork(netId) == null) {
Log.e(TAG, "connectToNetwork Invalid network Id=" + netId);
wrapper.sendFailure(WifiManager.ERROR);
return;
}
mWifiConfigManager.updateBeforeConnect(netId, callingUid);
clientModeManager.connectNetwork(result, wrapper, callingUid);
}
//packages/modules/Wifi/service/java/com/android/server/wifi/ConcreteClientModeManager.java
public void connectNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
int callingUid) {
Log.d(getTag(), "connectNetwork: " + result);
getClientMode().connectNetwork(result, wrapper, callingUid);
}
//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
public void connectNetwork(NetworkUpdateResult result, ActionListenerWrapper wrapper,
int callingUid) {
Message message =
obtainMessage(CMD_CONNECT_NETWORK, new ConnectNetworkMessage(result, wrapper));
message.sendingUid = callingUid;
sendMessage(message);
}
经过一系列调用,一直调用到 ClientModeImpl 中,调用到 connectNetwork 函数,其中向自身状态机中发送了一个 CMD_CONNECT_NETWORK 消息。
//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java/DisconnectedState.class
case CMD_CONNECT_NETWORK: {
ConnectNetworkMessage cnm = (ConnectNetworkMessage) message.obj;
if (mIpClient == null) {
logd("IpClient is not ready, CONNECT_NETWORK dropped");
cnm.listener.sendFailure(WifiManager.ERROR);
break;
}
NetworkUpdateResult result = cnm.result;
int netId = result.getNetworkId();
connectToUserSelectNetwork(
netId, message.sendingUid, result.hasCredentialChanged());
mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CONNECT_NETWORK,
mWifiConfigManager.getConfiguredNetwork(netId));
cnm.listener.sendSuccess();
break;
}
private void connectToUserSelectNetwork(int netId, int uid, boolean forceReconnect) {
logd("connectToUserSelectNetwork netId " + netId + ", uid " + uid
+ ", forceReconnect = " + forceReconnect);
if (!forceReconnect && (mLastNetworkId == netId || mTargetNetworkId == netId)) {
// We're already connecting/connected to the user specified network, don't trigger a
// reconnection unless it was forced.
logi("connectToUserSelectNetwork already connecting/connected=" + netId);
} else {
mWifiConnectivityManager.prepareForForcedConnection(netId);
if (uid == Process.SYSTEM_UID) {
mWifiMetrics.setNominatorForNetwork(netId,
WifiMetricsProto.ConnectionEvent.NOMINATOR_MANUAL);
}
startConnectToNetwork(netId, uid, SUPPLICANT_BSSID_ANY);
}
}
public void startConnectToNetwork(int networkId, int uid, String bssid) {
sendMessage(CMD_START_CONNECT, networkId, uid, bssid);
}
在 CMD_CONNECT_NETWORK 的情况下,首先获取 ConnectNetworkMessage 对象中的数据。然后检查 mIpClient是否为空,如果为空表示 IpClient 没有准备好,会记录日志,发送一个错误回调给监听器,并结束。否则,从结果中获取网络 ID,并调用 connectToUserSelectNetwork 方法连接到用户选择的网络。连接过程中会记录连接事件到 WifiMetrics,然后发送一个成功回调给监听器。
connectToUserSelectNetwork 方法会比较当前正在连接/已连接的网络 ID 和用户选择的网络 ID,如果相同且没有强制重新连接的需求,则不触发重新连接。否则,调用 WifiConnectivityManager 的 prepareForForcedConnection 方法准备强制连接网络,如果连接是由系统进程发起的,则将网络 ID 设为手动指定,并调用 startConnectToNetwork 方法开始连接网络。
startConnectToNetwork 方法向自身状态机发送 CMD_START_CONNECT 消息,将网络 ID、UID 和 BSSID 作为参数传递。
//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java/DisconnectedState.class
case CMD_START_CONNECT: {
if (mIpClient == null) {
logd("IpClient is not ready, START_CONNECT dropped");
break;
}
/* connect command coming from auto-join */
int netId = message.arg1;
int uid = message.arg2;
String bssid = (String) message.obj;
mSentHLPs = false;
// Stop lingering (if it was lingering before) if we start a new connection.
// This means that the ClientModeManager was reused for another purpose, so it
// should no longer be in lingering mode.
mClientModeManager.setShouldReduceNetworkScore(false);
if (!hasConnectionRequests()) {
if (mNetworkAgent == null) {
loge("CMD_START_CONNECT but no requests and not connected,"
+ " bailing");
//break;
} else if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
loge("CMD_START_CONNECT but no requests and connected, but app "
+ "does not have sufficient permissions, bailing");
break;
}
}
WifiConfiguration config =
mWifiConfigManager.getConfiguredNetworkWithoutMasking(netId);
logd("CMD_START_CONNECT "
+ " my state " + getCurrentState().getName()
+ " nid=" + netId
+ " roam=" + mIsAutoRoaming);
if (config == null) {
loge("CMD_START_CONNECT and no config, bail out...");
break;
}
mTargetNetworkId = netId;
// Update scorecard while there is still state from existing connection
mLastScanRssi = mWifiConfigManager.findScanRssi(netId,
mWifiHealthMonitor.getScanRssiValidTimeMs());
mWifiScoreCard.noteConnectionAttempt(mWifiInfo, mLastScanRssi, config.SSID);
mWifiBlocklistMonitor.setAllowlistSsids(config.SSID, Collections.emptyList());
mWifiBlocklistMonitor.updateFirmwareRoamingConfiguration(Set.of(config.SSID));
updateWifiConfigOnStartConnection(config, bssid);
reportConnectionAttemptStart(config, mTargetBssid,
WifiMetricsProto.ConnectionEvent.ROAM_UNRELATED);
String currentMacAddress = mWifiNative.getMacAddress(mInterfaceName);
mWifiInfo.setMacAddress(currentMacAddress);
Log.i(getTag(), "Connecting with " + currentMacAddress + " as the mac address");
mTargetWifiConfiguration = config;
mNetworkNotFoundEventCount = 0;
/* Check for FILS configuration again after updating the config */
if (config.isFilsSha256Enabled() || config.isFilsSha384Enabled()) {
boolean isIpClientStarted = startIpClient(config, true);
if (isIpClientStarted) {
mIpClientWithPreConnection = true;
break;
}
}
connectToNetwork(config);
break;
}
首先检查 IpClient 是否为空,如果为空,则打印日志并中断该消息的处理。
获取消息的参数:netId、UID 和 BSSID。
设置 SentHLPs 标志位为 false。
如果没有连接请求,则检查是否存在 NetworkAgent。如果不存在网络代理且应用程序没有足够的权限访问网络设置,则打印日志并中断该消息的处理。
通过网络ID获取对应的 WifiConfiguration 配置。
更新一些状态和数据,包括 TargetNetworkId、上一次扫描到的信号强度 LastScanRssi、连接尝试记入WifiScoreCard、更新 WifiBlocklistMonitor 的配置等。
更新 Wi-Fi 接口的 MAC 地址为当前的物理地址。
设置目标 Wi-Fi 配置为当前配置。
连接前检查FILS配置,如果开启了FILS SHA256或FILS SHA384,则启动IP客户端。
调用connectToNetwork方法连接到指定的网络。
//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java
private void connectToNetwork(WifiConfiguration config) {
if ((config != null) && mWifiNative.connectToNetwork(mInterfaceName, config)) {
mWifiLastResortWatchdog.noteStartConnectTime(config.networkId);
mWifiMetrics.logStaEvent(mInterfaceName, StaEvent.TYPE_CMD_START_CONNECT, config);
mIsAutoRoaming = false;
transitionTo(mL2ConnectingState);
} else {
loge("CMD_START_CONNECT Failed to start connection to network " + config);
mTargetWifiConfiguration = null;
stopIpClient();
reportConnectionAttemptEnd(
WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
WifiMetricsProto.ConnectionEvent.HLF_NONE,
WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
}
}
调用 WifiNative.connectToNetwork,WifiNative中通过 HDIL 调用到下层,来实现具体的连接过程。
//packages/modules/Wifi/service/java/com/android/server/wifi/WifiNative.java
public boolean connectToNetwork(@NonNull String ifaceName, WifiConfiguration configuration) {
// Abort ongoing scan before connect() to unblock connection request.
mWifiCondManager.abortScan(ifaceName);
return mSupplicantStaIfaceHal.connectToNetwork(ifaceName, configuration);
}
//packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaIfaceHal.java
public boolean connectToNetwork(@NonNull String ifaceName, @NonNull WifiConfiguration config) {
synchronized (mLock) {
logd("connectToNetwork " + config.getProfileKey());
WifiConfiguration currentConfig = getCurrentNetworkLocalConfig(ifaceName);
if (WifiConfigurationUtil.isSameNetwork(config, currentConfig)) {
String networkSelectionBSSID = config.getNetworkSelectionStatus()
.getNetworkSelectionBSSID();
String networkSelectionBSSIDCurrent =
currentConfig.getNetworkSelectionStatus().getNetworkSelectionBSSID();
if (Objects.equals(networkSelectionBSSID, networkSelectionBSSIDCurrent)) {
logd("Network is already saved, will not trigger remove and add operation.");
} else {
logd("Network is already saved, but need to update BSSID.");
if (!setCurrentNetworkBssid(
ifaceName,
config.getNetworkSelectionStatus().getNetworkSelectionBSSID())) {
loge("Failed to set current network BSSID.");
return false;
}
mCurrentNetworkLocalConfigs.put(ifaceName, new WifiConfiguration(config));
}
} else {
mCurrentNetworkRemoteHandles.remove(ifaceName);
mCurrentNetworkLocalConfigs.remove(ifaceName);
mLinkedNetworkLocalAndRemoteConfigs.remove(ifaceName);
if (!removeAllNetworks(ifaceName)) {
loge("Failed to remove existing networks");
return false;
}
Pair<SupplicantStaNetworkHal, WifiConfiguration> pair =
addNetworkAndSaveConfig(ifaceName, config);
if (pair == null) {
loge("Failed to add/save network configuration: " + config
.getProfileKey());
return false;
}
mCurrentNetworkRemoteHandles.put(ifaceName, pair.first);
mCurrentNetworkLocalConfigs.put(ifaceName, pair.second);
}
SupplicantStaNetworkHal networkHandle =
checkSupplicantStaNetworkAndLogFailure(ifaceName, "connectToNetwork");
if (networkHandle == null) {
loge("No valid remote network handle for network configuration: "
+ config.getProfileKey());
return false;
}
PmkCacheStoreData pmkData = mPmkCacheEntries.get(config.networkId);
if (pmkData != null
&& !WifiConfigurationUtil.isConfigForPskNetwork(config)
&& pmkData.expirationTimeInSec > mClock.getElapsedSinceBootMillis() / 1000) {
logi("Set PMK cache for config id " + config.networkId);
if (networkHandle.setPmkCache(pmkData.data)) {
mWifiMetrics.setConnectionPmkCache(ifaceName, true);
}
}
if (!networkHandle.select()) {
loge("Failed to select network configuration: " + config.getProfileKey());
return false;
}
return true;
}
}
首先终止扫描,接着调用 SupplicantStaIfaceHal 中的方式来实现连接。接下来是 connectToNetwork 的具体实现
- 通过调用
getCurrentNetworkLocalConfig
方法获取当前网络的本地配置。- 如果传入的Wi-Fi配置和当前配置是同一个网络(使用
WifiConfigurationUtil.isSameNetwork
方法进行比较),则进一步比较网络选择的BSSID。 - 如果网络选择的BSSID相同,表示网络已保存,不需要执行删除和添加操作,打印日志进行提示。
- 如果网络选择的BSSID不同,表示网络已保存但需要更新BSSID,调用
setCurrentNetworkBssid
方法设置当前网络的BSSID,并更新当前网络的本地配置。
- 如果传入的Wi-Fi配置和当前配置是同一个网络(使用
- 如果传入的Wi-Fi配置和当前配置不是同一个网络,需要执行以下操作:
- 从相应的映射表中移除当前网络的远程句柄、本地配置和本地与远程配置之间的映射关系。
- 调用
removeAllNetworks
方法移除所有已存在的网络。 - 调用
addNetworkAndSaveConfig
方法添加并保存网络配置,返回网络的远程句柄和本地配置。- 如果添加/保存网络配置失败,打印错误日志并返回失败。
- 将网络的远程句柄、本地配置分别存储到
mCurrentNetworkRemoteHandles
和mCurrentNetworkLocalConfigs
映射表中。
- 使用
checkSupplicantStaNetworkAndLogFailure
方法检查并获取Wi-Fi网络的远程句柄,如果句柄无效,打印错误日志并返回失败。 - 从
mPmkCacheEntries
缓存中获取与该网络ID相关的PMK缓存数据。 - 如果找到了PMK缓存数据,并且网络配置不是PSK网络类型,且PMK缓存未过期,则调用
setPmkCache
方法将PMK缓存设置到远程网络句柄中,并记录连接成功使用了PMK缓存的度量指标。 - 如果网络选择成功,调用远程网络句柄的
select
方法来选择配置的网络。如果选择失败,则打印错误日志并返回失败。
//packages/modules/Wifi/service/java/com/android/server/wifi/SupplicantStaNetworkHal.java
public boolean select() {
synchronized (mLock) {
final String methodStr = "select";
if (!checkISupplicantStaNetworkAndLogFailure(methodStr)) return false;
try {
SupplicantStatus status = mISupplicantStaNetwork.select();
return checkStatusAndLogFailure(status, methodStr);
} catch (RemoteException e) {
handleRemoteException(e, methodStr);
return false;
}
}
}
//external/wpa_supplicant_8/wpa_supplicant/hidl/1.4/sta_network.cpp
Return<void> StaNetwork::select(select_cb _hidl_cb)
{
return validateAndCall(
this, SupplicantStatusCode::FAILURE_NETWORK_INVALID,
&StaNetwork::selectInternal, _hidl_cb);
}
SupplicantStatus StaNetwork::selectInternal()
{
struct wpa_ssid *wpa_ssid = retrieveNetworkPtr();
if (wpa_ssid->disabled == 2) {
return {SupplicantStatusCode::FAILURE_UNKNOWN, ""};
}
struct wpa_supplicant *wpa_s = retrieveIfacePtr();
wpa_s->scan_min_time.sec = 0;
wpa_s->scan_min_time.usec = 0;
wpa_supplicant_select_network(wpa_s, wpa_ssid);
return {SupplicantStatusCode::SUCCESS, ""};
}
SupplicantStaNetworkHal 中主要是向下调用到 HAL 层,由 wpa_supplicant 进程来实现具体的认证关联过程。接下来回到 ClientModeImpl 中,调用 WifiLastResortWatchdog 的 noteStartConnectTime 方法记录连接开始的时间,用于后续监控和故障恢复。将自动漫游标志设置为 false,表示当前连接不是自动漫游。更新 WifiInfo 的 networkId、BSSID 和 SSID。最后将当前状态转换为 L2ConnectingState。
在 HAL 层,在连接过程中,wpa_state 发生改变则会回调到 SupplicantStaIfaceCallbackImpl 的 onStateChanged。其中通过 WifiMonitor 发送了一个广播,调用 broadcastNetworkConnectionEvent 广播了 network connection event。
//packages/modules/Wifi/server/java/com/android/server/wifi/WifiMonitor.java
public void broadcastNetworkConnectionEvent(String iface, int networkId, boolean filsHlpSent,
WifiSsid ssid, String bssid) {
sendMessage(iface, NETWORK_CONNECTION_EVENT,
new NetworkConnectionEventInfo(networkId, ssid, bssid, filsHlpSent));
}
WifiMonitor 向 ClientModeImpl 发送了一个 NETWORK_CONNECTION_EVENT 消息。在 L2ConnectingState 的状态下处理,但是实际上并未处理。所以交给它的父状态来处理,在父状态 ConnectingOrConnectedState 中处理。
//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java/ConnectingOrConnectedState.class
case WifiMonitor.NETWORK_CONNECTION_EVENT: {
if (mVerboseLoggingEnabled) log("ConnectingOrConnectedState Network connection established");
NetworkConnectionEventInfo connectionInfo =
(NetworkConnectionEventInfo) message.obj;
mLastNetworkId = connectionInfo.networkId;
mSentHLPs = connectionInfo.isFilsConnection;
if (mSentHLPs) mWifiMetrics.incrementL2ConnectionThroughFilsAuthCount();
mWifiConfigManager.clearRecentFailureReason(mLastNetworkId);
mLastBssid = connectionInfo.bssid;
// TODO: This check should not be needed after ClientModeImpl refactor.
// Currently, the last connected network configuration is left in
// wpa_supplicant, this may result in wpa_supplicant initiating connection
// to it after a config store reload. Hence the old network Id lookups may not
// work, so disconnect the network and let network selector reselect a new
// network.
WifiConfiguration config = getConnectedWifiConfigurationInternal();
if (config == null) {
logw("Connected to unknown networkId " + mLastNetworkId
+ ", disconnecting...");
sendMessage(CMD_DISCONNECT, StaEvent.DISCONNECT_UNKNOWN_NETWORK);
break;
}
mWifiInfo.setBSSID(mLastBssid);
mWifiInfo.setNetworkId(mLastNetworkId);
mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName));
ScanDetailCache scanDetailCache =
mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId);
ScanResult scanResult = null;
if (scanDetailCache != null && mLastBssid != null) {
scanResult = scanDetailCache.getScanResult(mLastBssid);
if (scanResult != null) {
mWifiInfo.setFrequency(scanResult.frequency);
}
}
if (mWifiInfo.getFrequency() == -1) {
updateLinkLayerStatsRssiSpeedFrequencyCapabilities();
}
// We need to get the updated pseudonym from supplicant for EAP-SIM/AKA/AKA'
if (config.enterpriseConfig != null
&& config.enterpriseConfig.isAuthenticationSimBased()) {
mLastSubId = mWifiCarrierInfoManager.getBestMatchSubscriptionId(config);
mLastSimBasedConnectionCarrierName =
mWifiCarrierInfoManager.getCarrierNameForSubId(mLastSubId);
String anonymousIdentity;
if (mInterfaceName.equals("sawlan")){
anonymousIdentity = null;
}
else{
anonymousIdentity = mWifiNative.getEapAnonymousIdentity(mInterfaceName);
}
if (!TextUtils.isEmpty(anonymousIdentity)
&& !WifiCarrierInfoManager
.isAnonymousAtRealmIdentity(anonymousIdentity)) {
String decoratedPseudonym = mWifiCarrierInfoManager
.decoratePseudonymWith3GppRealm(config,
anonymousIdentity);
if (decoratedPseudonym != null) {
anonymousIdentity = decoratedPseudonym;
}
if (mVerboseLoggingEnabled) {
log("EAP Pseudonym: " + anonymousIdentity);
}
// Save the pseudonym only if it is a real one
config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
} else {
// Clear any stored pseudonyms
config.enterpriseConfig.setAnonymousIdentity(null);
}
mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID);
if (config.isPasspoint()) {
mPasspointManager.setAnonymousIdentity(config);
} else if (config.fromWifiNetworkSuggestion) {
mWifiNetworkSuggestionsManager.setAnonymousIdentity(config);
}
}
// When connecting to Passpoint, ask for the Venue URL
if (config.isPasspoint()) {
mTermsAndConditionsUrl = null;
if (scanResult == null && mLastBssid != null) {
// The cached scan result of connected network would be null at the
// first connection, try to check full scan result list again to look up
// matched scan result associated to the current SSID and BSSID.
scanResult = mScanRequestProxy.getScanResult(mLastBssid);
}
if (scanResult != null) {
mPasspointManager.requestVenueUrlAnqpElement(scanResult);
}
}
transitionTo(mL3ProvisioningState);
break;
}
- 如果启用了详细日志记录(mVerboseLoggingEnabled为true),输出日志信息。
- 获取网络连接相关信息,包括网络ID(networkId)、是否使用Fils连接(isFilsConnection)、BSSID(bssid)等。
- 如果使用了Fils连接(mSentHLPs为true),通过mWifiMetrics增加L2连接通过Fils认证的计数。
- 清除最近的连接失败原因(mWifiConfigManager.clearRecentFailureReason)。
- 设置最后连接的BSSID和网络ID(mLastBssid和mLastNetworkId)。
- 根据当前接口名(mInterfaceName)获取MAC地址(mWifiInfo.setMacAddress)。
- 获取与当前网络相关的扫描结果,并更新WiFi信息的频率(mWifiInfo.setFrequency)。
- 如果频率为-1,则通过更新链路层统计的RSSI、速度、频率和能力信息来更新频率(updateLinkLayerStatsRssiSpeedFrequencyCapabilities)。
- 如果当前网络配置是基于SIM卡认证的企业网络(EAP-SIM/AKA/AKA’),则获取更新的伪装标识(pseudonym)。
- 如果伪装标识存在且不是匿名域标识,则使用3GPP域装饰伪装标识,并保存到企业配置中。
- 根据网络类型的不同,更新网络配置并通知相应的管理器(mPasspointManager、mWifiNetworkSuggestionsManager)。
- 如果连接的是Passpoint网络,通过扫描结果获取场所URL,并请求相关的ANQP元素(mPasspointManager.requestVenueUrlAnqpElement)。
- 切换到L3ProvisioningState状态。
L3ProvisioningState 的父状态是 L2ConnectedState。
//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java/L2ConnectedState.class
public void enter() {
if (mVerboseLoggingEnabled) Log.v(getTag(), "enter L2ConnectedState");
mRssiPollToken++;
if (mEnableRssiPolling) {
if (isPrimary()) {
mLinkProbeManager.resetOnNewConnection();
}
sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);
}
sendNetworkChangeBroadcast(DetailedState.CONNECTING);
// If this network was explicitly selected by the user, evaluate whether to inform
// ConnectivityService of that fact so the system can treat it appropriately.
final WifiConfiguration config = getConnectedWifiConfigurationInternal();
final boolean explicitlySelected;
// Non primary CMMs is never user selected. This prevents triggering the No Internet
// dialog for those networks, which is difficult to handle.
if (isPrimary() && isRecentlySelectedByTheUser(config)) {
// If explicitlySelected is true, the network was selected by the user via Settings
// or QuickSettings. If this network has Internet access, switch to it. Otherwise,
// switch to it only if the user confirms that they really want to switch, or has
// already confirmed and selected "Don't ask again".
explicitlySelected =
mWifiPermissionsUtil.checkNetworkSettingsPermission(config.lastConnectUid);
if (mVerboseLoggingEnabled) {
log("Network selected by UID " + config.lastConnectUid + " explicitlySelected="
+ explicitlySelected);
}
} else {
explicitlySelected = false;
}
if (mVerboseLoggingEnabled) {
log("explicitlySelected=" + explicitlySelected + " acceptUnvalidated="
+ config.noInternetAccessExpected);
}
NetworkAgentConfig.Builder naConfigBuilder = new NetworkAgentConfig.Builder()
.setLegacyType(ConnectivityManager.TYPE_WIFI)
.setLegacyTypeName(NETWORKTYPE)
.setExplicitlySelected(explicitlySelected)
.setUnvalidatedConnectivityAcceptable(
explicitlySelected && config.noInternetAccessExpected)
.setPartialConnectivityAcceptable(config.noInternetAccessExpected);
if (config.carrierMerged) {
String subscriberId = null;
TelephonyManager subMgr = mTelephonyManager.createForSubscriptionId(
config.subscriptionId);
if (subMgr != null) {
subscriberId = subMgr.getSubscriberId();
}
if (subscriberId != null) {
naConfigBuilder.setSubscriberId(subscriberId);
}
}
if (mVcnManager == null && SdkLevel.isAtLeastS()) {
mVcnManager = mContext.getSystemService(VcnManager.class);
}
if (mVcnManager != null) {
mVcnPolicyChangeListener = new WifiVcnNetworkPolicyChangeListener();
mVcnManager.addVcnNetworkPolicyChangeListener(new HandlerExecutor(getHandler()),
mVcnPolicyChangeListener);
}
final NetworkAgentConfig naConfig = naConfigBuilder.build();
final NetworkCapabilities nc = getCapabilities(
getConnectedWifiConfigurationInternal(), getConnectedBssidInternal());
// This should never happen.
if (mNetworkAgent != null) {
Log.wtf(getTag(), "mNetworkAgent is not null: " + mNetworkAgent);
mNetworkAgent.unregister();
}
mNetworkAgent = mWifiInjector.makeWifiNetworkAgent(nc, mLinkProperties, naConfig,
mNetworkFactory.getProvider(), new WifiNetworkAgentCallback());
log("mNetworkAgent = " + mNetworkAgent);
mWifiScoreReport.setNetworkAgent(mNetworkAgent);
// We must clear the config BSSID, as the wifi chipset may decide to roam
// from this point on and having the BSSID specified in the network block would
// cause the roam to faile and the device to disconnect
clearTargetBssid("L2ConnectedState");
mWifiMetrics.setWifiState(mInterfaceName, WifiMetricsProto.WifiLog.WIFI_ASSOCIATED);
mWifiScoreCard.noteNetworkAgentCreated(mWifiInfo,
mNetworkAgent.getNetwork().getNetId());
mWifiBlocklistMonitor.handleBssidConnectionSuccess(mLastBssid, mWifiInfo.getSSID());
// too many places to record connection failure with too many failure reasons.
// So only record success here.
mWifiMetrics.noteFirstL2ConnectionAfterBoot(true);
mCmiMonitor.onL2Connected(mClientModeManager);
mIsLinkedNetworkRoaming = false;
if (mInterfaceName.equals("sawlan")) {
sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
}
}
检查是否启用了 RSSI 轮询并判断当前网络是不是主要网络,是的话则重置链接探测管理器。调用 sendMessage 方法发送一个消息 CMD_RSSI_POLL,带有 RssiPollToken参数。调用 sendNetworkChangeBroadcast 方法发送一个网络状态改变的广播,状态为 CONNECTING。获取当前连接的 WifiConfiguration对象。判断网络是否是用户显式选择的网络,并将结果赋值给 explicitlySelected。创建一个 NetworkAgentConfig.Builder 对象,并设置与网络代理相关的配置信息。接下来是针对合并运营商网络的处理逻辑。如果网络配置的 carrierMerged 属性为真,则获取订阅 ID 对应的运营商 ID,并将其设置到 naConfigBuilder 中。使用 naConfigBuilder 构建 NetworkAgentConfig 对象。调用getCapabilities 方法获取当前连接的NetworkCapabilities。创建一个 WifiNetworkAgent 对象。设置 WifiScoreReport的网络代理为新创建的 NetworkAgent。清除目标 BSSID,这样设备就可以在漫游时自由选择最佳的接入点,而不会受限于特定的 BSSID。接下来是各种记录事件。最后根据 InterfaceName 是否为"sawlan"来发送CMD_IP_CONFIGURATION_SUCCESSFUL 消息。我们配置的 Station 的 InterfaceName 就是 sawlan。
//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java/L3ProvisioningState.class
public void enter() {
if (mVerboseLoggingEnabled) Log.v(getTag(), "enter L3ProvisioningState");
WifiConfiguration currentConfig = getConnectedWifiConfigurationInternal();
if (mIpClientWithPreConnection && mIpClient != null) {
mIpClient.notifyPreconnectionComplete(mSentHLPs);
mIpClientWithPreConnection = false;
mSentHLPs = false;
} else {
startIpClient(currentConfig, false);
}
// Get Link layer stats so as we get fresh tx packet counters
getWifiLinkLayerStats();
}
mIpClientWithPreConnection = false;接下来 startIpClient,来获取相关的 L3 层配置。
在 L2ConnectedState 中的 enter 方法中,发送了一个 CMD_IP_CONFIGURATION_SUCCESSFUL 消息。
//packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java/L2ConnectedState.class
case CMD_IP_CONFIGURATION_SUCCESSFUL: {
if (getConnectedWifiConfigurationInternal() == null || mNetworkAgent == null) {
// The current config may have been removed while we were connecting,
// trigger a disconnect to clear up state.
reportConnectionAttemptEnd(
WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION,
WifiMetricsProto.ConnectionEvent.HLF_NONE,
WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
mWifiNative.disconnect(mInterfaceName);
} else {
handleSuccessfulIpConfiguration();
sendConnectedState();
transitionTo(mL3ConnectedState);
}
break;
}
同样在 L2ConnectedState 处理这条消息,成功获取到相关 IP 配置。接着转换到 L3ConnectedState。
packages/modules/Wifi/service/java/com/android/server/wifi/ClientModeImpl.java/L3ConnectedState.class
public void enter() {
if (mVerboseLoggingEnabled) {
log("enter L3ConnectedState mScreenOn=" + mScreenOn);
}
reportConnectionAttemptEnd(
WifiMetrics.ConnectionEvent.FAILURE_NONE,
WifiMetricsProto.ConnectionEvent.HLF_NONE,
WifiMetricsProto.ConnectionEvent.FAILURE_REASON_UNKNOWN);
mWifiConnectivityManager.handleConnectionStateChanged(
mClientModeManager,
WifiConnectivityManager.WIFI_STATE_CONNECTED);
registerConnected();
mTargetWifiConfiguration = null;
mWifiScoreReport.reset();
mLastSignalLevel = -1;
// Not roaming anymore
mIsAutoRoaming = false;
mTargetNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
mWifiLastResortWatchdog.connectedStateTransition(true);
mWifiStateTracker.updateState(mInterfaceName, WifiStateTracker.CONNECTED);
// Inform WifiLockManager
mWifiLockManager.updateWifiClientConnected(mClientModeManager, true);
WifiConfiguration config = getConnectedWifiConfigurationInternal();
mWifiScoreReport.startConnectedNetworkScorer(
mNetworkAgent.getNetwork().getNetId(), isRecentlySelectedByTheUser(config));
updateLinkLayerStatsRssiAndScoreReport();
mWifiScoreCard.noteIpConfiguration(mWifiInfo);
// too many places to record L3 failure with too many failure reasons.
// So only record success here.
mWifiMetrics.noteFirstL3ConnectionAfterBoot(true);
}