前面提到的新 Android N scan机制 ,现在再看一个新的东西,WifiConnectivityManager,之前android connect一个wifi和做scan的操作都是放在wifistatemachine中的,整个看起来很杂乱。现在google在android N中做了个新的东西,WifiConnectivityManager,通过这个东西来管理scan和connect。这里我从wifi connect一个ssid的流程来看这个WifiConnectivityManager.
这个API没有发生变动。
public void connect(WifiConfiguration config, ActionListener listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
// Use INVALID_NETWORK_ID for arg1 when passing a config object
// arg1 is used to pass network id when the network already exists
getChannel().sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
putListener(listener), config);
}
在WifiserviceImpl.java中会收到这个CMD,跟着做相应的处理
/*
WifiServiceImpl.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi)
*/
*/
private class ClientHandler extends Handler {
ClientHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
............
case WifiManager.CONNECT_NETWORK:
case WifiManager.SAVE_NETWORK: {
WifiConfiguration config = (WifiConfiguration) msg.obj;
int networkId = msg.arg1;
if (msg.what == WifiManager.SAVE_NETWORK) {
Slog.d("WiFiServiceImpl ", "SAVE"
+ " nid=" + Integer.toString(networkId)
+ " uid=" + msg.sendingUid
+ " name="
+ mContext.getPackageManager().getNameForUid(msg.sendingUid));
}
if (msg.what == WifiManager.CONNECT_NETWORK) {
Slog.d("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);
//直接给WifiStatemachine把这个CMD送过去
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;
}
.........................
看下wifistatemachinemachine,一般都是在ConnectModeState中会处理这个:
/*
WifiStateMachine.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi)
*/
class ConnectModeState extends State {
..........
case WifiManager.CONNECT_NETWORK:
.............................
/* Tell network selection the user did try to connect to that network if from
settings */
boolean persist =
mWifiConfigManager.checkConfigOverridePermission(message.sendingUid);
//设置mLastSelectedConfiguration变量,表示最近一次用户选择连接的热点 mWifiConfigManager.setAndEnableLastSelectedConfiguration(netId);
if (mWifiConnectivityManager != null) {
//通过WifiConnectivityManager去连接
mWifiConnectivityManager.connectToUserSelectNetwork(netId, persist);
}
//Start a new ConnectionEvent due to connect_network, this is always user
//selected
mWifiMetrics.startConnectionEvent(config, mTargetRoamBSSID,
WifiMetricsProto.ConnectionEvent.ROAM_USER_SELECTED);
if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ true,
message.sendingUid) && mWifiNative.reconnect()) {
lastConnectAttemptTimestamp = System.currentTimeMillis();
targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);
/* The state tracker handles enabling networks upon completion/failure */
mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
if (didDisconnect) {
/* Expect a disconnection from the old connection */
transitionTo(mDisconnectingState);
} else if (updatedExisting && getCurrentState() == mConnectedState &&
getCurrentWifiConfiguration().networkId == netId) {
// Update the current set of network capabilities, but stay in the
// current state.
updateCapabilities(config);
} else {
/**
* Directly go to disconnected state where we
* process the connection events from supplicant
*/
transitionTo(mDisconnectedState);
}
} else {
loge("Failed to connect config: " + config + " netId: " + netId);
replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.ERROR);
reportConnectionAttemptEnd(
WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED,
WifiMetricsProto.ConnectionEvent.HLF_NONE);
break;
}
连接一个用户指定的SSID
/*
WifiConnectivityManager.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi)
*/
/**
* Handler when user specifies a particular network to connect to
*/
public void connectToUserSelectNetwork(int netId, boolean persistent) {
Log.i(TAG, "connectToUserSelectNetwork: netId=" + netId
+ " persist=" + persistent);
//QualifiedNetworkSelector ssid质量选择器
① mQualifiedNetworkSelector.userSelectNetwork(netId, persistent);
clearConnectionAttemptTimeStamps();
}
其实这个class的设计是用于自动连接一个信号比较好的wifi ssid的。把用户连接一个热点的逻辑整合在这里,应该是为了避免在android 5.1经常出现的用户触发一个连接热点的操作,结果后台auto connect自己选择一个热点也去连接了,结果两个flow打架
/**
* This class looks at all the connectivity scan results then
* select an network for the phone to connect/roam to.
*/
看下①处的userSelectNetwork
/**
* This API is called when user explicitly select a network. Currently, it is used in following
* cases:
* (1) User explicitly choose to connect to a saved network
* (2) User save a network after add a new network
* (3) User save a network after modify a saved network
* Following actions will be triggered:
* 1. if this network is disabled, we need re-enable it again
* 2. we considered user prefer this network over all the networks visible in latest network
* selection procedure
*
* @param netId new network ID for either the network the user choose or add
* @param persist whether user has the authority to overwrite current connect choice
* @return true -- There is change made to connection choice of any saved network
* false -- There is no change made to connection choice of any saved network
*/
public boolean userSelectNetwork(int netId, boolean persist) {
WifiConfiguration selected = mWifiConfigManager.getWifiConfiguration(netId);
localLog("userSelectNetwork:" + netId + " persist:" + persist);
if (selected == null || selected.SSID == null) {
localLoge("userSelectNetwork: Bad configuration with nid=" + netId);
return false;
}
//看下用户选择的这个wifi是不是enabled
if (!selected.getNetworkSelectionStatus().isNetworkEnabled()) {
mWifiConfigManager.updateNetworkSelectionStatus(netId,
WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLE);
}
if (!persist) {
localLog("User has no privilege to overwrite the current priority");
return false;
}
boolean change = false;
String key = selected.configKey();
// This is only used for setting the connect choice timestamp for debugging purposes.
long currentTime = mClock.currentTimeMillis();
//遍历一下sacn result中所有的ssid,看下哪一个是用户selected的
List savedNetworks = mWifiConfigManager.getSavedNetworks();
for (WifiConfiguration network : savedNetworks) {
WifiConfiguration config = mWifiConfigManager.getWifiConfiguration(network.networkId);
WifiConfiguration.NetworkSelectionStatus status = config.getNetworkSelectionStatus();
if (config.networkId == selected.networkId) {
if (status.getConnectChoice() != null) {
//把旧的记录着用户selected的ssid remove掉
localLog("Remove user selection preference of " + status.getConnectChoice()
+ " Set Time: " + status.getConnectChoiceTimestamp() + " from "
+ config.SSID + " : " + config.networkId);
status.setConnectChoice(null);
status.setConnectChoiceTimestamp(WifiConfiguration.NetworkSelectionStatus
.INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
change = true;
}
continue;
}
//把当前的这个添加进去
if (status.getSeenInLastQualifiedNetworkSelection()
&& (status.getConnectChoice() == null
|| !status.getConnectChoice().equals(key))) {
localLog("Add key:" + key + " Set Time: " + currentTime + " to "
+ getNetworkString(config));
status.setConnectChoice(key);
status.setConnectChoiceTimestamp(currentTime);
change = true;
}
}
//Write this change to file
//写到wificonfig中
if (change) {
mWifiConfigManager.writeKnownNetworkHistory();
return true;
}
return false;
}
到这里的就结束了。。。。。这部分逻辑应该是为防止attempt auto join去跑的逻辑。
回到前面的WiFiConnectivityManager
中,跑完①之后,跟着会跑②
public void connectToUserSelectNetwork(int netId, boolean persistent) {
Log.i(TAG, "connectToUserSelectNetwork: netId=" + netId
+ " persist=" + persistent);
①mQualifiedNetworkSelector.userSelectNetwork(netId, persistent);
②clearConnectionAttemptTimeStamps();
}
②的代码很简单,调用了ConnectionAttemptTimeStamps,这个是管理wifi auto connect存在每次尝试去连接的时间戳,ConnectionAttemptTimeStamps是一个双向列表。
/**
* This is used to clear the connection attempt rate limiter. This is done when the user
* explicitly tries to connect to a specified network.
*/
private void clearConnectionAttemptTimeStamps() {
mConnectionAttemptTimeStamps.clear();
}
回到前面的WifiStatemachine中,跟着后面直接call mWifiNative.reconnect()去连接一个SSID。
if (mWifiConfigManager.selectNetwork(config, /* updatePriorities = */ true,
message.sendingUid) && mWifiNative.reconnect()) {
lastConnectAttemptTimestamp = System.currentTimeMillis();
targetWificonfiguration = mWifiConfigManager.getWifiConfiguration(netId);
/* The state tracker handles enabling networks upon completion/failure */
mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
if (didDisconnect) {
/* Expect a disconnection from the old connection */
transitionTo(mDisconnectingState);
}
android N对这个auto connect的部分做了大改,重新做了。这个Auto connect的机制其实是为了做wifi 热点的漫游用,即当找到信号等质量更好的热点的时候,就会切过去。但是在之前,特别是android L这个部分和用户connect的flow产生冲突,出现了很多的bug,很鸡肋。
前面
前面提到的新 Android N scan机制 提到了几种scan场景的应用,这个auto connect都会用到。这里我们就只分析一种case就好。
startPeriodicScan,这个是当屏幕是亮屏的时候,后台一直做scan的操作。
/*
WifiConnectivityManager.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi)
*/
// Start a periodic scan when screen is on
private void startPeriodicScan(boolean scanImmediately) {
mPnoScanListener.resetLowRssiNetworkRetryDelay();
// Due to b/28020168, timer based single scan will be scheduled
// to provide periodic scan in an exponential backoff fashion.
if (!ENABLE_BACKGROUND_SCAN) {
if (scanImmediately) {
resetLastPeriodicSingleScanTimeStamp();
}
mPeriodicSingleScanInterval = PERIODIC_SCAN_INTERVAL_MS;
startPeriodicSingleScan();
} else {
ScanSettings settings = new ScanSettings();
settings.band = getScanBand();
settings.reportEvents = WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT
| WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
settings.numBssidsPerScan = 0;
settings.periodInMs = PERIODIC_SCAN_INTERVAL_MS;
mPeriodicScanListener.clearScanDetails();
//调用了mScanner.startBackgroundScan去跑scan,worksource 是WIFI_WORK_SOURCE
mScanner.startBackgroundScan(settings, mPeriodicScanListener, WIFI_WORK_SOURCE);
}
}
看下这里传入的监听器:mPeriodicScanListener
/*
WifiConnectivityManager.java (frameworks\opt\net\wifi\service\java\com\android\server\wifi)
*/
// Periodic scan results listener. A periodic scan is initiated when
// screen is on.
private class PeriodicScanListener implements WifiScanner.ScanListener {
private List mScanDetails = new ArrayList();
public void clearScanDetails() {
mScanDetails.clear();
}
@Override
public void onSuccess() {
localLog("PeriodicScanListener onSuccess");
// reset the count
mScanRestartCount = 0;
}
@Override
public void onFailure(int reason, String description) {
Log.e(TAG, "PeriodicScanListener onFailure:"
+ " reason: " + reason
+ " description: " + description);
// reschedule the scan
if (mScanRestartCount++ < MAX_SCAN_RESTART_ALLOWED) {
scheduleDelayedConnectivityScan(RESTART_SCAN_DELAY_MS);
} else {
mScanRestartCount = 0;
Log.e(TAG, "Failed to successfully start periodic scan for "
+ MAX_SCAN_RESTART_ALLOWED + " times");
}
}
@Override
public void onPeriodChanged(int periodInMs) {
localLog("PeriodicScanListener onPeriodChanged: "
+ "actual scan period " + periodInMs + "ms");
}
@Override
public void onResults(WifiScanner.ScanData[] results) {
//当scan到了结果之后,跑handleScanResults去处理
handleScanResults(mScanDetails, "PeriodicScanListener");
clearScanDetails();
}
@Override
public void onFullResult(ScanResult fullScanResult) {
if (mDbg) {
localLog("PeriodicScanListener onFullResult: "
+ fullScanResult.SSID + " capabilities "
+ fullScanResult.capabilities);
}
mScanDetails.add(ScanDetailUtil.toScanDetail(fullScanResult));
}
}
看下这个函数都做了些什么?
/**
* Handles 'onResult' callbacks for the Periodic, Single & Pno ScanListener.
* Executes selection of potential network candidates, initiation of connection attempt to that
* network.
*
* @return true - if a candidate is selected by QNS
* false - if no candidate is selected by QNS
*/
private boolean handleScanResults(List scanDetails, String listenerName) {
localLog(listenerName + " onResults: start QNS");
//调用QualifiedNetworkSelector去筛选目标ssid,这个算法有点复杂。我看下参数就好
①WifiConfiguration candidate =
mQualifiedNetworkSelector.selectQualifiedNetwork(false,
mUntrustedConnectionAllowed, scanDetails,
mStateMachine.isLinkDebouncing(), mStateMachine.isConnected(),
mStateMachine.isDisconnected(),
mStateMachine.isSupplicantTransientState());
mWifiLastResortWatchdog.updateAvailableNetworks(
mQualifiedNetworkSelector.getFilteredScanDetails());
if (candidate != null) {
localLog(listenerName + ": QNS candidate-" + candidate.SSID);
②connectToNetwork(candidate);
return true;
} else {
return false;
}
}
① selectQualifiedNetwork
/**
* ToDo: This should be called in Connectivity Manager when it gets new scan result
* check whether a network slection is needed. If need, check all the new scan results and
* select a new qualified network/BSSID to connect to
*
* @param forceSelectNetwork true -- start a qualified network selection anyway,no matter
* current network is already qualified or not.
* false -- if current network is already qualified, do not do new
* selection
* @param isUntrustedConnectionsAllowed true -- user allow to connect to untrusted network
* false -- user do not allow to connect to untrusted
* network
* @param scanDetails latest scan result obtained (should be connectivity scan only)
* @param isLinkDebouncing true -- Link layer is under debouncing
* false -- Link layer is not under debouncing
* @param isConnected true -- device is connected to an AP currently
* false -- device is not connected to an AP currently
* @param isDisconnected true -- WifiStateMachine is at disconnected state
* false -- WifiStateMachine is not at disconnected state
* @param isSupplicantTransient true -- supplicant is in a transient state
* false -- supplicant is not in a transient state
* @return the qualified network candidate found. If no available candidate, return null
*/
public WifiConfiguration selectQualifiedNetwork(boolean forceSelectNetwork ,
boolean isUntrustedConnectionsAllowed, List scanDetails,
boolean isLinkDebouncing, boolean isConnected, boolean isDisconnected,
boolean isSupplicantTransient) {
② connectToNetwork(candidate);
对我们通过算法算出来的目标ssid发起连接,candidate是一个WifiConfiguration对象。
/**
* Attempt to connect to a network candidate.
*
* Based on the currently connected network, this menthod determines whether we should
* connect or roam to the network candidate recommended by QNS.
*/
private void connectToNetwork(WifiConfiguration candidate) {
ScanResult scanResultCandidate = candidate.getNetworkSelectionStatus().getCandidate();
if (scanResultCandidate == null) {
Log.e(TAG, "connectToNetwork: bad candidate - " + candidate
+ " scanResult: " + scanResultCandidate);
return;
}
String targetBssid = scanResultCandidate.BSSID;
String targetAssociationId = candidate.SSID + " : " + targetBssid;
//如果我们筛选出来的ssid正好是上一次attempt连接的ssid,或者是supplicant现在正在连接的目标ssid,则放弃
// Check if we are already connected or in the process of connecting to the target
// BSSID. mWifiInfo.mBSSID tracks the currently connected BSSID. This is checked just
// in case the firmware automatically roamed to a BSSID different from what QNS
// selected.
if (targetBssid != null
&& (targetBssid.equals(mLastConnectionAttemptBssid)
|| targetBssid.equals(mWifiInfo.getBSSID()))
&& SupplicantState.isConnecting(mWifiInfo.getSupplicantState())) {
localLog("connectToNetwork: Either already connected "
+ "or is connecting to " + targetAssociationId);
return;
}
Long elapsedTimeMillis = mClock.elapsedRealtime();
/**
* This checks the connection attempt rate and recommends whether the connection attempt
* should be skipped or not. This attempts to rate limit the rate of connections to
* prevent us from flapping between networks and draining battery rapidly.
*/
//控制attempt连接速率,如果间隔时间太快,那就放弃
if (!mScreenOn && shouldSkipConnectionAttempt(elapsedTimeMillis)) {
localLog("connectToNetwork: Too many connection attempts. Skipping this attempt!");
mTotalConnectivityAttemptsRateLimited++;
return;
}
//终于要开始连接了,把时间记录下来noteConnectionAttempt
noteConnectionAttempt(elapsedTimeMillis);
mLastConnectionAttemptBssid = targetBssid;
WifiConfiguration currentConnectedNetwork = mConfigManager
.getWifiConfiguration(mWifiInfo.getNetworkId());
String currentAssociationId = (currentConnectedNetwork == null) ? "Disconnected" :
(mWifiInfo.getSSID() + " : " + mWifiInfo.getBSSID());
//做漫游操作还是autoconnect的操作
if (currentConnectedNetwork != null
&& (currentConnectedNetwork.networkId == candidate.networkId
|| currentConnectedNetwork.isLinked(candidate))) {
localLog("connectToNetwork: Roaming from " + currentAssociationId + " to "
+ targetAssociationId);
mStateMachine.autoRoamToNetwork(candidate.networkId, scanResultCandidate);
} else {
localLog("connectToNetwork: Reconnect from " + currentAssociationId + " to "
+ targetAssociationId);
mStateMachine.autoConnectToNetwork(candidate.networkId, scanResultCandidate.BSSID);
}
}
接下来就交给Wifistatemachine去处理了。
/**
* Automatically connect to the network specified
*
* @param networkId ID of the network to connect to
* @param bssid BSSID of the network
*/
public void autoConnectToNetwork(int networkId, String bssid) {
sendMessage(CMD_AUTO_CONNECT, networkId, 0, bssid);
}
/**
* Automatically roam to the network specified
*
* @param networkId ID of the network to roam to
* @param scanResult scan result which identifies the network to roam to
*/
public void autoRoamToNetwork(int networkId, ScanResult scanResult) {
sendMessage(CMD_AUTO_ROAM, networkId, 0, scanResult);
}