/* * 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.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.wifi.util.HexEncoding; 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.RemoteException; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.modules.utils.build.SdkLevel; 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 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 WifiAwareSession#createNetworkSpecifierOpen(int, byte[]) * @see WifiAwareSession#createNetworkSpecifierPassphrase(int, byte[], String) */ public static final int WIFI_AWARE_DATA_PATH_ROLE_RESPONDER = 1; /** @hide */ @IntDef({ WIFI_AWARE_DISCOVERY_LOST_REASON_UNKNOWN, WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE}) @Retention(RetentionPolicy.SOURCE) public @interface DiscoveryLostReasonCode { } /** * Reason code provided in {@link DiscoverySessionCallback#onServiceLost(PeerHandle, int)} * indicating that the service was lost for unknown reason. */ public static final int WIFI_AWARE_DISCOVERY_LOST_REASON_UNKNOWN = 0; /** * Reason code provided in {@link DiscoverySessionCallback#onServiceLost(PeerHandle, int)} * indicating that the service advertised by the peer is no longer visible. This may be because * the peer is out of range or because the peer stopped advertising this service. */ public static final int WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE = 1; private final Context mContext; private final IWifiAwareManager mService; private final Object mLock = new Object(); // lock access to the following vars /** @hide */ public WifiAwareManager(@NonNull Context context, @NonNull 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(); } } /** * Return the current status of the Aware service: whether or not the device is already attached * to an Aware cluster. To attach to an Aware cluster, please use * {@link #attach(AttachCallback, Handler)} or * {@link #attach(AttachCallback, IdentityChangedListener, Handler)}. * @return A boolean indicating whether the device is attached to a cluster at this time (true) * or not (false). */ public boolean isDeviceAttached() { try { return mService.isDeviceAttached(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Enable the Wifi Aware Instant communication mode. If the device doesn't support this feature * calling this API will result no action. * @see Characteristics#isInstantCommunicationModeSupported() * @param enable true for enable, false otherwise. * @hide */ @SystemApi @RequiresApi(Build.VERSION_CODES.S) public void enableInstantCommunicationMode(boolean enable) { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } try { mService.enableInstantCommunicationMode(mContext.getOpPackageName(), enable); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Return the current status of the Wifi Aware instant communication mode. * If the device doesn't support this feature, return will always be false. * @see Characteristics#isInstantCommunicationModeSupported() * @return true if it is enabled, false otherwise. */ @RequiresApi(Build.VERSION_CODES.S) public boolean isInstantCommunicationModeEnabled() { if (!SdkLevel.isAtLeastS()) { throw new UnsupportedOperationException(); } try { return mService.isInstantCommunicationModeEnabled(); } 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. *
* May return {@code null} if the Wi-Fi Aware service is not initialized. Use * {@link #attach(AttachCallback, Handler)} or * {@link #attach(AttachCallback, IdentityChangedListener, Handler)} to initialize the Wi-Fi * Aware service. * * @return An object specifying configuration limitations of Aware. */ public @Nullable Characteristics getCharacteristics() { try { return mService.getCharacteristics(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Return the available resources of the Wi-Fi aware service: a set of parameters which specify * limitations on service usage, e.g the number of data-paths which could be created. *
* May return {@code null} if the Wi-Fi Aware service is not initialized. Use * {@link #attach(AttachCallback, Handler)} or * {@link #attach(AttachCallback, IdentityChangedListener, Handler)} to initialize the Wi-Fi * Aware service. * * @return An object specifying the currently available resource of the Wi-Fi Aware service. */ public @Nullable AwareResources getAvailableAwareResources() { try { return mService.getAvailableAwareResources(); } 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_FINE_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(), mContext.getAttributionTag(),
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(), mContext.getAttributionTag(), 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(), mContext.getAttributionTag(), 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 */
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public void requestMacAddresses(int uid, List