/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.net.wifi.aware; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemService; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.util.Log; import libcore.util.HexEncoding; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.nio.BufferOverflowException; import java.util.List; /** * This class provides the primary API for managing Wi-Fi Aware operations: * discovery and peer-to-peer data connections. *
* The class provides access to: *
* Aware may not be usable when Wi-Fi is disabled (and other conditions). To validate that * the functionality is available use the {@link #isAvailable()} function. To track * changes in Aware usability register for the {@link #ACTION_WIFI_AWARE_STATE_CHANGED} * broadcast. Note that this broadcast is not sticky - you should register for it and then * check the above API to avoid a race condition. *
* An application must use {@link #attach(AttachCallback, Handler)} to initialize a * Aware cluster - before making any other Aware operation. Aware cluster membership is a * device-wide operation - the API guarantees that the device is in a cluster or joins a * Aware cluster (or starts one if none can be found). Information about attach success (or * failure) are returned in callbacks of {@link AttachCallback}. Proceed with Aware * discovery or connection setup only after receiving confirmation that Aware attach * succeeded - {@link AttachCallback#onAttached(WifiAwareSession)}. When an * application is finished using Aware it must use the * {@link WifiAwareSession#close()} API to indicate to the Aware service that the device * may detach from the Aware cluster. The device will actually disable Aware once the last * application detaches. *
* Once a Aware attach is confirmed use the * {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, Handler)} * or * {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback, * Handler)} to create publish or subscribe Aware discovery sessions. Events are called on the * provided callback object {@link DiscoverySessionCallback}. Specifically, the * {@link DiscoverySessionCallback#onPublishStarted(PublishDiscoverySession)} * and * {@link DiscoverySessionCallback#onSubscribeStarted( *SubscribeDiscoverySession)} * return {@link PublishDiscoverySession} and * {@link SubscribeDiscoverySession} * objects respectively on which additional session operations can be performed, e.g. updating * the session {@link PublishDiscoverySession#updatePublish(PublishConfig)} and * {@link SubscribeDiscoverySession#updateSubscribe(SubscribeConfig)}. Sessions can * also be used to send messages using the * {@link DiscoverySession#sendMessage(PeerHandle, int, byte[])} APIs. When an * application is finished with a discovery session it must terminate it using the * {@link DiscoverySession#close()} API. *
* Creating connections between Aware devices is managed by the standard * {@link ConnectivityManager#requestNetwork(NetworkRequest, * ConnectivityManager.NetworkCallback)}. * The {@link NetworkRequest} object should be constructed with: *
Note: The broadcast is only delivered to registered receivers - no manifest registered * components will be launched. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_WIFI_AWARE_STATE_CHANGED = "android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED"; /** @hide */ @IntDef({ WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, WIFI_AWARE_DATA_PATH_ROLE_RESPONDER}) @Retention(RetentionPolicy.SOURCE) public @interface DataPathRole { } /** * Connection creation role is that of INITIATOR. Used to create a network specifier string * when requesting a Aware network. * * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle) * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String) * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[]) * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String) */ public static final int WIFI_AWARE_DATA_PATH_ROLE_INITIATOR = 0; /** * Connection creation role is that of RESPONDER. Used to create a network specifier string * when requesting a Aware network. * * @see DiscoverySession#createNetworkSpecifierOpen(PeerHandle) * @see DiscoverySession#createNetworkSpecifierPassphrase(PeerHandle, String) * @see WifiAwareSession#createNetworkSpecifierOpen(int, byte[]) * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String) */ public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1; private final Context mContext; private final IWifiAwareManager mService; private final Object mLock = new Object(); // lock access to the following vars /** @hide */ public WifiAwareManager(Context context, IWifiAwareManager service) { mContext = context; mService = service; } /** * Returns the current status of Aware API: whether or not Aware is available. To track * changes in the state of Aware API register for the * {@link #ACTION_WIFI_AWARE_STATE_CHANGED} broadcast. * * @return A boolean indicating whether the app can use the Aware API at this time (true) or * not (false). */ public boolean isAvailable() { try { return mService.isUsageEnabled(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns the characteristics of the Wi-Fi Aware interface: a set of parameters which specify * limitations on configurations, e.g. the maximum service name length. * * @return An object specifying configuration limitations of Aware. */ public Characteristics getCharacteristics() { try { return mService.getCharacteristics(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or * create connections to peers. The device will attach to an existing cluster if it can find * one or create a new cluster (if it is the first to enable Aware in its vicinity). Results * (e.g. successful attach to a cluster) are provided to the {@code attachCallback} object. * An application must call {@link WifiAwareSession#close()} when done with the * Wi-Fi Aware object. *
* Note: a Aware cluster is a shared resource - if the device is already attached to a cluster * then this function will simply indicate success immediately using the same {@code * attachCallback}. * * @param attachCallback A callback for attach events, extended from * {@link AttachCallback}. * @param handler The Handler on whose thread to execute the callbacks of the {@code * attachCallback} object. If a null is provided then the application's main thread will be * used. */ public void attach(@NonNull AttachCallback attachCallback, @Nullable Handler handler) { attach(handler, null, attachCallback, null); } /** * Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or * create connections to peers. The device will attach to an existing cluster if it can find * one or create a new cluster (if it is the first to enable Aware in its vicinity). Results * (e.g. successful attach to a cluster) are provided to the {@code attachCallback} object. * An application must call {@link WifiAwareSession#close()} when done with the * Wi-Fi Aware object. *
* Note: a Aware cluster is a shared resource - if the device is already attached to a cluster * then this function will simply indicate success immediately using the same {@code * attachCallback}. *
* This version of the API attaches a listener to receive the MAC address of the Aware interface
* on startup and whenever it is updated (it is randomized at regular intervals for privacy).
* The application must have the {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
* permission to execute this attach request. Otherwise, use the
* {@link #attach(AttachCallback, Handler)} version. Note that aside from permission
* requirements this listener will wake up the host at regular intervals causing higher power
* consumption, do not use it unless the information is necessary (e.g. for OOB discovery).
*
* @param attachCallback A callback for attach events, extended from
* {@link AttachCallback}.
* @param identityChangedListener A listener for changed identity, extended from
* {@link IdentityChangedListener}.
* @param handler The Handler on whose thread to execute the callbacks of the {@code
* attachCallback} and {@code identityChangedListener} objects. If a null is provided then the
* application's main thread will be used.
*/
public void attach(@NonNull AttachCallback attachCallback,
@NonNull IdentityChangedListener identityChangedListener,
@Nullable Handler handler) {
attach(handler, null, attachCallback, identityChangedListener);
}
/** @hide */
public void attach(Handler handler, ConfigRequest configRequest,
AttachCallback attachCallback,
IdentityChangedListener identityChangedListener) {
if (VDBG) {
Log.v(TAG, "attach(): handler=" + handler + ", callback=" + attachCallback
+ ", configRequest=" + configRequest + ", identityChangedListener="
+ identityChangedListener);
}
if (attachCallback == null) {
throw new IllegalArgumentException("Null callback provided");
}
synchronized (mLock) {
Looper looper = (handler == null) ? Looper.getMainLooper() : handler.getLooper();
try {
Binder binder = new Binder();
mService.connect(binder, mContext.getOpPackageName(),
new WifiAwareEventCallbackProxy(this, looper, binder, attachCallback,
identityChangedListener), configRequest,
identityChangedListener != null);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
/** @hide */
public void disconnect(int clientId, Binder binder) {
if (VDBG) Log.v(TAG, "disconnect()");
try {
mService.disconnect(clientId, binder);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public void publish(int clientId, Looper looper, PublishConfig publishConfig,
DiscoverySessionCallback callback) {
if (VDBG) Log.v(TAG, "publish(): clientId=" + clientId + ", config=" + publishConfig);
if (callback == null) {
throw new IllegalArgumentException("Null callback provided");
}
try {
mService.publish(mContext.getOpPackageName(), clientId, publishConfig,
new WifiAwareDiscoverySessionCallbackProxy(this, looper, true, callback,
clientId));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public void updatePublish(int clientId, int sessionId, PublishConfig publishConfig) {
if (VDBG) {
Log.v(TAG, "updatePublish(): clientId=" + clientId + ",sessionId=" + sessionId
+ ", config=" + publishConfig);
}
try {
mService.updatePublish(clientId, sessionId, publishConfig);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public void subscribe(int clientId, Looper looper, SubscribeConfig subscribeConfig,
DiscoverySessionCallback callback) {
if (VDBG) {
if (VDBG) {
Log.v(TAG,
"subscribe(): clientId=" + clientId + ", config=" + subscribeConfig);
}
}
if (callback == null) {
throw new IllegalArgumentException("Null callback provided");
}
try {
mService.subscribe(mContext.getOpPackageName(), clientId, subscribeConfig,
new WifiAwareDiscoverySessionCallbackProxy(this, looper, false, callback,
clientId));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public void updateSubscribe(int clientId, int sessionId, SubscribeConfig subscribeConfig) {
if (VDBG) {
Log.v(TAG, "updateSubscribe(): clientId=" + clientId + ",sessionId=" + sessionId
+ ", config=" + subscribeConfig);
}
try {
mService.updateSubscribe(clientId, sessionId, subscribeConfig);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public void terminateSession(int clientId, int sessionId) {
if (VDBG) {
Log.d(TAG,
"terminateSession(): clientId=" + clientId + ", sessionId=" + sessionId);
}
try {
mService.terminateSession(clientId, sessionId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public void sendMessage(int clientId, int sessionId, PeerHandle peerHandle, byte[] message,
int messageId, int retryCount) {
if (peerHandle == null) {
throw new IllegalArgumentException(
"sendMessage: invalid peerHandle - must be non-null");
}
if (VDBG) {
Log.v(TAG, "sendMessage(): clientId=" + clientId + ", sessionId=" + sessionId
+ ", peerHandle=" + peerHandle.peerId + ", messageId="
+ messageId + ", retryCount=" + retryCount);
}
try {
mService.sendMessage(clientId, sessionId, peerHandle.peerId, message, messageId,
retryCount);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public NetworkSpecifier createNetworkSpecifier(int clientId, int role, int sessionId,
@NonNull PeerHandle peerHandle, @Nullable byte[] pmk, @Nullable String passphrase) {
if (VDBG) {
Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
+ ", peerHandle=" + ((peerHandle == null) ? peerHandle : peerHandle.peerId)
+ ", pmk=" + ((pmk == null) ? "null" : "non-null")
+ ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
}
if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
&& role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
throw new IllegalArgumentException(
"createNetworkSpecifier: Invalid 'role' argument when creating a network "
+ "specifier");
}
if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext,
Build.VERSION_CODES.P)) {
if (peerHandle == null) {
throw new IllegalArgumentException(
"createNetworkSpecifier: Invalid peer handle - cannot be null");
}
}
return new WifiAwareNetworkSpecifier(
(peerHandle == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB_ANY_PEER
: WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB,
role,
clientId,
sessionId,
peerHandle != null ? peerHandle.peerId : 0, // 0 is an invalid peer ID
null, // peerMac (not used in this method)
pmk,
passphrase,
Process.myUid());
}
/** @hide */
public NetworkSpecifier createNetworkSpecifier(int clientId, @DataPathRole int role,
@NonNull byte[] peer, @Nullable byte[] pmk, @Nullable String passphrase) {
if (VDBG) {
Log.v(TAG, "createNetworkSpecifier: role=" + role
+ ", pmk=" + ((pmk == null) ? "null" : "non-null")
+ ", passphrase=" + ((passphrase == null) ? "null" : "non-null"));
}
if (role != WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
&& role != WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
throw new IllegalArgumentException(
"createNetworkSpecifier: Invalid 'role' argument when creating a network "
+ "specifier");
}
if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR || !WifiAwareUtils.isLegacyVersion(mContext,
Build.VERSION_CODES.P)) {
if (peer == null) {
throw new IllegalArgumentException(
"createNetworkSpecifier: Invalid peer MAC - cannot be null");
}
}
if (peer != null && peer.length != 6) {
throw new IllegalArgumentException("createNetworkSpecifier: Invalid peer MAC address");
}
return new WifiAwareNetworkSpecifier(
(peer == null) ? WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB_ANY_PEER
: WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
role,
clientId,
0, // 0 is an invalid session ID
0, // 0 is an invalid peer ID
peer,
pmk,
passphrase,
Process.myUid());
}
private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub {
private static final int CALLBACK_CONNECT_SUCCESS = 0;
private static final int CALLBACK_CONNECT_FAIL = 1;
private static final int CALLBACK_IDENTITY_CHANGED = 2;
private final Handler mHandler;
private final WeakReference