WiFi DirectでP2P通信を行う(2)


WiFi DirectとはWiFi対応機器同士を直接接続するP2P通信方式です。
P2P通信の名前の通り、アクセスポイントを必要としないことから、手軽に無線LANを使って通信できるAndorid 4.0の注目の新機能です。
(無線LANでは以前から1対1通信を行うアドホックモードが存在していましたが、WiFi Directではさらに発展させた1対複数通信が可能です)

前回の「WiFi DirectでP2P通信を行う(1)」の続編です。ブロードキャストレシーバに関してはこちらを参照してください。
今回も前回同様、WiFiDirectDemoをサンプルにWiFi Directの接続・切断処理、接続処理中のキャンセルについて解説します。
WiFiDirectDemo http://developer.android.com/resources/samples/WiFiDirectDemo/index.html

注意:Android SDKのAPI ReferenceではWiFi Directを”Wi-Fi p2p”、WiFi Direct対応デバイスを”Peers”と別名で表記しています。記事中でも気をつけて触れていますが随時読み替えてください。
(まだ実機での動作確認ができていませんので記事の記載に誤りがあった場合、随時アップデートします!)

WiFi Directの接続/切断処理のポイントは以下の通りです。

WifiP2pManagerの大事なメソッド

メソッド名説明
ActivityクラスgetSystemService(Context.WIFI_P2P_SERVICE);WifiP2pManagerのインスタンス取得
WifiP2pManager.Channel initialize (Context srcContext, Looper srcLooper, WifiP2pManager.ChannelListener listener) 初期化を行う。第3引数は接続切断時のリスナー、nullでもよい。返り値のChannelは接続チャンネルを識別するために利用する
connect (WifiP2pManager.Channel c, WifiP2pConfig config, WifiP2pManager.ActionListener listener)
P2P接続の開始。接続情報は第2引数WifiP2pConfigに格納する
removeGroup (WifiP2pManager.Channel c, WifiP2pManager.ActionListener listener)
P2P接続の切断。切断したいChannelを指定する
cancelConnect (WifiP2pManager.Channel c, WifiP2pManager.ActionListener listener)
P2P接続処理中(ネゴシエーション中)のキャンセル。接続処理が完了した後はremoveGroupを使用すること。

また前回記事で紹介したWiFi Directの接続状態通知の仕組み(ブロードキャストレシーバの挙動、前回記事参照)について復習しておいたほうがよい内容をまとめています。あわせて確認すると理解が深まるでしょう。
意識的に接続しにいく場合以外では、WiFi Directでの接続状態や本体デバイスや相手のデバイス(Peers)情報は、intentで通知されます。独自のブロードキャストレシーバを用意することで、それらイベント(intent)について簡単に扱うことができます。

WifiP2pManagerで定義されているアクションと対応する処理

アクション名処理
WIFI_P2P_STATE_CHANGED_ACTIONWiFi Directの有効/無効状態が通知される。機能制限やユーザへの通知に利用
WIFI_P2P_PEERS_CHANGED_ACTIONWifiP2pManagerクラスのrequestPeersメソッドをつかってデバイス一覧WifiP2pDeviceListを取得。個々のデバイス情報はWifiP2pDevice
WIFI_P2P_CONNECTION_CHANGED_ACTIONWifiP2pManagerクラスのrequestConnectionInfoメソッドを使って接続状態WiFiP2pInfoを取得する
WIFI_P2P_THIS_DEVICE_CHANGED_ACTIONintentから自分自身のデバイス状態の変更通知WifiP2pDeviceを取り出して、接続状態にあわせた処理を行う

Reference:
http://developer.android.com/reference/android/net/wifi/p2p/WifiP2pManager.html
http://developer.android.com/reference/android/net/wifi/p2p/WifiP2pInfo.html
http://developer.android.com/reference/android/net/wifi/p2p/WifiP2pDevice.html

WiFi Directの接続/切断処理のサンプルコードは続きから

WiFi Directで接続する

接続前の準備としてgetSystemServiceメソッドを使ってWifiP2pManagerを取得して、初期化処理を行います。
WiFiDirectActivity.java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        ...(省略)...
        manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
        channel = manager.initialize(this, getMainLooper(), null);
    }

7行目のinitializeメソッドでは第1引数にthis(Contextを指定)、第2引数にgetMainLooper(WiFi Direct機能を使うLooperを指定する)、第3引数はコネクション切断時のリスナー(WifiP2pManager.ChannelListener)ですが、今回はリスナー登録を行わないため、nullを指定しています。 
初期化が完了すれば、接続処理です(サンプルのWiFiDirectDemoでは、WiFiDirectActivityのconnectメソッドが該当します)。

WiFiDirectActivity.java

public class WiFiDirectActivity extends Activity implements ChannelListener, DeviceActionListener {
    ...(省略)...
    public void connect(WifiP2pConfig config) {
        manager.connect(channel, config, new ActionListener() {

            public void onSuccess() {
                // WiFiDirectBroadcastReceiver will notify us. Ignore for now.
            }

            public void onFailure(int reason) {
                Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }
    ...(省略)...
}

4行目:WifiP2pManagerクラスのconnectメソッドで接続を開始します(ネゴシエーションの開始)。
connectメソッドの第1引数はinitializeで取得したchannelです。第2引数は接続設定WifiP2pConfig(後述)です。
第3引数はWifiP2pManager.ActionListenerインターフェイスです。ネゴシエーションの結果をActionListnerのonSuccessメソッドまたはonFailureメソッドで返却します。
失敗時(onFailure)はエラーコードも通知されます。

このActionListenerは接続正否のみ通知されるため、Activityの制御に最低限必要な簡易的な通知と考えた方が良いでしょう。
どんなデバイスと接続したか、などの詳細情報はブロードキャストレシーバにより接続状態通知として通知されます。

実際にWiFiDirectDemoでの接続設定を行うコードは以下の通りです。
DeviceDetailFragment.java

public class DeviceDetailFragment extends Fragment implements ConnectionInfoListener {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        mContentView = inflater.inflate(R.layout.device_detail, null);
        mContentView.findViewById(R.id.btn_connect).setOnClickListener(new View.OnClickListener() {

            public void onClick(View v) {
                WifiP2pConfig config = new WifiP2pConfig();
                config.deviceAddress = device.deviceAddress;
                config.wps.setup = WpsInfo.PBC;
                if (progressDialog != null && progressDialog.isShowing()) {
                    progressDialog.dismiss();
                }
                progressDialog = ProgressDialog.show(getActivity(), "Press back to cancel",
                        "Connecting to :" + device.deviceAddress, true, true);
                ((DeviceActionListener) getActivity()).connect(config);

            }
        });
        ...(省略)...
        return mContentView;
    }

9行目から11行目にかけて、接続設定を行っています。接続設定用のWifiP2pConfigクラスのインスタンス生成、接続デバイスのIPアドレス設定を行っています。
11行目:WpsInfoはWi-Fi Protected Setupの略称です。定数値PBCはPush button configurationのことでワンタッチで接続設定ができるように指定しています。
http://developer.android.com/reference/android/net/wifi/WpsInfo.html
17行目:WiFiDirectActivityのconnectメソッドを呼び出して、WiFi Directの接続を行います

WiFi Directを切断する


接続済みのWiFi Directのコネクション切断はremoveGroupメソッドを使って簡単に行えます
WiFiDirectActivity.java

public class WiFiDirectActivity extends Activity implements ChannelListener, DeviceActionListener {
    ...(省略)...
    public void disconnect() {
        final DeviceDetailFragment fragment = (DeviceDetailFragment) getFragmentManager()
                .findFragmentById(R.id.frag_detail);
        fragment.resetViews();
        manager.removeGroup(channel, new ActionListener() {

            public void onFailure(int reasonCode) {
                Log.d(TAG, "Disconnect failed. Reason :" + reasonCode);

            }

            public void onSuccess() {
                fragment.getView().setVisibility(View.GONE);
            }

        });
    }
    ...(省略)...
}

7行目:disconnectメソッドの中で、WifiP2pManagerクラスのremoveGroupメソッドが呼ばれています。
第1引数にchannelを指定します。第2引数はActionListenerです。(connectメソッドの第3引数と同様)

切断時のActionListenerも接続正否のみ通知されます。やはりActivityの制御に最低限必要な簡易的な通知と考えてください。
詳細情報はブロードキャストレシーバにより接続状態通知として通知されますが、切断時はデバイスを切り離すだけなので、接続時に比べて必要な作業は多くないでしょう。
実際にWiFiDirectDemoでのコネクション切断を行うコードは以下の通りです。
DeviceDetailFragment.java

public class DeviceDetailFragment extends Fragment implements ConnectionInfoListener {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        mContentView = inflater.inflate(R.layout.device_detail, null);

         ...(省略)...

        mContentView.findViewById(R.id.btn_disconnect).setOnClickListener(
                new View.OnClickListener() {

                    public void onClick(View v) {
                        ((DeviceActionListener) getActivity()).disconnect();
                    }
                });

         ...(省略)...

        return mContentView;
    }

WiFi Direct対応デバイスを表示するフラグメントの生成時(onCreateView)の
13行目:切断ボタンのオンクリックリスナーでWiFiDirectActivityのdicsonnectメソッドが呼び出されています。

WiFi Direct接続をキャンセルする


接続処理(ネゴシエーション)中のキャンセルは単純な切断とは異なり、特殊な処理が必要です。
先に説明したremoveGroupメソッドはネゴシエーション完了後の切断用のため、ネゴシエーション中はcancelConnectメソッドを利用します。
WiFiDirectActivity.java

public class WiFiDirectActivity extends Activity implements ChannelListener, DeviceActionListener {
    ...(省略)...
    public void cancelDisconnect() {

        /*
         * A cancel abort request by user. Disconnect i.e. removeGroup if
         * already connected. Else, request WifiP2pManager to abort the ongoing
         * request
         */
        if (manager != null) {
            final DeviceListFragment fragment = (DeviceListFragment) getFragmentManager()
                    .findFragmentById(R.id.frag_list);
            if (fragment.getDevice() == null
                    || fragment.getDevice().status == WifiP2pDevice.CONNECTED) {
                disconnect();
            } else if (fragment.getDevice().status == WifiP2pDevice.AVAILABLE
                    || fragment.getDevice().status == WifiP2pDevice.INVITED) {

                manager.cancelConnect(channel, new ActionListener() {

                    public void onSuccess() {
                        Toast.makeText(WiFiDirectActivity.this, "Aborting connection",
                                Toast.LENGTH_SHORT).show();
                    }

                    public void onFailure(int reasonCode) {
                        Toast.makeText(WiFiDirectActivity.this,
                                "Connect abort request failed. Reason Code: " + reasonCode,
                                Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }
    }
}

14行目:デバイス状態を確認し、接続済み(WifiP2pDevice.CONNECTED)であれば、disconnectメソッド(→WifiP2pManagerクラスのremoveGroupメソッドを内部で呼んでます)を、
16,17行目:接続可能(WifiP2pDevice.AVAILABLE)、要求済み(ネゴシエーション中、 WifiP2pDevice.INVITED)であれば、WifiP2pManagerクラスのcancelConnectメソッドを呼びます。
cancelConnectメソッドの引数はremoveGroupメソッドと同様に、第1引数にchannel、第2引数にActionListenerを登録します。
ActionListenerでは、Toastなどで結果通知を行うと良いでしょう。

以上、接続、切断、キャンセルを解説しました。おつかれさまでした。