(九十九)Android O wpsプロセスの簡単な整理

16312 ワード

1.wps概要
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に呼び出すstartWps
PS:
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に着くのはやはり見ているのが苦痛で、続きます.