Wi-Fi Aware の概要

Wi-Fi Aware 機能により、Android 8.0(API レベル 26)以降を搭載しているデバイスは、他の種類の接続なしでデバイス同士を直接検出して接続できます。Wi-Fi Aware は、Neighbor Awareness Networking(NAN)とも呼ばれます。

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

Wi-Fi Aware API を使用すると、アプリで次のオペレーションを実施できます。

  • 他のデバイスを検出する: API には、近くにある他のデバイスを検出するメカニズムがあります。このプロセスは、1 つのデバイスが 1 つ以上の検出可能なサービスを公開すると開始されます。次に、デバイスが 1 つ以上のサービスに登録し、パブリッシャーの Wi-Fi 範囲に入ると、一致するパブリッシャーが検出されたという通知がサブスクライバーに送信されます。サブスクライバーは、パブリッシャーを発見した後、短いメッセージを送信するか、検出されたデバイスとのネットワーク接続を確立できます。デバイスは、同時にパブリッシャーとサブスクライバーの両方になれます。

  • ネットワーク接続を作成する: 2 台のデバイスが互いを検出したら、アクセス ポイントなしで双方向 Wi-Fi Aware ネットワーク接続を作成できます。

Wi-Fi Aware ネットワーク接続は、Bluetooth 接続よりも長距離にわたって高いスループット レートをサポートします。このタイプの接続は、写真共有アプリなど、ユーザー間で大量のデータを共有するアプリに役立ちます。

Android 12(API レベル 31)の機能強化

Android 12(API レベル 31)では、Wi-Fi Aware に対する機能強化が行われています。

  • Android 12(API レベル 31)以降を搭載しているデバイスでは、onServiceLost() コールバックを使用して、サービスの停止や範囲外への移動により、検出されたサービスがアプリで失われたときにアラートを受け取ることができます。
  • Wi-Fi Aware のデータパスの設定が簡略化されました。以前のバージョンでは、L2 メッセージングを使用してイニシエータの MAC アドレスが提供されるため、レイテンシが発生していました。Android 12 以降を搭載したデバイスでは、任意のピアを受け入れるようにレスポンダー(サーバー)を設定できます。つまり、イニエータの MAC アドレスをあらかじめ把握する必要はありません。これにより、データパスの立ち上げが高速化され、1 つのネットワーク リクエストだけで複数のポイントツーポイント リンクが有効になります。
  • Android 12 以降で実行されるアプリでは、WifiAwareManager.getAvailableAwareResources() メソッドを使用して、現在利用可能なデータパスの数の取得、セッションの公開、セッションのサブスクライブを行うことができます。これにより、目的の機能を実行するのに十分なリソースがあるかどうかをアプリで判断できます。

初期設定

Wi-Fi Aware の検出とネットワークを使用するようにアプリを設定するには、次の手順を行います。

  1. アプリのマニフェストで次の権限をリクエストします。

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- If your app targets Android 13 (API level 33)
         or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
                     <!-- If your app derives location information from
                          Wi-Fi APIs, don't include the "usesPermissionFlags"
                          attribute. -->
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                     <!-- If any feature in your app relies on precise location
                          information, don't include the "maxSdkVersion"
                          attribute. -->
                     android:maxSdkVersion="32" />
    
  2. 以下に示すように、デバイスが PackageManager API を使用して Wi-Fi Aware をサポートしているかどうかを確認します。

    Kotlin

    context.packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE)
    

    Java

    context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_AWARE);
    
  3. Wi-Fi Aware が現在利用できるかどうかを確認します。デバイス上に Wi-Fi Aware があっても、ユーザーが Wi-Fi または位置情報を無効にしているために、現在利用できない場合があります。ハードウェアやファームウェアの機能によっては、Wi-Fi Direct、SoftAP、テザリングを使用している場合、一部のデバイスでは Wi-Fi Aware がサポートされていない場合があります。Wi-Fi Aware が現在使用可能かどうかを確認するには、isAvailable() を呼び出します。

    Wi-Fi Aware を利用できるかどうかは、いつでも変更される可能性があります。アプリは BroadcastReceiver を登録して ACTION_WIFI_AWARE_STATE_CHANGED を受信する必要があります。これは、在庫状況が変更されるたびに送信されます。アプリがブロードキャスト インテントを受信すると、既存のセッションをすべて破棄し(Wi-Fi Aware サービスが中断したと想定)、現在の可用性の状態を確認し、それに応じて動作を調整する必要があります。次に例を示します。

    Kotlin

    val wifiAwareManager = context.getSystemService(Context.WIFI_AWARE_SERVICE) as WifiAwareManager?
    val filter = IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED)
    val myReceiver = object : BroadcastReceiver() {
    
        override fun onReceive(context: Context, intent: Intent) {
            // discard current sessions
            if (wifiAwareManager?.isAvailable) {
                ...
            } else {
                ...
            }
        }
    }
    context.registerReceiver(myReceiver, filter)
    

    Java

    WifiAwareManager wifiAwareManager = 
            (WifiAwareManager)context.getSystemService(Context.WIFI_AWARE_SERVICE)
    IntentFilter filter =
            new IntentFilter(WifiAwareManager.ACTION_WIFI_AWARE_STATE_CHANGED);
    BroadcastReceiver myReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // discard current sessions
            if (wifiAwareManager.isAvailable()) {
                ...
            } else {
                ...
            }
        }
    };
    context.registerReceiver(myReceiver, filter);
    

詳細については、ブロードキャストをご覧ください。

セッションを取得する

Wi-Fi Aware の使用を開始するには、アプリで attach() を呼び出して WifiAwareSession を取得する必要があります。このメソッドは次の処理を行います。

  • Wi-Fi Aware ハードウェアをオンにする。
  • Wi-Fi Aware クラスタに参加、または Wi-Fi Aware クラスタを形成する。
  • 一意の名前空間を持つ Wi-Fi Aware セッションを作成します。この名前空間は、その中で作成されたすべての検出セッションのコンテナとして機能します。

アプリが正常にアタッチされると、システムは onAttached() コールバックを実行します。このコールバックは、アプリが以降のすべてのセッション オペレーションで使用する必要がある WifiAwareSession オブジェクトを提供します。アプリは、セッションを使用してサービスを公開したり、サービスに登録したりできます。

アプリは attach() を 1 回だけ呼び出す必要があります。アプリが attach() を複数回呼び出した場合、アプリは呼び出しごとに異なるセッションを受け取り、それぞれに独自の名前空間を持ちます。これは複雑なシナリオで役立ちますが、通常は回避する必要があります。

サービスを公開する

サービスを検出可能にするには、次のパラメータを受け取る publish() メソッドを呼び出します。

  • PublishConfig には、サービスの名前と、一致フィルタなどの他の構成プロパティを指定します。
  • DiscoverySessionCallback は、イベントが発生したとき(サブスクライバーがメッセージを受信したときなど)に実行するアクションを指定します。

次の例をご覧ください。

Kotlin

val config: PublishConfig = PublishConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.publish(config, object : DiscoverySessionCallback() {

    override fun onPublishStarted(session: PublishDiscoverySession) {
        ...
    }

    override fun onMessageReceived(peerHandle: PeerHandle, message: ByteArray) {
        ...
    }
})

Java

PublishConfig config = new PublishConfig.Builder()
    .setServiceName(“Aware_File_Share_Service_Name”)
    .build();

awareSession.publish(config, new DiscoverySessionCallback() {
    @Override
    public void onPublishStarted(PublishDiscoverySession session) {
        ...
    }
    @Override
    public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
        ...
    }
}, null);

パブリッシュが成功すると、onPublishStarted() コールバック メソッドが呼び出されます。

公開後、一致するサブスクライバー アプリを実行しているデバイスが公開デバイスの Wi-Fi 範囲に移動すると、サブスクライバーはサービスを検出します。サブスクライバーがパブリッシャーを発見したとき、パブリッシャーは通知を受信しません。ただし、サブスクライバーがパブリッシャーにメッセージを送信すると、パブリッシャーは通知を受信します。その場合、onMessageReceived() コールバック メソッドが呼び出されます。このメソッドの PeerHandle 引数を使用して、サブスクライバーにメッセージを送信したり、サブスクライバーへの接続を作成したりできます。

サービスの公開を停止するには、DiscoverySession.close() を呼び出します。検出セッションは親 WifiAwareSession に関連付けられます。親セッションを閉じると、関連付けられている検出セッションも終了します。破棄されたオブジェクトもクローズされますが、対象範囲外のセッションがいつクローズされるかは保証されないため、close() メソッドを明示的に呼び出すことをおすすめします。

サービスに登録する

サービスに登録するには、次のパラメータを受け取る subscribe() メソッドを呼び出します。

  • SubscribeConfig には、登録するサービスの名前と、一致フィルタなどの他の構成プロパティを指定します。
  • DiscoverySessionCallback は、パブリッシャーが検出されたときなど、イベントが発生したときに実行するアクションを指定します。

次の例をご覧ください。

Kotlin

val config: SubscribeConfig = SubscribeConfig.Builder()
        .setServiceName(AWARE_FILE_SHARE_SERVICE_NAME)
        .build()
awareSession.subscribe(config, object : DiscoverySessionCallback() {

    override fun onSubscribeStarted(session: SubscribeDiscoverySession) {
        ...
    }

    override fun onServiceDiscovered(
            peerHandle: PeerHandle,
            serviceSpecificInfo: ByteArray,
            matchFilter: List<ByteArray>
    ) {
        ...
    }
}, null)

Java

SubscribeConfig config = new SubscribeConfig.Builder()
    .setServiceName("Aware_File_Share_Service_Name")
    .build();

awareSession.subscribe(config, new DiscoverySessionCallback() {
    @Override
    public void onSubscribeStarted(SubscribeDiscoverySession session) {
        ...
    }

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

サブスクライブ オペレーションが成功すると、システムはアプリで onSubscribeStarted() コールバックを呼び出します。アプリがパブリッシャーを検出した後に、コールバックで SubscribeDiscoverySession 引数を使用してパブリッシャーと通信できるため、この参照を保存する必要があります。検出セッションで updateSubscribe() を呼び出すことで、いつでもサブスクライブ セッションを更新できます。

この時点で、サブスクリプションは一致するパブリッシャーが Wi-Fi 範囲内に入るのを待ちます。この場合、システムは onServiceDiscovered() コールバック メソッドを実行します。このコールバックの PeerHandle 引数を使用して、そのパブリッシャーへのメッセージを送信したり、接続を作成したりできます。

サービスへの登録を停止するには、DiscoverySession.close() を呼び出します。検出セッションは親 WifiAwareSession に関連付けられます。親セッションを閉じると、関連付けられている検出セッションも終了します。破棄されたオブジェクトもクローズされますが、対象範囲外のセッションがいつクローズされるかは保証されないため、close() メソッドを明示的に呼び出すことをおすすめします。

メッセージを送信して

別のデバイスにメッセージを送信するには、次のオブジェクトが必要です。

  • DiscoverySession。このオブジェクトを使用すると、sendMessage() を呼び出すことができます。アプリは、サービスを公開するサービスに登録することで、DiscoverySession を取得します。

  • 他のデバイスの PeerHandle(メッセージをルーティングするため)。アプリは、次の 2 つの方法のいずれかで別のデバイスの PeerHandle を取得します。

    • アプリがサービスを公開し、サブスクライバーからメッセージを受信します。アプリは onMessageReceived() コールバックからサブスクライバーの PeerHandle を取得します。
    • アプリがサービスに登録します。次に、一致するパブリッシャーを検出すると、アプリは onServiceDiscovered() コールバックからパブリッシャーの PeerHandle を取得します。

メッセージを送信するには、sendMessage() を呼び出します。この場合、次のコールバックが発生する可能性があります。

  • ピアがメッセージを正常に受信すると、システムによって送信側のアプリで onMessageSendSucceeded() コールバックが呼び出されます。
  • ピアがメッセージを受信すると、システムは受信側のアプリで onMessageReceived() コールバックを呼び出します。

PeerHandle はピアとの通信に必要ですが、ピアの永続的な識別子としては使用しないでください。上位レベルの識別子はアプリケーションで使用できます。検出サービス自体や後続のメッセージに埋め込まれます。検出サービスに識別子を埋め込むには、PublishConfig または SubscribeConfigsetMatchFilter() または setServiceSpecificInfo() メソッドを使用します。setMatchFilter() メソッドは検出に影響を与えますが、setServiceSpecificInfo() メソッドは検出に影響しません。

識別子をメッセージに埋め込むことは、メッセージ バイト配列を変更して識別子(たとえば、最初の数バイトとして)が含まれることを意味します。

接続を作成する

Wi-Fi Aware は、2 つの Wi-Fi Aware デバイス間のクライアント サーバー ネットワークをサポートします。

クライアント サーバー接続の設定手順は次のとおりです。

  1. Wi-Fi Aware 検出を使用して、(サーバー上で)サービスを公開し、(クライアント上で)サービスに登録します。

  2. サブスクライバーがパブリッシャーを検出したら、サブスクライバーからパブリッシャーにメッセージを送信します。

  3. パブリッシャー デバイスで ServerSocket を起動し、ポートを設定または取得します。

    Kotlin

    val ss = ServerSocket(0)
    val port = ss.localPort
    

    Java

    ServerSocket ss = new ServerSocket(0);
    int port = ss.getLocalPort();
    
  4. ConnectivityManager で、WifiAwareNetworkSpecifier を使用してパブリッシャーの Wi-Fi Aware ネットワークをリクエストします。検出セッションと、サブスクライバーが送信したメッセージから取得したサブスクライバーの PeerHandle を指定します。

    Kotlin

    val networkSpecifier = WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build()
    val myNetworkRequest = NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build()
    val callback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            ...
        }
    
        override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
            ...
        }
    
        override fun onLost(network: Network) {
            ...
        }
    }
    
    connMgr.requestNetwork(myNetworkRequest, callback);
    

    Java

    NetworkSpecifier networkSpecifier = new WifiAwareNetworkSpecifier.Builder(discoverySession, peerHandle)
        .setPskPassphrase("somePassword")
        .setPort(port)
        .build();
    NetworkRequest myNetworkRequest = new NetworkRequest.Builder()
        .addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
        .setNetworkSpecifier(networkSpecifier)
        .build();
    ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            ...
        }
    
        @Override
        public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
            ...
        }
    
        @Override
        public void onLost(Network network) {
            ...
        }
    };
    
    ConnectivityManager connMgr.requestNetwork(myNetworkRequest, callback);
    
  5. パブリッシャーは、ネットワークをリクエストしたら、サブスクライバーにメッセージを送信する必要があります。

  6. サブスクライバーがパブリッシャーからメッセージを受信したら、パブリッシャーと同じメソッドを使用して、サブスクライバーの Wi-Fi Aware ネットワークをリクエストします。NetworkSpecifier を作成するときにポートを指定しないでください。ネットワーク接続が使用可能になったとき、変更されたとき、または失われたときに、適切なコールバック メソッドが呼び出されます。

  7. サブスクライバーで onAvailable() メソッドが呼び出されると、Network オブジェクトが利用可能になります。このオブジェクトを使用して Socket を開いてパブリッシャーの ServerSocket と通信できますが、ServerSocket の IPv6 アドレスとポートを把握している必要があります。これらは、onCapabilitiesChanged() コールバックで提供される NetworkCapabilities オブジェクトから取得します。

    Kotlin

    val peerAwareInfo = networkCapabilities.transportInfo as WifiAwareNetworkInfo
    val peerIpv6 = peerAwareInfo.peerIpv6Addr
    val peerPort = peerAwareInfo.port
    ...
    val socket = network.getSocketFactory().createSocket(peerIpv6, peerPort)
    

    Java

    WifiAwareNetworkInfo peerAwareInfo = (WifiAwareNetworkInfo) networkCapabilities.getTransportInfo();
    Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr();
    int peerPort = peerAwareInfo.getPort();
    ...
    Socket socket = network.getSocketFactory().createSocket(peerIpv6, peerPort);
    
  8. ネットワーク接続が完了したら、unregisterNetworkCallback() を呼び出します。

ピアの距離測定と位置情報に基づく検出

Wi-Fi RTT の位置情報機能を備えたデバイスでは、ピアまでの距離を直接測定し、この情報を使用して Wi-Fi Aware サービス ディスカバリを制限できます。

Wi-Fi RTT API を使用すると、MAC アドレスまたは PeerHandle を使用して、Wi-Fi Aware ピアを直接距離測定できます。

Wi-Fi Aware 検出は、特定のジオフェンス内のサービスのみを検出するように制限できます。たとえば、3 m(3,000 mm として指定)以上 10 m 以下(10,000 mm として指定)の "Aware_File_Share_Service_Name" サービスを公開するデバイスを検出できるジオフェンスを設定できます。

ジオフェンスを有効にするには、パブリッシャーとサブスクライバーの両方で対応する必要があります。

  • パブリッシャーは setRangingEnabled(true) を使用して、公開サービスで距離測定を有効にする必要があります。

    パブリッシャーが距離測定を有効にしていない場合、サブスクライバーが指定したジオフェンス制約は無視され、距離に関係なく通常の検出が実行されます。

  • サブスクライバーは、setMinDistanceMmsetMaxDistanceMm を組み合わせてジオフェンスを指定する必要があります。

    どちらの値でも、距離が指定されていない場合は制限がないことを意味します。最大距離のみを指定した場合は、最小距離が 0 になります。最小距離のみを指定した場合、最大値はないことを意味します。

ジオフェンス内でピアサービスが検出されると、onServiceDiscoveredWithinRange コールバックがトリガーされ、ピアまでの測定された距離が提供されます。その後、必要に応じて Direct Wi-Fi RTT API を呼び出して、後で距離を測定できます。