Wi-Fi 连接过程可以从 Settings App 中点击任意 Wi-Fi 条目连接说起。点击条目以后会弹出一个对话框,根据不同的 Wi-Fi 类型需要填入必要的信息,再点击连接按钮,发起连接过程。
点击 Dialog 上的按钮会路由到 WifiDialog.BUTTON_SUBMIT 分支,如果是已经连接成功的 Wi-Fi 则路由到 WifiDialog.BUTTON_FORGET 分支。WifiDialog.BUTTON_SUBMIT 分支进一步调用了 submit(…) 方法。
submit(…) 方法中首先调用 WifiConfigController 类的 getConfig() 方法获取此 Wi-Fi 的 WifiConfiguration。接着就会调用 WifiManager save(…) 方法保存此 WifiConfiguration,最后进一步调用 connect(…) 方法,connect(…) 方法实际调用了 WifiManager 的 connect(…) 方法。
packages/apps/Settings/src/com/android/settings/wifi/WifiSettings.java
public class WifiSettings extends RestrictedSettingsFragment
implements DialogInterface.OnClickListener, Indexable, WifiTracker.WifiListener,
AccessPointListener {
......
@Override
public void onClick(DialogInterface dialogInterface, int button) {
if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
forget();
} else if (button == WifiDialog.BUTTON_SUBMIT) {
if (mDialog != null) {
submit(mDialog.getController());
}
}
}
/* package */ void submit(WifiConfigController configController) {
final WifiConfiguration config = configController.getConfig();
if (config == null) {
if (mSelectedAccessPoint != null
&& mSelectedAccessPoint.isSaved()) {
connect(mSelectedAccessPoint.getConfig());
}
} else if (configController.isModify()) {
mWifiManager.save(config, mSaveListener);
} else {
mWifiManager.save(config, mSaveListener);
if (mSelectedAccessPoint != null) { // Not an "Add network"
connect(config);
}
}
mWifiTracker.resumeScanning();
}
......
protected void connect(final WifiConfiguration config) {
MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_CONNECT);
mWifiManager.connect(config, mConnectListener);
}
......
}
getConfig() 方法实现行数不少,实际上就是在构建 WifiConfiguration 对象。根据不同的 Wi-Fi 安全类型去填充 WifiConfiguration 中不同的字段,这里包括我们家庭常用的 WPA2/Personal 以及企业常用的 WPA2/Enterprise。企业级 Wi-Fi 会额外构建 WifiEnterpriseConfig 对象。另外,这部分构建 WifiConfiguration 对象的代码对 App 做连接 Wi-Fi 功能有非常大的参考意义。
packages/apps/Settings/src/com/android/settings/wifi/WifiConfigController.java
public class WifiConfigController implements TextWatcher,
AdapterView.OnItemSelectedListener, OnCheckedChangeListener {
......
/* package */ WifiConfiguration getConfig() {
if (!mEdit) {
return null;
}
WifiConfiguration config = new WifiConfiguration();
if (mAccessPoint == null) {
config.SSID = AccessPoint.convertToQuotedString(
mSsidView.getText().toString());
// If the user adds a network manually, assume that it is hidden.
config.hiddenSSID = true;
} else if (!mAccessPoint.isSaved()) {
config.SSID = AccessPoint.convertToQuotedString(
mAccessPoint.getSsidStr());
} else {
config.networkId = mAccessPoint.getConfig().networkId;
}
switch (mAccessPointSecurity) {
case AccessPoint.SECURITY_NONE:
config.allowedKeyManagement.set(KeyMgmt.NONE);
break;
case AccessPoint.SECURITY_WEP:
config.allowedKeyManagement.set(KeyMgmt.NONE);
config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED);
if (mPasswordView.length() != 0) {
int length = mPasswordView.length();
String password = mPasswordView.getText().toString();
// WEP-40, WEP-104, and 256-bit WEP (WEP-232?)
if ((length == 10 || length == 26 || length == 58) &&
password.matches("[0-9A-Fa-f]*")) {
config.wepKeys[0] = password;
} else {
config.wepKeys[0] = '"' + password + '"';
}
}
break;
case AccessPoint.SECURITY_PSK:
config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
if (mPasswordView.length() != 0) {
String password = mPasswordView.getText().toString();
if (password.matches("[0-9A-Fa-f]{64}")) {
config.preSharedKey = password;
} else {
config.preSharedKey = '"' + password + '"';
}
}
break;
case AccessPoint.SECURITY_EAP:
config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
config.enterpriseConfig = new WifiEnterpriseConfig();
int eapMethod = mEapMethodSpinner.getSelectedItemPosition();
int phase2Method = mPhase2Spinner.getSelectedItemPosition();
config.enterpriseConfig.setEapMethod(eapMethod);
switch (eapMethod) {
case Eap.PEAP:
// PEAP supports limited phase2 values
// Map the index from the PHASE2_PEAP_ADAPTER to the one used
// by the API which has the full list of PEAP methods.
switch(phase2Method) {
case WIFI_PEAP_PHASE2_NONE:
config.enterpriseConfig.setPhase2Method(Phase2.NONE);
break;
case WIFI_PEAP_PHASE2_MSCHAPV2:
config.enterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
break;
case WIFI_PEAP_PHASE2_GTC:
config.enterpriseConfig.setPhase2Method(Phase2.GTC);
break;
default:
Log.e(TAG, "Unknown phase2 method" + phase2Method);
break;
}
break;
default:
// The default index from PHASE2_FULL_ADAPTER maps to the API
config.enterpriseConfig.setPhase2Method(phase2Method);
break;
}
String caCert = (String) mEapCaCertSpinner.getSelectedItem();
if (caCert.equals(unspecifiedCert)) caCert = "";
config.enterpriseConfig.setCaCertificateAlias(caCert);
String clientCert = (String) mEapUserCertSpinner.getSelectedItem();
if (clientCert.equals(unspecifiedCert)) clientCert = "";
config.enterpriseConfig.setClientCertificateAlias(clientCert);
if (eapMethod == Eap.SIM || eapMethod == Eap.AKA || eapMethod == Eap.AKA_PRIME) {
config.enterpriseConfig.setIdentity("");
config.enterpriseConfig.setAnonymousIdentity("");
} else if (eapMethod == Eap.PWD) {
config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString());
config.enterpriseConfig.setAnonymousIdentity("");
} else {
config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString());
config.enterpriseConfig.setAnonymousIdentity(
mEapAnonymousView.getText().toString());
}
if (mPasswordView.isShown()) {
// For security reasons, a previous password is not displayed to user.
// Update only if it has been changed.
if (mPasswordView.length() > 0) {
config.enterpriseConfig.setPassword(mPasswordView.getText().toString());
}
} else {
// clear password
config.enterpriseConfig.setPassword(mPasswordView.getText().toString());
}
break;
default:
return null;
}
config.setIpConfiguration(
new IpConfiguration(mIpAssignment, mProxySettings,
mStaticIpConfiguration, mHttpProxy));
return config;
}
......
}
connect(…) 表示使用给定的 networkId 连接到网络,它用来代替 enableNetwork(),saveConfiguration() 和 reconnect()
save(…) 方法将给定的网络保存在 supplicant 配置中。如果网络已经存在,则会更新配置。默认情况下启用新网络。
- 对于新网络,将使用此函数代替 addNetwork(),enableNetwork() 和 saveConfiguration() 的顺序调用;
- 对于现有网络,它可以完成 updateNetwork() 和 saveConfiguration() 的任务。
这两个方法都先调用了 validateChannel() 来检查 AsyncChannel 是否为 null,如果为 null 就会抛出 IllegalStateException 异常。接着通过 AsyncChannel 发送消息。
AsyncChannel 表示两个 Handler 之间的异步通道,两个 Handler 可能位于同一进程也可能在不同进程中,AysncChannel 可以使用两种协议。第一种是简单的 request/reply 协议,其中服务器不需要知道是哪个客户机发出请求。第二种使用模型是 server/destination 也需要知道它连接的是哪个客户机。
frameworks/base/wifi/java/android/net/wifi/WifiManager.java
public class WifiManager {
......
public void connect(WifiConfiguration config, ActionListener listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
validateChannel();
// 当传递配置对象时,arg1 使用 INVALID_NETWORK_ID
// arg1 用于在网络已经存在时传递网络 id
sAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
putListener(listener), config);
}
public void save(WifiConfiguration config, ActionListener listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
validateChannel();
sAsyncChannel.sendMessage(SAVE_NETWORK, 0, putListener(listener), config);
}
......
}
sAsyncChannel 初始化的时候传入了 Messenger 对象,代表目的地处理方。这是从 getWifiServiceMessenger() 方法拿到的。此方法获取 WifiService Handler 的一个引用,这被客户端用来与 WifiService 建立 AsyncChannel。
frameworks/base/wifi/java/android/net/wifi/WifiManager.java
public class WifiManager {
......
public Messenger getWifiServiceMessenger() {
try {
return mService.getWifiServiceMessenger();
} catch (RemoteException e) {
return null;
} catch (SecurityException e) {
return null;
}
}
......
}
先进行权限检查,然后构建了一个 Messenger 对象返回。Messenger 构造器入参 mClientHandler 处理客户端发过来的消息。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
public final class WifiServiceImpl extends IWifiManager.Stub {
......
public Messenger getWifiServiceMessenger() {
enforceAccessPermission();
enforceChangePermission();
return new Messenger(mClientHandler);
}
......
}
客户端发来的消息在 ClientHandler handleMessage(…) 方法中得到处理。WifiManager.SAVE_NETWORK 消息就是 WifiManager 调用 save(…) 方法发送过来的。而 WifiManager.CONNECT_NETWORK 消息是 connect(…) 发过来的。这两个消息在这里直接被 WifiStateMachine 进行了转发。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
public final class WifiServiceImpl extends IWifiManager.Stub {
......
private class ClientHandler extends Handler {
ClientHandler(android.os.Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
......
/* 客户端命令被转发到状态机 */
case WifiManager.CONNECT_NETWORK:
case WifiManager.SAVE_NETWORK: {
WifiConfiguration config = (WifiConfiguration) msg.obj;
int networkId = msg.arg1;
if (msg.what == WifiManager.SAVE_NETWORK) {
Slog.e("WiFiServiceImpl ", "SAVE"
+ " nid=" + Integer.toString(networkId)
+ " uid=" + msg.sendingUid
+ " name="
+ mContext.getPackageManager().getNameForUid(msg.sendingUid));
}
if (msg.what == WifiManager.CONNECT_NETWORK) {
Slog.e("WiFiServiceImpl ", "CONNECT "
+ " nid=" + Integer.toString(networkId)
+ " uid=" + msg.sendingUid
+ " name="
+ mContext.getPackageManager().getNameForUid(msg.sendingUid));
}
if (config != null && isValid(config)) {
if (DBG) Slog.d(TAG, "Connect with config" + config);
mWifiStateMachine.sendMessage(Message.obtain(msg));
} else if (config == null
&& networkId != WifiConfiguration.INVALID_NETWORK_ID) {
if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);
mWifiStateMachine.sendMessage(Message.obtain(msg));
} else {
Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
if (msg.what == WifiManager.CONNECT_NETWORK) {
replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.INVALID_ARGS);
} else {
replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED,
WifiManager.INVALID_ARGS);
}
}
break;
}
case WifiManager.FORGET_NETWORK:
if (isOwner(msg.sendingUid)) {
mWifiStateMachine.sendMessage(Message.obtain(msg));
} else {
Slog.e(TAG, "Forget is not authorized for user");
replyFailed(msg, WifiManager.FORGET_NETWORK_FAILED,
WifiManager.NOT_AUTHORIZED);
}
break;
case WifiManager.START_WPS:
case WifiManager.CANCEL_WPS:
case WifiManager.DISABLE_NETWORK:
case WifiManager.RSSI_PKTCNT_FETCH: {
mWifiStateMachine.sendMessage(Message.obtain(msg));
break;
}
default: {
Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
break;
}
}
}
......
}
private ClientHandler mClientHandler;
......
}
保存网络配置,和连接都是在 ConnectModeState 类 processMessage(…) 方法中进行处理的。
保存 Wi-Fi 配置步骤:调用 WifiConfigStore 类 saveNetwork(…) 方法进行保存配置;
连接 Wi-Fi 步骤:
- 调用 WifiConfigStore 类 saveNetwork(…) 方法进行保存配置;
- 调用 WifiConfigStore 类 enableNetworkWithoutBroadcast(…) 方法进行 enable;
- 调用 WifiConfigStore 类 selectNetwork(…) 方法进行 select。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
public class WifiStateMachine extends StateMachine implements WifiNative.WifiPnoEventHandler,
WifiNative.WifiRssiEventHandler {
......
class ConnectModeState extends State {
@Override
public void enter() {
connectScanningService();
}
@Override
public boolean processMessage(Message message) {
WifiConfiguration config;
int netId;
boolean ok;
boolean didDisconnect;
String bssid;
String ssid;
NetworkUpdateResult result;
logStateAndMessage(message, getClass().getSimpleName());
switch (message.what) {
......
case WifiManager.CONNECT_NETWORK:
/**
* 对于一个新的网络,将传递一个配置来创建和连接。
* 对于一个现有的网络,将传递一个网络 id
*/
netId = message.arg1;
config = (WifiConfiguration) message.obj;
mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
boolean updatedExisting = false;
/* 保存网络配置 */
if (config != null) {
......
result = mWifiConfigStore.saveNetwork(config, message.sendingUid);
netId = result.getNetworkId();
}
......
// 请确保网络已启用,因为 supplicant 将不会重新启用它
mWifiConfigStore.enableNetworkWithoutBroadcast(netId, false);
if (mWifiConfigStore.selectNetwork(config, /* updatePriorities = */ true,
message.sendingUid) && mWifiNative.reconnect()) {
lastConnectAttemptTimestamp = System.currentTimeMillis();
targetWificonfiguration = mWifiConfigStore.getWifiConfiguration(netId);
/* 状态跟踪器在 completion/failure 时处理启用网络 */
mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
......
} else {
loge("Failed to connect config: " + config + " netId: " + netId);
replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.ERROR);
break;
}
break;
case WifiManager.SAVE_NETWORK:
mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
// Fall thru
case WifiStateMachine.CMD_AUTO_SAVE_NETWORK:
lastSavedConfigurationAttempt = null; // Used for debug
config = (WifiConfiguration) message.obj;
......
result = mWifiConfigStore.saveNetwork(config, WifiConfiguration.UNKNOWN_UID);
......
break;
......
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
......
}
添加/更新指定的配置并保存配置。
关键步骤:
- 调用 addOrUpdateNetworkNative(…) 添加或者更新配置;
- 如果是一个新网络,调用 WifiNative 类 enableNetwork(…) 方法 enable 网络;
- 调用 WifiNative 类 saveConfig(…) 保存配置。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiConfigStore.java
public class WifiConfigStore extends IpConfigStore {
......
NetworkUpdateResult saveNetwork(WifiConfiguration config, int uid) {
WifiConfiguration conf;
// 一个新的网络不能有 null SSID
if (config == null || (config.networkId == INVALID_NETWORK_ID &&
config.SSID == null)) {
return new NetworkUpdateResult(INVALID_NETWORK_ID);
}
......
boolean newNetwork = (config.networkId == INVALID_NETWORK_ID);
NetworkUpdateResult result = addOrUpdateNetworkNative(config, uid);
int netId = result.getNetworkId();
if (VDBG) localLog("WifiConfigStore: saveNetwork got it back netId=", netId);
/* enable 一个新网络 */
if (newNetwork && netId != INVALID_NETWORK_ID) {
if (VDBG) localLog("WifiConfigStore: will enable netId=", netId);
mWifiNative.enableNetwork(netId, false);
conf = mConfiguredNetworks.get(netId);
if (conf != null)
conf.status = Status.ENABLED;
}
conf = mConfiguredNetworks.get(netId);
if (conf != null) {
if (conf.autoJoinStatus != WifiConfiguration.AUTO_JOIN_ENABLED) {
if (VDBG) localLog("WifiConfigStore: re-enabling: " + conf.SSID);
// 重新启用自动加入,因为已经提供了新信息
conf.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED);
enableNetworkWithoutBroadcast(conf.networkId, false);
}
if (VDBG) {
loge("WifiConfigStore: saveNetwork got config back netId="
+ Integer.toString(netId)
+ " uid=" + Integer.toString(config.creatorUid));
}
}
mWifiNative.saveConfig();
sendConfiguredNetworksChangedBroadcast(conf, result.isNewNetwork() ?
WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
return result;
}
......
}
如果提供的 networkId 是 INVALID_NETWORK_ID,我们将创建一个新的空网络配置。否则,网络id应该引用现有的配置。添加网络调用了 WifiNative 类 addNetwork() 方法。接着会配置参数主要调用 WifiNative 类 setNetworkVariable(…) 方法实现。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java
public class WifiStateMachine extends StateMachine implements WifiNative.WifiPnoEventHandler,
WifiNative.WifiRssiEventHandler {
......
private NetworkUpdateResult addOrUpdateNetworkNative(WifiConfiguration config, int uid) {
......
int netId = config.networkId;
boolean newNetwork = false;
// INVALID_NETWORK_ID 的 networkId 表示我们希望创建一个新的网络
if (netId == INVALID_NETWORK_ID) {
WifiConfiguration savedConfig = mConfiguredNetworks.getByConfigKey(config.configKey());
if (savedConfig != null) {
netId = savedConfig.networkId;
} else {
if (mMOManager.getHomeSP(config.FQDN) != null) {
loge("addOrUpdateNetworkNative passpoint " + config.FQDN
+ " was found, but no network Id");
}
newNetwork = true;
// 添加网络
netId = mWifiNative.addNetwork();
if (netId < 0) {
loge("Failed to add a network!");
return new NetworkUpdateResult(INVALID_NETWORK_ID);
} else {
loge("addOrUpdateNetworkNative created netId=" + netId);
}
}
}
boolean updateFailed = true;
// 配置网络参数
setVariables: {
if (config.SSID != null &&
!mWifiNative.setNetworkVariable(
netId,
WifiConfiguration.ssidVarName,
encodeSSID(config.SSID))) {
loge("failed to set SSID: "+config.SSID);
break setVariables;
}
......
// 配置企业级 Wi-Fi 网络参数
if (config.enterpriseConfig != null &&
config.enterpriseConfig.getEapMethod() != WifiEnterpriseConfig.Eap.NONE) {
WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig;
......
}
updateFailed = false;
} // End of setVariables
if (updateFailed) {
if (newNetwork) {
mWifiNative.removeNetwork(netId);
loge("Failed to set a network variable, removed network: " + netId);
}
return new NetworkUpdateResult(INVALID_NETWORK_ID);
}
/*
* 网络配置的更新需要从 supplicant 那里读取它们,以更新 mConfiguredNetworks。
*/
WifiConfiguration currentConfig = mConfiguredNetworks.get(netId);
if (currentConfig == null) {
currentConfig = new WifiConfiguration();
currentConfig.setIpAssignment(IpAssignment.DHCP);
currentConfig.setProxySettings(ProxySettings.NONE);
currentConfig.networkId = netId;
if (config != null) {
// Carry over the creation parameters
currentConfig.selfAdded = config.selfAdded;
currentConfig.didSelfAdd = config.didSelfAdd;
currentConfig.ephemeral = config.ephemeral;
currentConfig.autoJoinUseAggressiveJoinAttemptThreshold
= config.autoJoinUseAggressiveJoinAttemptThreshold;
currentConfig.lastConnectUid = config.lastConnectUid;
currentConfig.lastUpdateUid = config.lastUpdateUid;
currentConfig.creatorUid = config.creatorUid;
currentConfig.creatorName = config.creatorName;
currentConfig.lastUpdateName = config.lastUpdateName;
currentConfig.peerWifiConfiguration = config.peerWifiConfiguration;
currentConfig.FQDN = config.FQDN;
currentConfig.providerFriendlyName = config.providerFriendlyName;
currentConfig.roamingConsortiumIds = config.roamingConsortiumIds;
currentConfig.validatedInternetAccess = config.validatedInternetAccess;
currentConfig.numNoInternetAccessReports = config.numNoInternetAccessReports;
currentConfig.updateTime = config.updateTime;
currentConfig.creationTime = config.creationTime;
}
if (DBG) {
log("created new config netId=" + Integer.toString(netId)
+ " uid=" + Integer.toString(currentConfig.creatorUid)
+ " name=" + currentConfig.creatorName);
}
}
......
readNetworkVariables(currentConfig);
......
mConfiguredNetworks.put(netId, currentConfig);
NetworkUpdateResult result = writeIpAndProxyConfigurationsOnChange(currentConfig, config);
result.setIsNewNetwork(newNetwork);
result.setNetworkId(netId);
......
return result;
}
......
}
现在不难发现承上启下的接口就是 WifiNative 类。可以清晰得看出把每个方法都包装成了一条命令执行了,以 addNetwork(…) 为例来分析一下。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiNative.java
public class WifiNative {
......
public int addNetwork() {
return doIntCommand("ADD_NETWORK");
}
public boolean setNetworkVariable(int netId, String name, String value) {
if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) return false;
if (name.equals(WifiConfiguration.pskVarName)
|| name.equals(WifiEnterpriseConfig.PASSWORD_KEY)) {
return doBooleanCommandWithoutLogging("SET_NETWORK " + netId + " " + name + " " + value);
} else {
return doBooleanCommand("SET_NETWORK " + netId + " " + name + " " + value);
}
}
public boolean enableNetwork(int netId, boolean disableOthers) {
if (DBG) logDbg("enableNetwork nid=" + Integer.toString(netId)
+ " disableOthers=" + disableOthers);
if (disableOthers) {
return doBooleanCommand("SELECT_NETWORK " + netId);
} else {
return doBooleanCommand("ENABLE_NETWORK " + netId);
}
}
......
public boolean selectNetwork(int netId) {
if (DBG) logDbg("selectNetwork nid=" + Integer.toString(netId));
return doBooleanCommand("SELECT_NETWORK " + netId);
}
......
public boolean saveConfig() {
return doBooleanCommand("SAVE_CONFIG");
}
......
}
doIntCommand(…) 方法内部调用了 doIntCommandNative(…) jni 方法转入 Native 层处理。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiNative.java
public class WifiNative {
......
private native int doIntCommandNative(String command);
......
private int doIntCommand(String command) {
if (DBG) Log.d(mTAG, "doInt: " + command);
synchronized (mLock) {
int cmdId = getNewCmdIdLocked();
String toLog = Integer.toString(cmdId) + ":" + mInterfacePrefix + command;
int result = doIntCommandNative(mInterfacePrefix + command);
localLog(toLog + " -> " + result);
if (DBG) Log.d(mTAG, " returned " + result);
return result;
}
}
......
}
android_net_wifi_doIntCommand(…) -> doIntCommand(…) -> doCommand(…) -> wifi_command(…)
frameworks/opt/net/wifi/service/jni/com_android_server_wifi_WifiNative.cpp
static jint android_net_wifi_doIntCommand(JNIEnv* env, jobject, jstring javaCommand) {
return doIntCommand(env, javaCommand);
}
......
static jint doIntCommand(JNIEnv* env, jstring javaCommand) {
char reply[REPLY_BUF_SIZE];
if (!doCommand(env, javaCommand, reply, sizeof(reply))) {
return -1;
}
return static_cast<jint>(atoi(reply));
}
......
static bool doCommand(JNIEnv* env, jstring javaCommand,
char* reply, size_t reply_len) {
ScopedUtfChars command(env, javaCommand);
if (command.c_str() == NULL) {
return false; // ScopedUtfChars already threw on error.
}
if (DBG) {
ALOGD("doCommand: %s", command.c_str());
}
--reply_len; // Ensure we have room to add NUL termination.
if (::wifi_command(command.c_str(), reply, &reply_len) != 0) {
return false;
}
// Strip off trailing newline.
if (reply_len > 0 && reply[reply_len-1] == '\n') {
reply[reply_len-1] = '\0';
} else {
reply[reply_len] = '\0';
}
return true;
}
wifi_command(…) 方法实现在 HAL 层,其只是将调用转发到 wifi_send_command(…) 方法。
wifi_command(…) 向 Wi-Fi 驱动程序发出命令。Android 扩展了链接 http://hostap.epitest.fi/wpa_supplicant/devel/ctrl_iface_page.html 中列出的标准命令,包括支持向驱动发送命令:
请参阅 wifi/java/android/net/wifi/WifiNative.java 了解支持的驱动程序命令的详细信息。
wifi_send_command(…) 关键工作是由 wpa_ctrl_request(…) 方法完成的。
hardware/libhardware_legacy/wifi/wifi.c
int wifi_command(const char *command, char *reply, size_t *reply_len)
{
return wifi_send_command(command, reply, reply_len);
}
......
int wifi_send_command(const char *cmd, char *reply, size_t *reply_len)
{
int ret;
if (ctrl_conn == NULL) {
ALOGV("Not connected to wpa_supplicant - \"%s\" command dropped.\n", cmd);
return -1;
}
ret = wpa_ctrl_request(ctrl_conn, cmd, strlen(cmd), reply, reply_len, NULL);
if (ret == -2) {
ALOGD("'%s' command timed out.\n", cmd);
/* unblocks the monitor receive socket for termination */
TEMP_FAILURE_RETRY(write(exit_sockets[0], "T", 1));
return -2;
} else if (ret < 0 || strncmp(reply, "FAIL", 4) == 0) {
return -1;
}
if (strncmp(cmd, "PING", 4) == 0) {
reply[*reply_len] = '\0';
}
return 0;
}
wpa_supplicant 实现了一个控制接口,外部程序可以使用该接口来控制 wpa_supplicant 守护程序的操作以及获取状态信息和事件通知。有一个小型 C 库,以单个 C 文件 wpa_ctrl.c 的形式提供,该库提供了帮助程序功能以方便使用控制界面。外部程序可以将此文件链接到其中,然后使用 wpa_ctrl.h 中记录的库函数与 wpa_supplicant 进行交互。该库也可以与 C++ 一起使用。 wpa_cli.c 和 wpa_gui 是使用此库的示例程序。
进程间通信有多种机制。例如,Linux 版本的 wpa_supplicant 使用 UNIX 域套接字作为控制接口,而 Windows 版本的 UDP 套接字。wpa_ctrl.h 中定义的功能的使用可用于从外部程序隐藏所用 IPC 的细节。
使用控制界面
需要与 wpa_supplicant 通信的外部程序(例如 GUI 或配置实用程序)应链接到 wpa_ctrl.c。这使他们可以使用辅助函数通过 wpa_ctrl_open() 打开与控制界面的连接,并通过 wpa_ctrl_request() 发送命令。
wpa_supplicant 使用控制接口进行两种类型的通信:命令和未经请求的事件消息。命令是一对消息,来自外部程序的请求和来自 wpa_supplicant 的响应。这些可以使用 wpa_ctrl_request() 执行。wpa_supplicant 将未经请求的事件消息发送到控制接口连接,而没有来自外部程序的特定请求来接收每个消息。但是,外部程序需要使用 wpa_ctrl_attach() 连接到控制接口,以接收这些未经请求的消息。
如果控制接口连接既用于命令又用于未经请求的事件消息,则有可能在命令请求和响应之间接收未经请求的消息。wpa_ctrl_request() 调用方将需要提供一个回调 msg_cb 来处理这些消息。通常,通过两次调用 wpa_ctrl_open() 来打开两个控制接口连接,然后将其中一个连接用于命令,将另一个连接用于未经请求的消息,会更容易。这样,命令 请求/响应对 不会被未经请求的消息破坏。 wpa_cli 是一个示例,该示例说明了如何同时使用两个连接和 wpa_gui 演示如何使用两个单独的连接。
一旦不再需要控制接口连接,则应通过调用 wpa_ctrl_close() 将其关闭。如果该连接用于未经请求的事件消息,则应首先通过调用 wpa_ctrl_detach() 断开连接。
命令最终由 socket send(…) 发送了出去。
external/wpa_supplicant_8/wpa_supplicant/src/common/wpa_ctrl.c
#ifdef CTRL_IFACE_SOCKET
int wpa_ctrl_request(struct wpa_ctrl *ctrl, const char *cmd, size_t cmd_len,
char *reply, size_t *reply_len,
void (*msg_cb)(char *msg, size_t len))
{
struct timeval tv;
struct os_reltime started_at;
int res;
fd_set rfds;
const char *_cmd;
char *cmd_buf = NULL;
size_t _cmd_len;
#ifdef CONFIG_CTRL_IFACE_UDP
if (ctrl->cookie) {
char *pos;
_cmd_len = os_strlen(ctrl->cookie) + 1 + cmd_len;
cmd_buf = os_malloc(_cmd_len);
if (cmd_buf == NULL)
return -1;
_cmd = cmd_buf;
pos = cmd_buf;
os_strlcpy(pos, ctrl->cookie, _cmd_len);
pos += os_strlen(ctrl->cookie);
*pos++ = ' ';
os_memcpy(pos, cmd, cmd_len);
} else
#endif /* CONFIG_CTRL_IFACE_UDP */
{
_cmd = cmd;
_cmd_len = cmd_len;
}
errno = 0;
started_at.sec = 0;
started_at.usec = 0;
retry_send:
if (send(ctrl->s, _cmd, _cmd_len, 0) < 0) {
if (errno == EAGAIN || errno == EBUSY || errno == EWOULDBLOCK)
{
/*
* Must be a non-blocking socket... Try for a bit
* longer before giving up.
*/
if (started_at.sec == 0)
os_get_reltime(&started_at);
else {
struct os_reltime n;
os_get_reltime(&n);
/* Try for a few seconds. */
if (os_reltime_expired(&n, &started_at, 5))
goto send_err;
}
os_sleep(1, 0);
goto retry_send;
}
send_err:
os_free(cmd_buf);
return -1;
}
os_free(cmd_buf);
for (;;) {
tv.tv_sec = 10;
tv.tv_usec = 0;
FD_ZERO(&rfds);
FD_SET(ctrl->s, &rfds);
res = select(ctrl->s + 1, &rfds, NULL, NULL, &tv);
if (res < 0)
return res;
if (FD_ISSET(ctrl->s, &rfds)) {
res = recv(ctrl->s, reply, *reply_len, 0);
if (res < 0)
return res;
if (res > 0 && reply[0] == '<') {
/* This is an unsolicited message from
* wpa_supplicant, not the reply to the
* request. Use msg_cb to report this to the
* caller. */
if (msg_cb) {
/* Make sure the message is nul
* terminated. */
if ((size_t) res == *reply_len)
res = (*reply_len) - 1;
reply[res] = '\0';
msg_cb(reply, res);
}
continue;
}
*reply_len = res;
break;
} else {
return -2;
}
}
return 0;
}
#endif /* CTRL_IFACE_SOCKET */