Android Wifi work station Framework and Architecture
with wpa_supplicant 0.8.X, BCM4329.
转载请注明出处。
Settings/Wifi UI part structure
WifiSettings是主对话框
167
168@Override
169public void onActivityCreated(Bundle savedInstanceState) {
170// We don't call super.onActivityCreated() here, since it assumes we already set up
171// Preference (probably in onCreate()), while WifiSettings exceptionally set it up in
172// this method.
173
174mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
175mWifiManager.asyncConnect(getActivity(), new WifiServiceHandler());
176if (savedInstanceState != null
177&& savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
178mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
179mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
180}
1101public void asyncConnect(Context srcContext, Handler srcHandler) {
1102mAsyncChannel.connect(srcContext, srcHandler, getMessenger());
1103}
establish a half connect between WifiManager and WifiService.
1117public void connectNetwork(WifiConfiguration config) {
1118if (config == null) {
1119return;
1120}
1121mAsyncChannel.sendMessage(CMD_CONNECT_NETWORK, config);
1122}
Activity创建的时候会建立和WifiService的AsyncChannel的连接,WifiService同样会建立一个AsyncChannelHandler来处理来自这个Channel的命令消息。AsyncChannel的所谓异步主要用来传递比较耗时的操作并把结果返回给原请求者。AsyncChannel两端分别有一个MessageHandler,srcHandler请求destHandler,destHandler把结果发回给srcHandler,主要是用于处理比较耗时的操作且在WifiManager中处理返回结果。
严格来说,在WifiService的Interface方法之外再做出一个通道提供额外的方法并不是一个什么好的设计。命令是可以通过同步接口发送给WifiService的,但是WifiService的处理结果如果通过broadcast或intent给WifiManager,则又解耦的过度;如果WifiManager实现一个binder做event sink,又有点小题大做,所以这儿引入这么个AsyncChannel实在是不得以而为之。
WifiSettings界面使能Wifi时会使用定时器请求扫描,获取扫描结果,列出AP列表。Scanner使用定时器,周期性向WifiService请求主动扫描,定时原理是发出本次扫描请求后延迟1s发送下次请求。相关代码如下:
private class Scanner extends Handler {
private int mRetry = 0;
void resume() {
if (!hasMessages(0)) {
sendEmptyMessage(0);
}
}
void forceScan() {
removeMessages(0);
sendEmptyMessage(0);
}
void pause() {
mRetry = 0;
removeMessages(0);
}
@Override
public void handleMessage(Message message) {
if (mWifiManager.startScanActive()) {
mRetry = 0;
} else if (++mRetry >= 3) {
mRetry = 0;
Toast.makeText(getActivity(), R.string.wifi_fail_to_scan,
Toast.LENGTH_LONG).show();
return;
}
sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
}
}
列出AP列表的代码如下:
In WifiSettings.java
final List<ScanResult> results = mWifiManager.getScanResults(); //同步操作
if (results != null) {
for (ScanResult result : results) {
// Ignore hidden and ad-hoc networks.
if (result.SSID == null || result.SSID.length() == 0 || result.capabilities.contains("[IBSS]")) {
continue;
}
boolean found = false;
for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
if (accessPoint.update(result))
found = true;
}
if (!found) {
AccessPoint accessPoint = new AccessPoint(getActivity(), result);
accessPoints.add(accessPoint);
apMap.put(accessPoint.ssid, accessPoint);
}
}
}
private void updateAccessPoints() {
final int wifiState = mWifiManager.getWifiState();
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLED:
// AccessPoints are automatically sorted with TreeSet.
final Collection<AccessPoint> accessPoints = constructAccessPoints();
getPreferenceScreen().removeAll();
if (mInXlSetupWizard) {
((WifiSettingsForSetupWizardXL)getActivity()).onAccessPointsUpdated(
getPreferenceScreen(), accessPoints);
} else {
for (AccessPoint accessPoint : accessPoints) {
// When WAPI is not customized to be on all
// WAPI APs will be invisible
if (accessPoint.isVisible()) {
getPreferenceScreen().addPreference(accessPoint);
}
}
}
break;
case WifiManager.WIFI_STATE_ENABLING:
getPreferenceScreen().removeAll();
break;
case WifiManager.WIFI_STATE_DISABLING:
addMessagePreference(R.string.wifi_stopping);
break;
case WifiManager.WIFI_STATE_DISABLED:
addMessagePreference(R.string.wifi_empty_list_wifi_off);
break;
}
}
可以看出对ADHOC的AP,隐藏AP(无SSID的)做了过滤处理。
当在AP列表上,长按某个AP时弹出三菜单Menu - ContextMenu。
这个Menu主要是连接该AP,修改该AP,忘记该AP,其处理代码如下:
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mSelectedAccessPoint == null) {
return super.onContextItemSelected(item);
}
switch (item.getItemId()) {
case MENU_ID_CONNECT: {
if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
if (!requireKeyStore(mSelectedAccessPoint.getConfig())) {
mWifiManager.connectNetwork(mSelectedAccessPoint.networkId);
}
} else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
/** Bypass dialog for unsecured networks */
mSelectedAccessPoint.generateOpenNetworkConfig();
mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig());
} else {
showConfigUi(mSelectedAccessPoint, true);
}
return true;
}
case MENU_ID_FORGET: {
mWifiManager.forgetNetwork(mSelectedAccessPoint.networkId);
return true;
}
case MENU_ID_MODIFY: {
showConfigUi(mSelectedAccessPoint, true);
return true;
}
}
return super.onContextItemSelected(item);
}
可以看出如果该AP已经经过配置,那么直接连接,如果没有经过配置且该AP没有密码,那么直接连接,否则则弹出配置对话框先进行配置(选择加密类型和输入密码)。
当点击AP列表中的某个AP时,直接根据该AP的情况进行处理,代码如下:
387@Override
388public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
389if (preference instanceof AccessPoint) {
390mSelectedAccessPoint = (AccessPoint) preference;
391/** Bypass dialog for unsecured, unsaved networks */
392if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
393mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
394mSelectedAccessPoint.generateOpenNetworkConfig();
395mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig());
396} else {
397showConfigUi(mSelectedAccessPoint, false);
398}
399} else {
400return super.onPreferenceTreeClick(screen, preference);
401}
402return true;
403}
可以看出和长按菜单的处理逻辑相似。
当使用Setttings主界面或Settings/Wifi界面的Switcher开关Wifi时,处理代码会调用到mWifiEnabler的setSwitch方法,然后会通知其listener,涉及方法如下:
88public void setSwitch(Switch switch_) {
89if (mSwitch == switch_) return;
90mSwitch.setOnCheckedChangeListener(null);
91mSwitch = switch_;
92mSwitch.setOnCheckedChangeListener(this);
93
94final int wifiState = mWifiManager.getWifiState();
95boolean isEnabled = wifiState == WifiManager.WIFI_STATE_ENABLED;
96boolean isDisabled = wifiState == WifiManager.WIFI_STATE_DISABLED;
97mSwitch.setChecked(isEnabled);
98mSwitch.setEnabled(isEnabled || isDisabled);
99}
100
101public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
102//Do nothing if called as a result of a state machine event
103if (mStateMachineEvent) {
104return;
105}
106// Show toast message if Wi-Fi is not allowed in airplane mode
107if (isChecked && !WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_WIFI)) {
108Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
109// Reset switch to off. No infinite check/listenenr loop.
110buttonView.setChecked(false);
111}
112
113// Disable tethering if enabling Wifi
114int wifiApState = mWifiManager.getWifiApState();
115if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
116(wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
117mWifiManager.setWifiApEnabled(null, false);
118}
119
120if (mWifiManager.setWifiEnabled(isChecked)) {
121// Intent has been taken into account, disable until new state is active
122mSwitch.setEnabled(false);
123} else {
124// Error
125Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();
126}
127}
可以看出当不是AP时,是使能Wifi,默认行为是根据配置挑选出AP进行连接。
另外的两个类的解释:
WifiConfigController是MVC中的Controller
WifiDialog是单击的config对话框
连接和DHCP过程中显示的字符串xml文件如下:
In /packages/apps/Settings/res/values/arrays.xml
223<!-- Wi-Fi settings -->
224
225<!-- Match this with the order of NetworkInfo.DetailedState. --> <skip />
226<!-- Wi-Fi settings. The status messages when the network is unknown. -->
227<string-array name="wifi_status">
228<!-- Status message of Wi-Fi when it is idle. -->
229<item></item>
230<!-- Status message of Wi-Fi when it is scanning. -->
231<item>Scanning\u2026</item>
232<!-- Status message of Wi-Fi when it is connecting. -->
233<item>Connecting\u2026</item>
234<!-- Status message of Wi-Fi when it is authenticating. -->
235<item>Authenticating\u2026</item>
236<!-- Status message of Wi-Fi when it is obtaining IP address. -->
237<item>Obtaining IP address\u2026</item>
238<!-- Status message of Wi-Fi when it is connected. -->
239<item>Connected</item>
240<!-- Status message of Wi-Fi when it is suspended. -->
241<item>Suspended</item>
242<!-- Status message of Wi-Fi when it is disconnecting. -->
243<item>Disconnecting\u2026</item>
244<!-- Status message of Wi-Fi when it is disconnected. -->
245<item>Disconnected</item>
246<!-- Status message of Wi-Fi when it is a failure. -->
247<item>Unsuccessful</item>
248</string-array>
至此UI界面响应部分介绍完毕,下面看WifiManager.
WifiManager是WifiService的客户端接口的封装类,WifiService是服务实现,接口是IWifiManager。
In IWifiManager.aidl
32interface IWifiManager {};
In WifiService.java
public class WifiService extends IWifiManager.Stub {};
In WifiManager.java, NOT the intermmediate file of IWifiManager.aidl
485public WifiManager(IWifiManager service, Handler handler) {
486mService = service;
487mHandler = handler;
488}
In ContextImpl.java每个Activity关联的Contect会得到WIFI_SERVICE,然后构造WifiManager封装类。代码如下:
449registerService(WIFI_SERVICE, new ServiceFetcher() {
450public Object createService(ContextImpl ctx) {
451IBinder b = ServiceManager.getService(WIFI_SERVICE);
452IWifiManager service = IWifiManager.Stub.asInterface(b);
453returnnew WifiManager(service, ctx.mMainThread.getHandler());
454}});
几个同步操作的控制流示例:
WifiSettings=>WifiManager::reconnect(…)()||||
=>WifiService:: reconnect ()=>WifiStateMachine :: reconnectCommand()
Scanner=>WifiManager::startScanActive()||||=>WifiService::startScan(active)=>WifiStateMachine ::startScan(active)
WifiEnabler=> WifiManager::setWifiEnabled() ||||=>WifiService::setWifiEnabled()=>WifiStateMachine ::setWifiEnabled()
WifiManager中大部分同步命令都是这么个流程;耗时的异步命令则是通过AsyncChannel发送给WifiService.
WifiService Part structure
In WifiService.java
public class WifiService extends IWifiManager.Stub {};
WifiService线程的启动如下
In SystemServer.java
384try {
385Slog.i(TAG, "Wi-Fi P2pService");
386wifiP2p = new WifiP2pService(context);
387ServiceManager.addService(Context.WIFI_P2P_SERVICE, wifiP2p);
388} catch (Throwable e) {
389reportWtf("starting Wi-Fi P2pService", e);
390}
391
392try {
393Slog.i(TAG, "Wi-Fi Service");
394wifi = new WifiService(context);
395ServiceManager.addService(Context.WIFI_SERVICE, wifi);
396} catch (Throwable e) {
397reportWtf("starting Wi-Fi Service", e);
398}
WifiService的构造函数本质是启动了一个带有消息队列的线程做WifiService,两个Handler attach到该消息队列上。
428HandlerThread wifiThread = new HandlerThread("WifiService");
429wifiThread.start();
430mAsyncServiceHandler = new AsyncServiceHandler(wifiThread.getLooper());
431mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper());
AsyncServiceHandler做为destHandler用于处理下行的来自WifiManager客户端的命令消息,而WifiStateMachineHandler做为srcHandler用于向WifiStateMachine.mSmhandler发送异步命令。现在的WifiStatemachine实现是和WifiService使用同一个looper,在同一个线程中,所以mAsyncServiceHandler、mWifiStateMachineHandler、WifiStateMachine.mSmhandler是使用同一个looper,运行在wifiThread中。WifiStateMachine籍由StateMachine具备有单独looper在单独线程运行的能力。
WifiSerivce作为一个service,WifiService.java在frameworks/base/services/java/com/android/server/目录下。但其实现使用的WifiStateMachine在frameworks/base/wifi/java/android/net/wifi/目录下。
WifiStateMachine状态机使用WifiNative wpa_ctrl和wpa_supplicant通讯, WifiMonitor监听wpa_supplicant的异步消息。因为WifiStateMachine的核心是状态机,所以其接口都是转换成命令投入状态机消息队列。WifiService传来的命令和WifiMonitor监听到的响应汇入WifiStateMachine的消息队列,驱动WifiStateMachine转动起来。
WifiStateMachine继承自StateMachine,是一个hierarchical state machine which processes messages and can have states arranged hierarchically,其细节参见StateMachine.java. WifiStateMachine的hierarchy如下图所示:
下面考察初始状态到Wifi使能扫描AP连接AP查询AP过程的状态机运转。
In WifiStateMachine.java
状态机会启动,进入InitialState状态。然后UI打开WifiEnabler最终WifiService:: setWifiEnabled会被执行到。相关代码如下:
553public WifiStateMachine(Context context, String wlanInterface) {
649if (DBG) setDbg(true);
650
651//start the state machine
652start();
653}
680public void setWifiEnabled(boolean enable) {
681mLastEnableUid.set(Binder.getCallingUid());
682if (enable) {
683/* Argument is the state that is entered prior to load */
684sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
685sendMessage(CMD_START_SUPPLICANT);
686} else {
687sendMessage(CMD_STOP_SUPPLICANT);
688/* Argument is the state that is entered upon success */
689sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
690}
691}
在InitialState.enter()中,初始情况下WifiDriver是没有加载的,所以进入DriverUnloadedState状态;
然后setWifiEnabled向状态机发出CMD_LOAD_DRIVER with WIFI_STATE_ENABLING和CMD_START_SUPPLICANT两个命令。
在DriverUnloadedState状态下,处理CMD_LOAD_DRIVER命令,向WifiP2pService发送WIFI_ENABLE_PENDING命令,也就是说通知WifiP2pService需要退出要启用Wifi了,因为WifiService和WifiP2pService同一时刻只能使用一个,然后转到WaitForP2pDisableState状态等待WifiP2pService的响应。
在WaitForP2pDisableState状态下收到WifiP2pService的响应WIFI_ENABLE_PROCEED,转到DriverLoadingState状态加载Wifi driver;对于其它请求会deferMessage到以后状态处理。
在DriverLoadingState状态启用线程异步加载Wifi driver,当加载成功时,向状态机自身发送消息CMD_LOAD_DRIVER_SUCCESS驱动状态机转换到DriverLoadedState,加载失败则发送消息CMD_LOAD_DRIVER_FAILED驱动状态机转换到DriverFailedState状态(会经由DriverUnloadedState)。
在DriverLoadedState状态下处理setWifiEnabled发出的defer到此的第二个命令CMD_START_SUPPLICANT,加载网卡芯片固件,启动wpa_supplicant,启动WifiMonitor接收wpa_supplicant的消息,转换状态到SupplicantStartingState等待WifiMonitor的事件(wpa_supplicant接入成功事件)。
在SupplicantStartingState状态下收到WifiMonitor. SUP_CONNECTION_EVENT表接入wpa_supplicant成功,regulate国家代号,初始化一些表征wpa_s的字段,转换状态至DriverStartedState;对于其它命令,defer处理。
经SupplicantStartedState.enter()和DriverStartedState.enter()中使用CMD_SET_COUNTRY_CODE和CMD_SET_FREQUENCY_BAND设置国家代号和频段,在本状态处理这两个命令;这两个命令是要发送给芯片的,所以在固件和驱动加载好后发送。设置好频段后,就向自身发送消息CMD_START_SCAN启动一次active scan。然后经由状态ConnectModeState转换状态至DisconnectedState。
在DisconnectedState下收到扫描结果WifiMonitor.SCAN_RESULTS_EVENT,消息路由给父状态SupplicantStartedState的processMessage进行处理,获取扫描结果并保存。在此状态下SupplicantStartedState处理与AP配置相关的命令,ConnectModeState处理与AP连接相关的命令。例如可以配置某个AP,然后请求connectNetwork,对于CMD_CONNECT_NETWORK,会使用Connect相关状态ConnectModeState.processMessage()处理,启动AP关联,然后转换状态到DisconnectingState与旧AP断开连接与新AP建立连接。
在DisconnectingState状态收到关联到新AP成功消息WifiMonitor.NETWORK_CONNECTION_EVENT后,使用ConnectModeState.processMessage处理,记录关联上的SSID,BSSID,获取AP信号强度和速度,广播消息,转换状态至ConnectingState进行IP获龋
在ConnectingState.enter()中使用静态IP或DHCP获取IP,配置IP,成功则转入ConnectedState状态。
在ConnectedState状态下,可以处理配置AP、连接新的AP、断开连接等,还有扫描请求,另外还要查询当前连接AP的信号强度和网络流量信息,在状态栏显示Wifi状态图标。这时通过CMD_RSSI_POLL命令实现的,当WifiService构造时,允许enableRssiPolling,一次CMD_ENABLE_RSSI_POLL会引发定时的CMD_RSSI_POLL,当屏幕未关闭时,CMD_RSSI_POLL就会定时向自身状态机发送;然后ConnectedState.processMessage调用fetchRssiAndLinkSpeedNative()处理CMD_RSSI_POLL。