/* * 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 static android.Manifest.permission.ACCESS_FINE_LOCATION; import static android.Manifest.permission.ACCESS_WIFI_STATE; import static android.Manifest.permission.CHANGE_WIFI_STATE; import static android.Manifest.permission.MANAGE_WIFI_NETWORK_SELECTION; import static android.Manifest.permission.NEARBY_WIFI_DEVICES; import static android.Manifest.permission.OVERRIDE_WIFI_CONFIG; import static android.net.wifi.ScanResult.WIFI_BAND_24_GHZ; import static android.net.wifi.ScanResult.WIFI_BAND_5_GHZ; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; 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.MacAddress; import android.net.NetworkRequest; import android.net.NetworkSpecifier; import android.net.wifi.IBooleanListener; import android.net.wifi.IIntegerListener; import android.net.wifi.IListListener; import android.net.wifi.OuiKeyedData; import android.net.wifi.WifiManager; 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.RemoteException; import android.util.Log; import androidx.annotation.RequiresApi; import com.android.modules.utils.HandlerExecutor; import com.android.modules.utils.build.SdkLevel; import com.android.wifi.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.ref.WeakReference; import java.nio.BufferOverflowException; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * 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"; /** * Intent broadcast sent whenever Wi-Fi Aware resource availability has changed. The resources * are attached with the {@link #EXTRA_AWARE_RESOURCES} extra. The resources can also be * obtained using the {@link #getAvailableAwareResources()} method. To receive this broadcast, * apps must hold {@link android.Manifest.permission#ACCESS_WIFI_STATE}. *
Note: The broadcast is only delivered to registered receivers - no manifest registered * components will be launched. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @RequiresApi(Build.VERSION_CODES.TIRAMISU) @RequiresPermission(ACCESS_WIFI_STATE) public static final String ACTION_WIFI_AWARE_RESOURCE_CHANGED = "android.net.wifi.aware.action.WIFI_AWARE_RESOURCE_CHANGED"; /** * Sent as a part of {@link #ACTION_WIFI_AWARE_RESOURCE_CHANGED} that contains an instance of * {@link AwareResources} representing the current Wi-Fi Aware resources. */ public static final String EXTRA_AWARE_RESOURCES = "android.net.wifi.aware.extra.AWARE_RESOURCES"; /** @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; /** @hide */ @IntDef({ WIFI_AWARE_SUSPEND_REDUNDANT_REQUEST, WIFI_AWARE_SUSPEND_INVALID_SESSION, WIFI_AWARE_SUSPEND_CANNOT_SUSPEND, WIFI_AWARE_SUSPEND_INTERNAL_ERROR}) @Retention(RetentionPolicy.SOURCE) public @interface SessionSuspensionFailedReasonCode {} /** * Reason code provided in {@link DiscoverySessionCallback#onSessionSuspendFailed(int)} when the * session is already suspended. * @hide */ @SystemApi public static final int WIFI_AWARE_SUSPEND_REDUNDANT_REQUEST = 0; /** * Reason code provided in {@link DiscoverySessionCallback#onSessionSuspendFailed(int)} when the * specified session does not support suspension. @hide */ @SystemApi public static final int WIFI_AWARE_SUSPEND_INVALID_SESSION = 1; /** * Reason code provided in {@link DiscoverySessionCallback#onSessionSuspendFailed(int)} when the * session could not be suspended due to more than one app using it. @hide */ @SystemApi public static final int WIFI_AWARE_SUSPEND_CANNOT_SUSPEND = 2; /** * Reason code provided in {@link DiscoverySessionCallback#onSessionSuspendFailed(int)} when an * error is encountered with the request. @hide */ @SystemApi public static final int WIFI_AWARE_SUSPEND_INTERNAL_ERROR = 3; /** @hide */ @IntDef({ WIFI_AWARE_RESUME_REDUNDANT_REQUEST, WIFI_AWARE_RESUME_INVALID_SESSION, WIFI_AWARE_RESUME_INTERNAL_ERROR}) @Retention(RetentionPolicy.SOURCE) public @interface SessionResumptionFailedReasonCode {} /** * Reason code provided in {@link DiscoverySessionCallback#onSessionResumeFailed(int)} when the * session is not suspended. * @hide */ @SystemApi public static final int WIFI_AWARE_RESUME_REDUNDANT_REQUEST = 0; /** * Reason code provided in {@link DiscoverySessionCallback#onSessionResumeFailed(int)} when the * specified session does not support suspension. @hide */ @SystemApi public static final int WIFI_AWARE_RESUME_INVALID_SESSION = 1; /** * Reason code provided in {@link DiscoverySessionCallback#onSessionResumeFailed(int)} when an * error is encountered with the request. @hide */ @SystemApi public static final int WIFI_AWARE_RESUME_INTERNAL_ERROR = 2; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = {"WIFI_BAND_"}, value = {WIFI_BAND_24_GHZ, WIFI_BAND_5_GHZ}) public @interface InstantModeBand {}; 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). */ @RequiresPermission(ACCESS_WIFI_STATE) 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). */ @RequiresPermission(ACCESS_WIFI_STATE) public boolean isDeviceAttached() { try { return mService.isDeviceAttached(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Return the device support for setting a channel requirement in a data-path request. If true * the channel set by * {@link WifiAwareNetworkSpecifier.Builder#setChannelFrequencyMhz(int, boolean)} will be * honored, otherwise it will be ignored. * @return True is the device support set channel on data-path request, false otherwise. */ @RequiresPermission(ACCESS_WIFI_STATE) public boolean isSetChannelOnDataPathSupported() { try { return mService.isSetChannelOnDataPathSupported(); } 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. *
* Note: before {@link android.os.Build.VERSION_CODES#TIRAMISU}, only system app can use this * API. Start with {@link android.os.Build.VERSION_CODES#TIRAMISU} apps hold * {@link android.Manifest.permission#OVERRIDE_WIFI_CONFIG} are allowed to use it. * * @see Characteristics#isInstantCommunicationModeSupported() * @param enable true for enable, false otherwise. * @hide */ @SystemApi @RequiresApi(Build.VERSION_CODES.S) @RequiresPermission(allOf = {CHANGE_WIFI_STATE, OVERRIDE_WIFI_CONFIG}) 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) @RequiresPermission(ACCESS_WIFI_STATE) 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. */ @RequiresPermission(ACCESS_WIFI_STATE) 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. */ @RequiresPermission(ACCESS_WIFI_STATE) 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. */ @RequiresPermission(allOf = { ACCESS_WIFI_STATE, CHANGE_WIFI_STATE }) public void attach(@NonNull AttachCallback attachCallback, @Nullable Handler handler) { attach(handler, null, attachCallback, null, false, 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).
*
* If targeting {@link android.os.Build.VERSION_CODES#TIRAMISU} or later, the application must
* have {@link android.Manifest.permission#NEARBY_WIFI_DEVICES} with
* android:usesPermissionFlags="neverForLocation". If the application does not declare
* android:usesPermissionFlags="neverForLocation", then it must also have
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
*
* If targeting an earlier release than {@link android.os.Build.VERSION_CODES#TIRAMISU}, the
* application must have {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
*
* Apps without {@link android.Manifest.permission#NEARBY_WIFI_DEVICES} or
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION} can use the
* {@link #attach(AttachCallback, Handler)} version.
* Note that aside from permission requirements the {@link IdentityChangedListener} will wake up
* the host at regular intervals causing higher power consumption, do not use it unless the
* information is necessary (e.g. for out-of-band discovery).
*
* @param attachCallback A callback for attach events, extended from
* {@link AttachCallback}.
* @param identityChangedListener A callback for changed identity or cluster ID, 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.
*/
@RequiresPermission(allOf = {
ACCESS_WIFI_STATE,
CHANGE_WIFI_STATE,
ACCESS_FINE_LOCATION,
NEARBY_WIFI_DEVICES}, conditional = true)
public void attach(@NonNull AttachCallback attachCallback,
@NonNull IdentityChangedListener identityChangedListener,
@Nullable Handler handler) {
attach(handler, null, attachCallback, identityChangedListener, false, null);
}
/**
* Attach to the Wi-Fi Aware service - enabling the application to create discovery sessions or
* create connections to peers. See {@link #attach(AttachCallback, IdentityChangedListener,
* Handler)} for more information.
*
* This version allows callers to provide an instance of {@link ConfigRequest}.
*
* @param configRequest Parameters for this request.
* @param executor The executor to execute the listener of the {@code attachCallback} object.
* @param attachCallback A callback for attach events, extended from {@link AttachCallback}.
* @param identityChangedListener A callback for changed identity or cluster ID, extended from
* {@link IdentityChangedListener}.
* @hide
*/
@RequiresPermission(allOf = {
ACCESS_WIFI_STATE,
CHANGE_WIFI_STATE,
ACCESS_FINE_LOCATION,
NEARBY_WIFI_DEVICES,
MANAGE_WIFI_NETWORK_SELECTION}, conditional = true)
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@FlaggedApi(Flags.FLAG_ANDROID_V_WIFI_API)
@SystemApi
public void attach(@NonNull ConfigRequest configRequest,
@NonNull @CallbackExecutor Executor executor, @NonNull AttachCallback attachCallback,
@NonNull IdentityChangedListener identityChangedListener) {
if (!SdkLevel.isAtLeastV()) {
throw new UnsupportedOperationException();
}
Objects.requireNonNull(configRequest);
Objects.requireNonNull(executor);
attach(null, configRequest, attachCallback, identityChangedListener, false, executor);
}
/** @hide */
public void attach(Handler handler, ConfigRequest configRequest,
AttachCallback attachCallback,
IdentityChangedListener identityChangedListener, boolean forOffloading,
Executor executor) {
if (VDBG) {
Log.v(TAG, "attach(): handler=" + handler + ", callback=" + attachCallback
+ ", configRequest=" + configRequest + ", identityChangedListener="
+ identityChangedListener + ", forOffloading" + forOffloading);
}
if (attachCallback == null) {
throw new IllegalArgumentException("Null callback provided");
}
synchronized (mLock) {
Executor localExecutor = executor;
if (localExecutor == null) {
localExecutor = new HandlerExecutor((handler == null)
? new Handler(Looper.getMainLooper()) : handler);
}
try {
Binder binder = new Binder();
Bundle extras = new Bundle();
if (SdkLevel.isAtLeastS()) {
extras.putParcelable(WifiManager.EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE,
mContext.getAttributionSource());
}
mService.connect(binder, mContext.getOpPackageName(), mContext.getAttributionTag(),
new WifiAwareEventCallbackProxy(this, localExecutor, binder,
attachCallback, identityChangedListener), configRequest,
identityChangedListener != null, extras, forOffloading);
} 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 setMasterPreference(int clientId, Binder binder, int mp) {
if (VDBG) Log.v(TAG, "setMasterPreference()");
try {
mService.setMasterPreference(clientId, binder, mp);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
public void getMasterPreference(int clientId, Binder binder, @NonNull Executor executor,
@NonNull Consumer> resultsCallback) {
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(resultsCallback, "resultsCallback cannot be null");
try {
mService.getPairedDevices(
mContext.getOpPackageName(),
new IListListener.Stub() {
public void onResult(List value) {
Binder.clearCallingIdentity();
executor.execute(() -> resultsCallback.accept(value));
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
public void suspend(int clientId, int sessionId) {
try {
mService.suspend(clientId, sessionId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
public void resume(int clientId, int sessionId) {
try {
mService.resume(clientId, sessionId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Attach to the Wi-Fi Aware service as an offload session. All discovery sessions and
* connections will be handled via out-of-band connections.
* The Aware session created by this attach method will have the lowest priority when resource
* conflicts arise (e.g. Aware has to be torn down to create other WiFi interfaces).
*
* @param executor The executor to execute the listener of the {@code attachCallback}
* object.
* @param attachCallback A callback for attach events, extended from
* {@link AttachCallback}.
* @hide
* @see #attach(AttachCallback, Handler)
*/
@SystemApi
@RequiresPermission(allOf = {ACCESS_WIFI_STATE, CHANGE_WIFI_STATE, OVERRIDE_WIFI_CONFIG})
public void attachOffload(@NonNull @CallbackExecutor Executor executor,
@NonNull AttachCallback attachCallback) {
if (executor == null) {
throw new IllegalArgumentException("Null executor provided");
}
attach(null, null, attachCallback, null, true, executor);
}
}