(九十九)Android O wpsプロセスの簡単な整理
1.wps概要
wifiのwps接続は主に2種類に分けられ、1つはボタンで、1つはpinコードです.例えば、試験機がwps接続を使用する場合、ボタンやpinコード方式を選択し、サービス側は例えばルータや補助機が制限時間内にボタンを押したり、試験機が提供したpinコードを入力したりして接続することができ、ssidを選択してパスワードを入力する必要がない接続方式に相当し、機密性も良いが、結局パスワードに負けないようにしなければならない.
注意:この文書では、WifiStateMachineの次のプロセスを無視します.
2.プロセス整理
2.1 WpsDialog
PS:
1)
wpsボタンはWpsInfoに対応する.PBC,wps pinはWpsInfoに対応する.DISPLAY
WpsPreferenceController.java
2.2 WifiManager
2.3 WifiServiceImpl
2.4 WifiStateMachine
3.まとめ
wps接続と通常のパスワードの流れを比較すると、実は流れが違うのは検査上で、検査が完了した後にWifiStateMachineに戻るかdhcpに戻るかのセットで接続したのですが、接続の流れがsupplicantに着くのはやはり見ているのが苦痛で、続きます.
wifiのwps接続は主に2種類に分けられ、1つはボタンで、1つはpinコードです.例えば、試験機がwps接続を使用する場合、ボタンやpinコード方式を選択し、サービス側は例えばルータや補助機が制限時間内にボタンを押したり、試験機が提供したpinコードを入力したりして接続することができ、ssidを選択してパスワードを入力する必要がない接続方式に相当し、機密性も良いが、結局パスワードに負けないようにしなければならない.
注意:この文書では、WifiStateMachineの次のプロセスを無視します.
2.プロセス整理
2.1 WpsDialog
private enum DialogState {
WPS_INIT,
WPS_START,
WPS_COMPLETE,
CONNECTED, //WPS + IP config is done
WPS_FAILED
}
DialogState mDialogState = DialogState.WPS_INIT;
まずこのダイアログボックスには上記のような状態が5種類あり、初期状態はWPS_INIT
public WpsDialog(Context context, int wpsSetup) {
super(context);
mContext = context;
mWpsSetup = wpsSetup;
class WpsListener extends WifiManager.WpsCallback {
public void onStarted(String pin) {
if (pin != null) {
updateDialog(DialogState.WPS_START, String.format(
mContext.getString(R.string.wifi_wps_onstart_pin), pin));
} else {
updateDialog(DialogState.WPS_START, mContext.getString(
R.string.wifi_wps_onstart_pbc));
}
}
public void onSucceeded() {
updateDialog(DialogState.WPS_COMPLETE,
mContext.getString(R.string.wifi_wps_complete));
}
public void onFailed(int reason) {
String msg;
switch (reason) {
case WifiManager.WPS_OVERLAP_ERROR:
msg = mContext.getString(R.string.wifi_wps_failed_overlap);
break;
case WifiManager.WPS_WEP_PROHIBITED:
msg = mContext.getString(R.string.wifi_wps_failed_wep);
break;
case WifiManager.WPS_TKIP_ONLY_PROHIBITED:
msg = mContext.getString(R.string.wifi_wps_failed_tkip);
break;
case WifiManager.IN_PROGRESS:
msg = mContext.getString(R.string.wifi_wps_in_progress);
break;
default:
msg = mContext.getString(R.string.wifi_wps_failed_generic);
break;
}
updateDialog(DialogState.WPS_FAILED, msg);
}
}
mWpsListener = new WpsListener();
mFilter = new IntentFilter();
mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
handleEvent(context, intent);
}
};
setCanceledOnTouchOutside(false);
}
ここではいくつかの初期化を行い、主にmWpsListenerを初期化し、ブロードキャスト受信機を登録し、メッセージ処理は以下の通りである. private void handleEvent(Context context, Intent intent) {
String action = intent.getAction();
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
final int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN);
if (state == WifiManager.WIFI_STATE_DISABLED) {
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
String msg = mContext.getString(R.string.wifi_wps_failed_wifi_disconnected);
updateDialog(DialogState.WPS_FAILED, msg);
}
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
WifiManager.EXTRA_NETWORK_INFO);
final NetworkInfo.DetailedState state = info.getDetailedState();
if (state == DetailedState.CONNECTED &&
mDialogState == DialogState.WPS_COMPLETE) {
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
if (wifiInfo != null) {
String msg = String.format(mContext.getString(
R.string.wifi_wps_connected), wifiInfo.getSSID());
updateDialog(DialogState.CONNECTED, msg);
}
}
}
}
最も重要なのはやはりwpsの流れで、こちらのonCreateの方法では直接startWpsの流れを始めます. @Override
protected void onCreate(Bundle savedInstanceState) {
mView = getLayoutInflater().inflate(R.layout.wifi_wps_dialog, null);
mTextView = (TextView) mView.findViewById(R.id.wps_dialog_txt);
mTextView.setText(R.string.wifi_wps_setup_msg);
mTimeoutBar = ((ProgressBar) mView.findViewById(R.id.wps_timeout_bar));
mTimeoutBar.setMax(WPS_TIMEOUT_S);
mTimeoutBar.setProgress(0);
mProgressBar = ((ProgressBar) mView.findViewById(R.id.wps_progress_bar));
mProgressBar.setVisibility(View.GONE);
mButton = ((Button) mView.findViewById(R.id.wps_dialog_btn));
mButton.setText(R.string.wifi_cancel);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
setView(mView);
if (savedInstanceState == null) {
startWps();
}
super.onCreate(savedInstanceState);
}
private void startWps() {
WpsInfo wpsConfig = new WpsInfo();
wpsConfig.setup = mWpsSetup;
mWifiManager.startWps(wpsConfig, mWpsListener);
}
WifiManagerに呼び出すstartWpsPS:
1)
/**
* A class representing Wi-Fi Protected Setup
*
* {@see WifiP2pConfig}
*/
public class WpsInfo implements Parcelable {
/** Push button configuration */
public static final int PBC = 0;
/** Display pin method configuration - pin is generated and displayed on device */
public static final int DISPLAY = 1;
/** Keypad pin method configuration - pin is entered on device */
public static final int KEYPAD = 2;
/** Label pin method configuration - pin is labelled on device */
public static final int LABEL = 3;
/** Invalid configuration */
public static final int INVALID = 4;
2)設定中のwps接続wpsボタンはWpsInfoに対応する.PBC,wps pinはWpsInfoに対応する.DISPLAY
WpsPreferenceController.java
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mWpsPushPref = screen.findPreference(KEY_WPS_PUSH);
mWpsPinPref = screen.findPreference(KEY_WPS_PIN);
if (mWpsPushPref == null || mWpsPinPref == null) {
return;
}
// WpsDialog: Create the dialog like WifiSettings does.
mWpsPushPref.setOnPreferenceClickListener((arg) -> {
WpsFragment wpsFragment = new WpsFragment(WpsInfo.PBC);
wpsFragment.show(mFragmentManager, KEY_WPS_PUSH);
return true;
}
);
// WpsDialog: Create the dialog like WifiSettings does.
mWpsPinPref.setOnPreferenceClickListener((arg) -> {
WpsFragment wpsFragment = new WpsFragment(WpsInfo.DISPLAY);
wpsFragment.show(mFragmentManager, KEY_WPS_PIN);
return true;
});
togglePreferences();
}
2.2 WifiManager
/**
* Start Wi-fi Protected Setup
*
* @param config WPS configuration (does not support {@link WpsInfo#LABEL})
* @param listener for callbacks on success or failure. Can be null.
* @throws IllegalStateException if the WifiManager instance needs to be
* initialized again
*/
public void startWps(WpsInfo config, WpsCallback listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
getChannel().sendMessage(START_WPS, 0, putListener(listener), config);
}
サービス側、すなわちWifiServiceImplにメッセージを送信2.3 WifiServiceImpl
case WifiManager.START_WPS:
if (checkChangePermissionAndReplyIfNotAuthorized(msg, WifiManager.WPS_FAILED)) {
mWifiStateMachine.sendMessage(Message.obtain(msg));
}
break;
WifiStateMachineに送信して処理を続行2.4 WifiStateMachine
class ConnectModeState extends State {
...
case WifiManager.START_WPS:
WpsInfo wpsInfo = (WpsInfo) message.obj;
if (wpsInfo == null) {
loge("Cannot start WPS with null WpsInfo object");
replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
break;
}
WpsResult wpsResult = new WpsResult();
// TODO(b/32898136): Not needed when we start deleting networks from supplicant
// on disconnect.
if (!mWifiNative.removeAllNetworks()) {
loge("Failed to remove networks before WPS");
}
switch (wpsInfo.setup) {
case WpsInfo.PBC:
if (mWifiNative.startWpsPbc(wpsInfo.BSSID)) {
wpsResult.status = WpsResult.Status.SUCCESS;
} else {
Log.e(TAG, "Failed to start WPS push button configuration");
wpsResult.status = WpsResult.Status.FAILURE;
}
break;
case WpsInfo.KEYPAD:
if (mWifiNative.startWpsRegistrar(wpsInfo.BSSID, wpsInfo.pin)) {
wpsResult.status = WpsResult.Status.SUCCESS;
} else {
Log.e(TAG, "Failed to start WPS push button configuration");
wpsResult.status = WpsResult.Status.FAILURE;
}
break;
case WpsInfo.DISPLAY:
wpsResult.pin = mWifiNative.startWpsPinDisplay(wpsInfo.BSSID);
if (!TextUtils.isEmpty(wpsResult.pin)) {
wpsResult.status = WpsResult.Status.SUCCESS;
} else {
Log.e(TAG, "Failed to start WPS pin method configuration");
wpsResult.status = WpsResult.Status.FAILURE;
}
break;
default:
wpsResult = new WpsResult(Status.FAILURE);
loge("Invalid setup for WPS");
break;
}
if (wpsResult.status == Status.SUCCESS) {
replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult);
transitionTo(mWpsRunningState);
} else {
loge("Failed to start WPS with config " + wpsInfo.toString());
replyToMessage(message, WifiManager.WPS_FAILED, WifiManager.ERROR);
}
break;
ここで起動に成功するとWifiManagerが送信されます.START_WPS_SUCCEEDEDがWifiManagerに渡されると、WifiManagerからListenerのonStartedメソッドがコールバックされます. case WifiManager.START_WPS_SUCCEEDED:
if (listener != null) {
WpsResult result = (WpsResult) message.obj;
((WpsCallback) listener).onStarted(result.pin);
//Listener needs to stay until completion or failure
synchronized (mListenerMapLock) {
mListenerMap.put(message.arg2, listener);
}
}
break;
startが終わったらWpsRunningStateに切り替えます /**
* WPS connection flow:
* 1. WifiStateMachine receive WPS_START message from WifiManager API.
* 2. WifiStateMachine initiates the appropriate WPS operation using WifiNative methods:
* {@link WifiNative#startWpsPbc(String)}, {@link WifiNative#startWpsPinDisplay(String)}, etc.
* 3. WifiStateMachine then transitions to this WpsRunningState.
* 4a. Once WifiStateMachine receive the connected event:
* {@link WifiMonitor#NETWORK_CONNECTION_EVENT},
* 4a.1 Load the network params out of wpa_supplicant.
* 4a.2 Add the network with params to WifiConfigManager.
* 4a.3 Enable the network with |disableOthers| set to true.
* 4a.4 Send a response to the original source of WifiManager API using {@link #mSourceMessage}.
* 4b. Any failures are notified to the original source of WifiManager API
* using {@link #mSourceMessage}.
* 5. We then transition to disconnected state and let network selection reconnect to the newly
* added network.
*/
class WpsRunningState extends State {
// Tracks the source to provide a reply
private Message mSourceMessage;
@Override
public void enter() {
mSourceMessage = Message.obtain(getCurrentMessage());
}
@Override
public boolean processMessage(Message message) {
logStateAndMessage(message, this);
switch (message.what) {
case WifiMonitor.WPS_SUCCESS_EVENT:
// Ignore intermediate success, wait for full connection
break;
case WifiMonitor.NETWORK_CONNECTION_EVENT:
Pair loadResult = loadNetworksFromSupplicantAfterWps();
boolean success = loadResult.first;
int netId = loadResult.second;
if (success) {
message.arg1 = netId;
replyToMessage(mSourceMessage, WifiManager.WPS_COMPLETED);
} else {
replyToMessage(mSourceMessage, WifiManager.WPS_FAILED,
WifiManager.ERROR);
}
mSourceMessage.recycle();
mSourceMessage = null;
deferMessage(message);
transitionTo(mDisconnectedState);
break;
コメントに記載されているように、NETWORK_が受信されるCONNECTION_EVENTメッセージ後にwpsが完了したことを示し、その後は一般的なWiFi接続と同じプロセスを開始し、ネットワーク構成をロードし、接続されたネットワークを有効にし、他のものを無効にし、WifiManagerを送信する.WPS_COMPLETEDメッセージはWifiManagerに通知し、WifiManagerはそれに応じてlistenerのonSucceeded()メソッドをコールバックして設定する. case WifiManager.WPS_COMPLETED:
if (listener != null) {
((WpsCallback) listener).onSucceeded();
}
break;
dhcpが終了するとネットワーク状態がconnectedに設定され、ブロードキャストメッセージが受信されwpsフローが終了する } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
WifiManager.EXTRA_NETWORK_INFO);
final NetworkInfo.DetailedState state = info.getDetailedState();
if (state == DetailedState.CONNECTED &&
mDialogState == DialogState.WPS_COMPLETE) {
WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
if (wifiInfo != null) {
String msg = String.format(mContext.getString(
R.string.wifi_wps_connected), wifiInfo.getSSID());
updateDialog(DialogState.CONNECTED, msg);
}
}
}
3.まとめ
wps接続と通常のパスワードの流れを比較すると、実は流れが違うのは検査上で、検査が完了した後にWifiStateMachineに戻るかdhcpに戻るかのセットで接続したのですが、接続の流れがsupplicantに着くのはやはり見ているのが苦痛で、続きます.