Android Wifi work station Framework and Architecture
with wpa_supplicant 0.8.X, BCM4329.
转载请注明出处。
Settings/Wifi UI part structure
WifiSettings是主对话框
167
168 @Override
169 public 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
174 mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
175 mWifiManager.asyncConnect(getActivity(), new WifiServiceHandler());
176 if (savedInstanceState != null
177 && savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
178 mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
179 mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
180 }
1101 public void asyncConnect(Context srcContext, Handler srcHandler) {
1102 mAsyncChannel.connect(srcContext, srcHandler, getMessenger());
1103 }
establish a half connect between WifiManager and WifiService.
1117 public void connectNetwork(WifiConfiguration config) {
1118 if (config == null) {
1119 return;
1120 }
1121 mAsyncChannel.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 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 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) {
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
388 public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
389 if (preference instanceof AccessPoint) {
390 mSelectedAccessPoint = (AccessPoint) preference;
391
392 if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
393 mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
394 mSelectedAccessPoint.generateOpenNetworkConfig();
395 mWifiManager.connectNetwork(mSelectedAccessPoint.getConfig());
396 } else {
397 showConfigUi(mSelectedAccessPoint, false);
398 }
399 } else {
400 return super.onPreferenceTreeClick(screen, preference);
401 }
402 return true;
403 }
可以看出和长按菜单的处理逻辑相似。
当使用Setttings主界面或Settings/Wifi界面的Switcher开关Wifi时,处理代码会调用到mWifiEnabler的setSwitch方法,然后会通知其listener,涉及方法如下:
88 public void setSwitch(Switch switch_) {
89 if (mSwitch == switch_) return;
90 mSwitch.setOnCheckedChangeListener(null);
91 mSwitch = switch_;
92 mSwitch.setOnCheckedChangeListener(this);
93
94 final int wifiState = mWifiManager.getWifiState();
95 boolean isEnabled = wifiState == WifiManager.WIFI_STATE_ENABLED;
96 boolean isDisabled = wifiState == WifiManager.WIFI_STATE_DISABLED;
97 mSwitch.setChecked(isEnabled);
98 mSwitch.setEnabled(isEnabled || isDisabled);
99 }
100
101 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
102 //Do nothing if called as a result of a state machine event
103 if (mStateMachineEvent) {
104 return;
105 }
106 // Show toast message if Wi-Fi is not allowed in airplane mode
107 if (isChecked && !WirelessSettings.isRadioAllowed(mContext, Settings.System.RADIO_WIFI)) {
108 Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();
109 // Reset switch to off. No infinite check/listenenr loop.
110 buttonView.setChecked(false);
111 }
112
113 // Disable tethering if enabling Wifi
114 int wifiApState = mWifiManager.getWifiApState();
115 if (isChecked && ((wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) ||
116 (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED))) {
117 mWifiManager.setWifiApEnabled(null, false);
118 }
119
120 if (mWifiManager.setWifiEnabled(isChecked)) {
121 // Intent has been taken into account, disable until new state is active
122 mSwitch.setEnabled(false);
123 } else {
124 // Error
125 Toast.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
224
225
226
227
228
229
230
231 Scanning\u2026
232
233 Connecting\u2026
234
235 Authenticating\u2026
236
237 Obtaining IP address\u2026
238
239 Connected
240
241 Suspended
242
243 Disconnecting\u2026
244
245 Disconnected
246
247 Unsuccessful
248
至此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
485 public WifiManager(IWifiManager service, Handler handler) {
486 mService = service;
487 mHandler = handler;
488 }
In ContextImpl.java每个Activity关联的Contect会得到WIFI_SERVICE,然后构造WifiManager封装类。代码如下:
449 registerService(WIFI_SERVICE, new ServiceFetcher() {
450 public Object createService(ContextImpl ctx) {
451 IBinder b = ServiceManager.getService(WIFI_SERVICE);
452 IWifiManager service = IWifiManager.Stub.asInterface(b);
453 returnnew 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
384 try {
385 Slog.i(TAG, "Wi-Fi P2pService");
386 wifiP2p = new WifiP2pService(context);
387 ServiceManager.addService(Context.WIFI_P2P_SERVICE, wifiP2p);
388 } catch (Throwable e) {
389 reportWtf("starting Wi-Fi P2pService", e);
390 }
391
392 try {
393 Slog.i(TAG, "Wi-Fi Service");
394 wifi = new WifiService(context);
395 ServiceManager.addService(Context.WIFI_SERVICE, wifi);
396 } catch (Throwable e) {
397 reportWtf("starting Wi-Fi Service", e);
398 }
WifiService的构造函数本质是启动了一个带有消息队列的线程做WifiService,两个Handler attach到该消息队列上。
428 HandlerThread wifiThread = new HandlerThread("WifiService");
429 wifiThread.start();
430 mAsyncServiceHandler = new AsyncServiceHandler(wifiThread.getLooper());
431 mWifiStateMachineHandler = 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会被执行到。相关代码如下:
553 public WifiStateMachine(Context context, String wlanInterface) {
649 if (DBG) setDbg(true);
650
651 //start the state machine
652 start();
653 }
680 public void setWifiEnabled(boolean enable) {
681 mLastEnableUid.set(Binder.getCallingUid());
682 if (enable) {
683
684 sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
685 sendMessage(CMD_START_SUPPLICANT);
686 } else {
687 sendMessage(CMD_STOP_SUPPLICANT);
688
689 sendMessage(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。