Android 8.0 - Wi-Fi Awareメモ

以下↓の機械翻訳
https://developer.android.com/guide/topics/connectivity/wifi-aware.html#create_a_connection

## 概要

> On some devices running Android 8.0 (API level 26) and higher, Wi-Fi Aware provides the ability to discover and connect directly to each other without any other type of connectivity between them, such as Wi-Fi Access Point or Cellular. Wi-Fi Aware is also known as Neighbor Awareness Networking or NAN.
> 


> Android 8.0（APIレベル26）以降を実行する一部のデバイスでは、Wi-FiアクセスポイントやCellularなど、Wi-Fi Awareは他のタイプの接続なしで互いに直接検出して接続することができます。 Wi-Fi AwareはNeighbor Awareness NetworkingまたはNANとも呼ばれます。


> Wi-Fi Aware networking works by forming clusters with neighboring devices, or by creating a new cluster if the device is the first one in an area. This clustering behavior is device-wide—apps have no control over clustering behavior—and the behavior is managed by the Wi-Fi Aware system service. Apps use the Wi-Fi Aware APIs to talk to the Wi-Fi Aware system service which, in turn, manages the Wi-Fi Aware hardware on the device.
> 

>Wi-Fi対応ネットワークは、隣接デバイスとのクラスタを形成することによって、またはデバイスがエリア内の最初のものであれば新しいクラスタを作成することによって機能する。このクラスタリングの振る舞いはデバイス全体であり、アプリケーションはクラスタリング動作を制御できず、その動作はWi-Fi Awareシステムサービスによって管理されます。アプリはWi-Fi Aware APIを使用してWi-Fi Awareシステムサービスと通信し、Wi-Fi Awareシステムサービスはデバイス上のWi-Fi Awareハードウェアを管理します。
>

>The Wi-Fi Aware APIs are available to apps that target Android 8.0 (API level 26), and let apps perform the following operations:
>

> Wi-Fi Aware APIは、Android 8.0（APIレベル26）をターゲットとするアプリケーションで使用でき、アプリに次の操作を実行させることができます。

- Discover other devices - The API has a mechanism for finding other devices in the vicinity. The process starts when one device publishes a discoverable service. Then, when a device subscribes to one or more services and enters the publisher's Wi-Fi range, the subscriber receives a notification that a matching publisher has been discovered. After the subscriber discovers a publisher, the subscriber can either send a short message or establish a network connection with the discovered device.

> 他のデバイスの発見 -  APIには、近くにある他のデバイスを見つけるための仕組みがあります。 1つのデバイスが検出可能なサービスを公開すると、プロセスが開始されます。次に、デバイスが1つ以上のサービスに加入し、パブリッシャのWi-Fi範囲に入ると、加入者は、一致するパブリッシャが発見されたという通知を受信します。サブスクライバがパブリッシャを検出した後、サブスクライバは、ショートメッセージを送信するか、または検出されたデバイスとのネットワーク接続を確立することができます。


- Create a network connection - After two devices have discovered each other, either through Wi-Fi Aware discovery or some other mechanism like Bluetooth or BLE, they can create a bi-directional Wi-Fi Aware network connection without an access point.

> ネットワーク接続の作成 -  2つのデバイスがWi-Fi AwareディスカバリまたはBluetoothやBLEなどの他のメカニズムを介して互いに検出した後、アクセスポイントなしで双方向のWi-Fi Awareネットワーク接続を作成できます。

>Wi-Fi Aware network connections are more reliable than Wi-Fi P2P connections and support higher throughput rates across longer distances than Bluetooth connections. These types of connections are useful for apps that share data between users, such as photo-sharing apps.

> Wi-Fi対応ネットワーク接続は、Wi-Fi P2P接続よりも信頼性が高く、Bluetooth接続よりも長距離にわたって高いスループットをサポートします。これらのタイプの接続は、写真共有アプリなど、ユーザー間でデータを共有するアプリに便利です。
> 

## Wi-Fi Awareセットアップ

### パーミッションの設定

```xml
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
```

### 端末のWi-Fi Awareサポートのチェック

```java
context.getPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
```

### Wi-Fi Aware利用可能状態変更通知を受け取るブロードキャストレシーバの登録

```java
IntentFilter filter =
    new IntentFilter(WifiAwareManager.WIFI_AWARE_STATE_CHANGED_ACTION);
BroadcastReceiver myReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(final Context context, final Intent intent) {
        if (WifiAwareManager.isAvailable()) {
            //Wi-Fi Awareが利用可能になった時の処理
        } else {
            //Wi-Fi Awareが利用不可になった時の処理
        }
    }
};
context.registerReceiver(myReceiver, filter);
```

## セッションの取得

> To start using Wi-Fi Aware, your app must obtain a WifiAwareSession by calling attach(). This method does the following:
> 

> Wi-Fi Awareの使用を開始するには、あなたのアプリケーションはattach（）を呼び出してWifiAwareSessionを取得する必要があります。このメソッドは次の処理を行います。

- Turns on the Wi-Fi Aware hardware.
- Forms a cluster if none exists.
- Creates a Wi-Fi Aware session with a unique namespace that acts as a container for all discovery sessions created within it.

## サービスのパブリッシュ

サービスを検出可能にするには、WiFiAwareSession#publish()メソッドを呼ぶ。

```java
PublishConfig config = new PublishConfig.Builder()
    .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
    .build();

mAwareSession.publish(config, new DiscoverySessionCallback() {
    @Override
    public void onPublishStarted(PublishDiscoverySession session) {
        //パブリッシュが成功したら呼ばれる。
        //一致するサブスクライバアプリを実行しているデバイスが公開デバイスのWi-Fi範囲に移動すると、サブスクライバはそのサービスを検出します。サブスクライバがパブリッシャを検出すると、パブリッシャはサブスクライバがパブリッシャにメッセージを送信するまで通知を受け取りません。
    }
    @Override
    public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
        //サブスクライバがパブリッシャにメッセージを送信すると、パブリッシャは通知を受け取ります。それが起こると、onMessageReceived（）コールバックメソッドが実行されます。このメソッドのPeerHandle引数を使用して、メッセージをサブスクライバに送り返すか、またはサブスクライバへの接続を作成できます。
    }, null);
```

> To stop publishing the service, call DiscoverySession.close(). Discovery sessions are associated with their parent WifiAwareSession. If the parent session is closed, its associated discovery sessions are also closed. However, the system doesn't guarantee when out-of-scope sessions are closed, so we recommend that you manually call DiscoverySession.close().

> サービスの公開を停止するには、DiscoverySession.close（）を呼び出します。ディスカバリーセッションは、親WifiAwareSessionに関連付けられています。親セッションがクローズされている場合、その関連ディスカバリー・セッションもクローズされます。ただし、スコープ外のセッションが閉じられたときは保証されないため、手動でDiscoverySession.close（）を呼び出すことをお勧めします。

## サービスのサブスクライブ

サービスをサブスクライブするにはWiFiAwareSession#subscribe()メソッドを呼ぶ。

```java
SubscribeConfig config = new SubscribeConfig.Builder()
    .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
    .build();

mAwareSession.subscribe(config, new DiscoverySessionCallback() {
    @Override
    public void onSubscribeStarted(SubscribeDiscoverySession session) {
        // サブスクライブ操作が成功すると、システムはアプリケーションでonSubscribeStarted（）コールバックを実行します。コールバックのSubscribeDiscoverySession引数を使用して、アプリがそれを検出した後にパブリッシャと通信できるので、この参照を保存する必要があります。ディスカバリー・セッションでupdateSubscribe（）を呼び出すと、いつでも記述を更新できます。
    }

    @Override
    public void onServiceDiscovered(PeerHandle peerHandle,
            byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
        // 一致するパブリッシャがWi-Fi圏内に来ると、システムはonServiceDiscovered（）コールバックメソッドを実行します。このコールバックのPeerHandle引数を使用して、メッセージを送信したり、そのパブリッシャへの接続を作成することができます。
    }
}, null);
```

## メッセージの送信

> To send a message to another device, you need the following objects:
> A DiscoverySession. This object allows you to call sendMessage(). Your app gets a DiscoverySession by either publishing a service or subscribing to a service.
The other device's PeerHandle, so you know where to send the message. Your app gets another device's PeerHandle in one of two ways:
Your app publishes a service. Then, when it receives a message from a subscriber, your app gets the subscriber's PeerHandle from the onMessageReceived() callback.
Your app subscribes to a service. Then, when it discovers a matching publisher, your app gets the publisher's PeerHandle from the onServiceDiscovered() callback.
To send a message, call sendMessage(). The following callbacks might then occur:

> 別のデバイスにメッセージを送信するには、次のオブジェクトが必要です。
> ディスカバリーセッション。このオブジェクトを使用すると、sendMessage（）を呼び出すことができます。あなたのアプリは、サービスを公開するかサービスに加入することでDiscoverySessionを取得します。
他のデバイスのPeerHandleは、メッセージの送信先を知っています。あなたのアプリは別のデバイスのPeerHandleを2つの方法のいずれかで取得します：
あなたのアプリはサービスを公開します。次に、サブスクライバからメッセージを受け取ると、アプリケーションはonMessageReceived（）コールバックからサブスクライバのPeerHandleを取得します。
あなたのアプリはサービスに加入しています。次に、一致するパブリッシャを検出すると、onServiceDiscovered（）コールバックからパブリッシャのPeerHandleが取得されます。
メッセージを送信するには、sendMessage（）を呼び出します。次のコールバックが発生する可能性があります。

> When the message is successfully received by the peer, the system executes the onMessageSendSucceeded() callback in the sending app.
When the peer receives a message, the system executes the onMessageReceived() callback in the receiving app.
Note: Messages are generally used for lightweight messaging, as they might not be delivered and are limited to about 255 bytes in length. To determine the exact length limit, call getMaxServiceSpecificInfoLength(). For high speed, bi-directional communication, your app should create a connection instead.

> メッセージがピアによって正常に受信されると、システムは送信アプリでonMessageSendSucceeded（）コールバックを実行します。
ピアがメッセージを受信すると、受信側のアプリケーションでonMessageReceived（）コールバックが実行されます。
注：メッセージは一般的に軽量メッセージングに使用されます。メッセージは配信されない可能性があり、約255バイトの長さに制限されているためです。正確な長さ制限を判別するには、getMaxServiceSpecificInfoLength（）を呼び出します。高速双方向通信の場合、アプリは代わりに接続を作成する必要があります。

## 接続

> There are two ways to create a Wi-Fi Aware connection. The first way assumes that you have used Wi-Fi Aware to discover the other device and you have the other device's PeerHandle. The second way assumes that you have discovered the other device's MAC address through some other mechanism, such as Bluetooth or BLE. This is known as out-of-band discovery, or OOB.

> Wi-Fi Aware接続を作成するには、2つの方法があります。 最初の方法は、Wi-Fi Awareを使用して他のデバイスを検出し、他のデバイスのPeerHandleを使用していることを前提としています。 2番目の方法では、BluetoothやBLEなどの他のメカニズムを使用して、他のデバイスのMACアドレスを検出したことを前提としています。 これは帯域外検出、つまりOOBと呼ばれます。

> Regardless of which method you choose, there are always two devices in a Wi-Fi Aware connection: an initiator and a responder. If you're using Wi-Fi Aware discovery, then the roles are fixed and don't need to be explicitly specified: the subscriber is the initiator and the publisher is the responder. If you are using out-of-band discovery, then the devices need to negotiate these roles on their own.

> どちらの方法を選択しても、Wi-Fi Aware接続には、イニシエータとレスポンダという2つのデバイスが常に存在します。 Wi-Fi Aware検出を使用している場合、役割は固定されており、明示的に指定する必要はありません。サブスクライバはイニシエータで、パブリッシャはレスポンダです。 帯域外検出を使用している場合、デバイスはこれらの役割を独自にネゴシエートする必要があります。

> To create a connection, complete the following sequence of steps:

>1. Create a network specifier:
For Wi-Fi Aware discovery, call either DiscoverySession.createNetworkSpecifierOpen() or DiscoverySession.createNetworkSpecifierPassphrase() from the publisher and subscriber.
For OOB discovery, call either WifiAwareSession.createNetworkSpecifierOpen() or WifiAwareSession.createNetworkSpecifierPassphrase() from both devices.
The responder isn't required to provide a MAC address or a PeerHandle. If no MAC address or PeerHandle is specified, the device accepts all connection requests.

> 接続を作成するには、次の手順を実行します。

> 1. ネットワーク指定子を作成する：
Wi-Fi Awareディスカバリの場合は、パブリッシャとサブスクライバからDiscoverySession.createNetworkSpecifierOpen（）またはDiscoverySession.createNetworkSpecifierPassphrase（）を呼び出します。
OOBディスカバリの場合は、両方のデバイスからWifiAwareSession.createNetworkSpecifierOpen（）またはWifiAwareSession.createNetworkSpecifierPassphrase（）を呼び出します。
レスポンダは、MACアドレスまたはPeerHandleを提供する必要はありません。 MACアドレスまたはPeerHandleが指定されていない場合、デバイスはすべての接続要求を受け入れます。

> 2. Build a network request, setting the transport mechanism to TRANSPORT_WIFI_AWARE:
 
> トランスポートメカニズムをTRANSPORT_WIFI_AWAREに設定して、ネットワーク要求を作成します。

```java
NetworkRequest myNetworkRequest = new NetworkRequest.Builder()
     .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
     .setNetworkSpecifier(networkSpecifier)
     .build();
```

> 3. Call requestNetwork() and provide the following callback methods:

> requestNetwork（）を呼び出し、次のコールバックメソッドを提供します。

```java
mCallback = new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        ...
    }
    @Override
    public void onLinkPropertiesChanged(Network network,
            LinkProperties linkProperties) {
        ...
    }
    @Override
    public void onLost(Network network) {
        ...
    }
};
mConnMgr.requestNetwork(networkRequest, mCallback);
```

> The appropriate callback methods are executed when the network connection is available, changed, or lost.

> 適切なコールバックメソッドは、ネットワーク接続が使用可能、変更、または失われたときに実行されます。

> 4 When you're finished with the network connection, call unregisterNetworkCallback().
Note: Building a network request and specifying the required network capabilities aren't specific to the Wi-Fi Aware API. For more information on working with network requests, see ConnectivityManager.

> ネットワーク接続が終了したら、unregisterNetworkCallback（）を呼び出します。
注：ネットワーク要求の作成と必要なネットワーク機能の指定は、Wi-Fi Aware APIに固有のものではありません。 ネットワーク要求の処理の詳細については、ConnectivityManagerを参照してください。

---

## 上記を踏まえたテストアプリ作成

上記を参考にパブリッシュを開始しサブスクライバからメッセージを受け取るパブリッシャ側
アプリと、サブスクライブしパブリッシャへメッセージを送信するサブスクライバ側アプリ実装を試す。

※手元のNexus5XとNexux9ではFEATURE_WIFI_AWAREのフィーチャーを持っていないため
hasSystemFeatureの判定ではじかれ動作確認できなかった。adb shell pm list featuresで確認してもリストになかった。
なので、動作は保証しません。

### パブリッシャ側アクティビティ

```java
package com.example.user.wifiawaretest;

import android.app.Activity;
import android.content.pm.PackageManager;
import android.net.wifi.aware.AttachCallback;
import android.net.wifi.aware.DiscoverySessionCallback;
import android.net.wifi.aware.PeerHandle;
import android.net.wifi.aware.PublishConfig;
import android.net.wifi.aware.PublishDiscoverySession;
import android.net.wifi.aware.WifiAwareManager;
import android.net.wifi.aware.WifiAwareSession;
import android.os.Handler;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity {

    private WifiAwareSession mAwareSession;
    private Handler mHandler = new Handler();
    public static final String AWARE_SERVICE_NAME = "Aware Service";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)) {
            Toast.makeText(this, "Wi-Fi Aware not supoortted", Toast.LENGTH_SHORT).show();
            return;
        }

        WifiAwareManager was = (WifiAwareManager)getSystemService(WifiAwareManager.class);

        was.attach(new MyAttachCallback(), mHandler);
    }

    class MyAttachCallback extends AttachCallback {
        @Override
        public void onAttachFailed() {
            Toast.makeText(MainActivity.this, "onAttachFailed", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onAttached(WifiAwareSession session) {
            Toast.makeText(MainActivity.this, "onAttach", Toast.LENGTH_SHORT).show();

            MainActivity.this.mAwareSession = session;
            PublishConfig config = new PublishConfig.Builder()
                    .setServiceName(AWARE_SERVICE_NAME)
                    .build();
            MainActivity.this.mAwareSession.publish(config, new DiscoverySessionCallback() {
                @Override
                public void onPublishStarted(PublishDiscoverySession session) {
                    Toast.makeText(MainActivity.this, "onPublishStarted", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
                    Toast.makeText(MainActivity.this, "onMessageReceived : " + message.toString(), Toast.LENGTH_SHORT).show();
                }
            }, null);
        }
    }
}
```

### サブスクライバ側

```java
package com.example.user.wifiawaresubscribertest;

import android.app.Activity;
import android.content.pm.PackageManager;
import android.net.wifi.aware.AttachCallback;
import android.net.wifi.aware.DiscoverySessionCallback;
import android.net.wifi.aware.PeerHandle;
import android.net.wifi.aware.PublishConfig;
import android.net.wifi.aware.PublishDiscoverySession;
import android.net.wifi.aware.SubscribeConfig;
import android.net.wifi.aware.SubscribeDiscoverySession;
import android.net.wifi.aware.WifiAwareManager;
import android.net.wifi.aware.WifiAwareSession;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Toast;

import java.util.List;

public class MainActivity extends Activity {

    private WifiAwareSession mAwareSession;
    private Handler mHandler = new Handler();
    public static final String AWARE_SERVICE_NAME = "Aware Service";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)) {
            Toast.makeText(this, "Wi-Fi Aware not supoortted", Toast.LENGTH_SHORT).show();
            return;
        }

        WifiAwareManager was = (WifiAwareManager)getSystemService(WifiAwareManager.class);

        was.attach(new MyAttachCallback(), mHandler);
    }

    class MyAttachCallback extends AttachCallback {
        @Override
        public void onAttachFailed() {
            Toast.makeText(MainActivity.this, "onAttachFailed", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onAttached(WifiAwareSession session) {
            Toast.makeText(MainActivity.this, "onAttach", Toast.LENGTH_SHORT).show();

            MainActivity.this.mAwareSession = session;

            SubscribeConfig config = new SubscribeConfig.Builder()
                    .setServiceName(AWARE_SERVICE_NAME)
                    .build();

            mAwareSession.subscribe(config, new DiscoverySessionCallback() {

                private PeerHandle mPeerHandle = null;
                @Override
                public void onSubscribeStarted(SubscribeDiscoverySession session) {
                    if (mPeerHandle != null) {
                        int messageId = 1;
                        session.sendMessage(mPeerHandle, messageId, "Test Message".getBytes());
                    }
                }

                @Override
                public void onServiceDiscovered(PeerHandle peerHandle,
                                                byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
                    mPeerHandle = peerHandle;
                }
            }, null);

        }
    }
}
```

### 共通 (パーミッション設定)

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.user.wifiawaretest">
    
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
```