blob: 19dc61eeb4611fcf53735fca96c934c2892dcc6a [file] [log] [blame]
/*
* 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.NEARBY_WIFI_DEVICES;
import static android.Manifest.permission.OVERRIDE_WIFI_CONFIG;
import android.annotation.CallbackExecutor;
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.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 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.
* <p>
* The class provides access to:
* <ul>
* <li>Initialize a Aware cluster (peer-to-peer synchronization). Refer to
* {@link #attach(AttachCallback, Handler)}.
* <li>Create discovery sessions (publish or subscribe sessions). Refer to
* {@link WifiAwareSession#publish(PublishConfig, DiscoverySessionCallback, Handler)} and
* {@link WifiAwareSession#subscribe(SubscribeConfig, DiscoverySessionCallback, Handler)}.
* <li>Create a Aware network specifier to be used with
* {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
* to set-up a Aware connection with a peer. Refer to {@link WifiAwareNetworkSpecifier.Builder}.
* </ul>
* <p>
* 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.
* <p>
* 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 <b>must</b> 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.
* <p>
* 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 <b>must</b> terminate it using the
* {@link DiscoverySession#close()} API.
* <p>
* Creating connections between Aware devices is managed by the standard
* {@link ConnectivityManager#requestNetwork(NetworkRequest,
* ConnectivityManager.NetworkCallback)}.
* The {@link NetworkRequest} object should be constructed with:
* <ul>
* <li>{@link NetworkRequest.Builder#addTransportType(int)} of
* {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_AWARE}.
* <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using
* {@link WifiAwareNetworkSpecifier.Builder}.
* </ul>
*/
@SystemService(Context.WIFI_AWARE_SERVICE)
public class WifiAwareManager {
private static final String TAG = "WifiAwareManager";
private static final boolean DBG = false;
private static final boolean VDBG = false; // STOPSHIP if true
/**
* Broadcast intent action to indicate that the state of Wi-Fi Aware availability has changed
* and all active Aware sessions are no longer usable. Use the {@link #isAvailable()} to query
* the current status.
* This broadcast is <b>not</b> sticky, use the {@link #isAvailable()} API after registering
* the broadcast to check the current state of Wi-Fi Aware.
* <p>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}.
* <p>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;
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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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 <b>must</b> call {@link WifiAwareSession#close()} when done with the
* Wi-Fi Aware object.
* <p>
* 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 <b>must</b> call {@link WifiAwareSession#close()} when done with the
* Wi-Fi Aware object.
* <p>
* 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}.
* <p>
* 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);
}
/** @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<Integer> resultsCallback) {
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(resultsCallback, "resultsCallback cannot be null");
try {
mService.getMasterPreference(clientId, binder,
new IIntegerListener.Stub() {
public void onResult(int value) {
Binder.clearCallingIdentity();
executor.execute(() -> resultsCallback.accept(value));
}
});
} 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 {
Bundle extras = new Bundle();
if (SdkLevel.isAtLeastS()) {
extras.putParcelable(WifiManager.EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE,
mContext.getAttributionSource());
}
mService.publish(mContext.getOpPackageName(), mContext.getAttributionTag(), clientId,
publishConfig,
new WifiAwareDiscoverySessionCallbackProxy(this, looper, true, callback,
clientId), extras);
} 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 {
Bundle extras = new Bundle();
if (SdkLevel.isAtLeastS()) {
extras.putParcelable(WifiManager.EXTRA_PARAM_KEY_ATTRIBUTION_SOURCE,
mContext.getAttributionSource());
}
mService.subscribe(mContext.getOpPackageName(), mContext.getAttributionTag(), clientId,
subscribeConfig,
new WifiAwareDiscoverySessionCallbackProxy(this, looper, false, callback,
clientId), extras);
} 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 void initiateNanPairingSetupRequest(int clientId, int sessionId, PeerHandle peerHandle,
String password, String pairingDeviceAlias, int cipherSuite) {
if (peerHandle == null) {
throw new IllegalArgumentException(
"initiateNanPairingRequest: invalid peerHandle - must be non-null");
}
if (VDBG) {
Log.v(TAG, "initiateNanPairingRequest(): clientId=" + clientId
+ ", sessionId=" + sessionId + ", peerHandle=" + peerHandle.peerId);
}
try {
mService.initiateNanPairingSetupRequest(clientId, sessionId, peerHandle.peerId,
password, pairingDeviceAlias, cipherSuite);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
public void responseNanPairingSetupRequest(int clientId, int sessionId, PeerHandle peerHandle,
int requestId, String password, String pairingDeviceAlias, boolean accept,
int cipherSuite) {
if (peerHandle == null) {
throw new IllegalArgumentException(
"initiateNanPairingRequest: invalid peerHandle - must be non-null");
}
if (VDBG) {
Log.v(TAG, "initiateNanPairingRequest(): clientId=" + clientId
+ ", sessionId=" + sessionId + ", peerHandle=" + peerHandle.peerId);
}
try {
mService.responseNanPairingSetupRequest(clientId, sessionId, peerHandle.peerId,
requestId, password, pairingDeviceAlias, accept, cipherSuite);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
*/
public void initiateBootStrappingSetupRequest(int clientId, int sessionId,
PeerHandle peerHandle, int method) {
if (peerHandle == null) {
throw new IllegalArgumentException(
"initiateBootStrappingSetupRequest: invalid peerHandle - must be non-null");
}
if (VDBG) {
Log.v(TAG, "initiateBootStrappingSetupRequest(): clientId=" + clientId
+ ", sessionId=" + sessionId + ", peerHandle=" + peerHandle.peerId);
}
try {
mService.initiateBootStrappingSetupRequest(clientId, sessionId, peerHandle.peerId,
method);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public void requestMacAddresses(int uid, int[] peerIds,
IWifiAwareMacAddressProvider callback) {
try {
mService.requestMacAddresses(uid, peerIds, callback);
} 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 (!WifiAwareUtils.isLegacyVersion(mContext, Build.VERSION_CODES.Q)) {
throw new UnsupportedOperationException(
"API deprecated - use WifiAwareNetworkSpecifier.Builder");
}
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,
0, // no port info for deprecated IB APIs
-1); // no transport info for deprecated IB APIs
}
/** @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,
0, // no port info for OOB APIs
-1); // no transport protocol info for OOB APIs
}
private static class WifiAwareEventCallbackProxy extends IWifiAwareEventCallback.Stub {
private final WeakReference<WifiAwareManager> mAwareManager;
private final Binder mBinder;
private final Executor mExecutor;
private final AttachCallback mAttachCallback;
private final IdentityChangedListener mIdentityChangedListener;
/**
* Constructs a {@link AttachCallback} using the specified looper.
* All callbacks will delivered on the thread of the specified looper.
*
* @param executor The executor to execute the callbacks.
*/
WifiAwareEventCallbackProxy(WifiAwareManager mgr, Executor executor, Binder binder,
final AttachCallback attachCallback,
final IdentityChangedListener identityChangedListener) {
mAwareManager = new WeakReference<>(mgr);
mExecutor = executor;
mBinder = binder;
mAttachCallback = attachCallback;
mIdentityChangedListener = identityChangedListener;
}
@Override
public void onConnectSuccess(int clientId) {
if (VDBG) Log.v(TAG, "onConnectSuccess");
Binder.clearCallingIdentity();
mExecutor.execute(() -> {
WifiAwareManager mgr = mAwareManager.get();
if (mgr == null) {
Log.w(TAG, "WifiAwareEventCallbackProxy: handleMessage post GC");
return;
}
mAttachCallback.onAttached(new WifiAwareSession(mgr, mBinder, clientId));
});
}
@Override
public void onConnectFail(int reason) {
if (VDBG) Log.v(TAG, "onConnectFail: reason=" + reason);
Binder.clearCallingIdentity();
mExecutor.execute(() -> {
WifiAwareManager mgr = mAwareManager.get();
if (mgr == null) {
Log.w(TAG, "WifiAwareEventCallbackProxy: handleMessage post GC");
return;
}
mAwareManager.clear();
mAttachCallback.onAttachFailed();
});
}
@Override
public void onIdentityChanged(byte[] mac) {
if (VDBG) Log.v(TAG, "onIdentityChanged: mac=" + new String(HexEncoding.encode(mac)));
Binder.clearCallingIdentity();
mExecutor.execute(() -> {
WifiAwareManager mgr = mAwareManager.get();
if (mgr == null) {
Log.w(TAG, "WifiAwareEventCallbackProxy: handleMessage post GC");
return;
}
if (mIdentityChangedListener == null) {
Log.e(TAG, "CALLBACK_IDENTITY_CHANGED: null listener.");
} else {
mIdentityChangedListener.onIdentityChanged(mac);
}
});
}
@Override
public void onAttachTerminate() {
if (VDBG) Log.v(TAG, "onAwareSessionTerminated");
Binder.clearCallingIdentity();
mExecutor.execute(() -> {
WifiAwareManager mgr = mAwareManager.get();
if (mgr == null) {
Log.w(TAG, "WifiAwareEventCallbackProxy: handleMessage post GC");
return;
}
mAwareManager.clear();
mAttachCallback.onAwareSessionTerminated();
});
}
@Override
public void onClusterIdChanged(
@IdentityChangedListener.ClusterChangeEvent int clusterEventType,
byte[] clusterId) {
Binder.clearCallingIdentity();
mExecutor.execute(() -> {
WifiAwareManager mgr = mAwareManager.get();
if (mgr == null) {
Log.w(TAG, "WifiAwareEventCallbackProxy: handleMessage post GC");
return;
}
if (mIdentityChangedListener == null) {
Log.e(TAG, "CALLBACK_CLUSTER_ID_CHANGED: null listener.");
} else {
try {
mIdentityChangedListener.onClusterIdChanged(
clusterEventType, MacAddress.fromBytes(clusterId));
} catch (IllegalArgumentException iae) {
Log.e(TAG, " Invalid MAC address, " + iae);
}
}
});
}
}
private static class WifiAwareDiscoverySessionCallbackProxy extends
IWifiAwareDiscoverySessionCallback.Stub {
private final WeakReference<WifiAwareManager> mAwareManager;
private final boolean mIsPublish;
private final DiscoverySessionCallback mOriginalCallback;
private final int mClientId;
private final Handler mHandler;
private DiscoverySession mSession;
WifiAwareDiscoverySessionCallbackProxy(WifiAwareManager mgr, Looper looper,
boolean isPublish, DiscoverySessionCallback originalCallback,
int clientId) {
mAwareManager = new WeakReference<>(mgr);
mIsPublish = isPublish;
mOriginalCallback = originalCallback;
mClientId = clientId;
if (VDBG) {
Log.v(TAG, "WifiAwareDiscoverySessionCallbackProxy ctor: isPublish=" + isPublish);
}
mHandler = new Handler(looper);
}
@Override
public void onSessionStarted(int sessionId) {
if (VDBG) Log.v(TAG, "onSessionStarted: sessionId=" + sessionId);
mHandler.post(() -> onProxySessionStarted(sessionId));
}
@Override
public void onSessionConfigSuccess() {
if (VDBG) Log.v(TAG, "onSessionConfigSuccess");
mHandler.post(mOriginalCallback::onSessionConfigUpdated);
}
@Override
public void onSessionConfigFail(int reason) {
if (VDBG) Log.v(TAG, "onSessionConfigFail: reason=" + reason);
mHandler.post(() -> {
mOriginalCallback.onSessionConfigFailed();
if (mSession == null) {
/* creation failed (as opposed to update failing) */
mAwareManager.clear();
}
});
}
@Override
public void onSessionTerminated(int reason) {
if (VDBG) Log.v(TAG, "onSessionTerminated: reason=" + reason);
mHandler.post(() -> onProxySessionTerminated(reason));
}
@Override
public void onSessionSuspendSucceeded() {
if (VDBG) Log.v(TAG, "onSessionSuspendSucceeded");
mHandler.post(mOriginalCallback::onSessionSuspendSucceeded);
}
@Override
public void onSessionSuspendFail(int reason) {
if (VDBG) Log.v(TAG, "onSessionSuspendFail: reason=" + reason);
mHandler.post(() -> mOriginalCallback.onSessionSuspendFailed(reason));
}
@Override
public void onSessionResumeSucceeded() {
if (VDBG) Log.v(TAG, "onSessionResumeSucceeded");
mHandler.post(mOriginalCallback::onSessionResumeSucceeded);
}
@Override
public void onSessionResumeFail(int reason) {
if (VDBG) Log.v(TAG, "onSessionResumeFail: reason=" + reason);
mHandler.post(() -> mOriginalCallback.onSessionResumeFailed(reason));
}
@Override
public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter,
int peerCipherSuite, byte[] scid, String pairingAlias,
AwarePairingConfig pairingConfig) {
if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
mHandler.post(() -> {
List<byte[]> matchFilterList = getMatchFilterList(matchFilter);
mOriginalCallback.onServiceDiscovered(new PeerHandle(peerId), serviceSpecificInfo,
matchFilterList);
mOriginalCallback.onServiceDiscovered(
new ServiceDiscoveryInfo(new PeerHandle(peerId), peerCipherSuite,
serviceSpecificInfo, matchFilterList, scid, pairingAlias,
pairingConfig));
});
}
private List<byte[]> getMatchFilterList(byte[] matchFilter) {
List<byte[]> matchFilterList = null;
try {
matchFilterList = new TlvBufferUtils.TlvIterable(0, 1, matchFilter).toList();
} catch (BufferOverflowException e) {
matchFilterList = Collections.emptyList();
Log.e(TAG, "onServiceDiscovered: invalid match filter byte array '"
+ new String(HexEncoding.encode(matchFilter))
+ "' - cannot be parsed: e=" + e);
}
return matchFilterList;
}
@Override
public void onMatchWithDistance(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter,
int distanceMm, int peerCipherSuite, byte[] scid, String pairingAlias,
AwarePairingConfig pairingConfig) {
if (VDBG) {
Log.v(TAG, "onMatchWithDistance: peerId=" + peerId + ", distanceMm=" + distanceMm);
}
mHandler.post(() -> {
List<byte[]> matchFilterList = getMatchFilterList(matchFilter);
mOriginalCallback.onServiceDiscoveredWithinRange(
new PeerHandle(peerId),
serviceSpecificInfo,
matchFilterList, distanceMm);
mOriginalCallback.onServiceDiscoveredWithinRange(
new ServiceDiscoveryInfo(
new PeerHandle(peerId),
peerCipherSuite,
serviceSpecificInfo,
matchFilterList,
scid,
pairingAlias,
pairingConfig),
distanceMm);
});
}
@Override
public void onMatchExpired(int peerId) {
if (VDBG) {
Log.v(TAG, "onMatchExpired: peerId=" + peerId);
}
mHandler.post(() ->
mOriginalCallback.onServiceLost(new PeerHandle(peerId),
WIFI_AWARE_DISCOVERY_LOST_REASON_PEER_NOT_VISIBLE));
}
@Override
public void onMessageSendSuccess(int messageId) {
if (VDBG) Log.v(TAG, "onMessageSendSuccess");
mHandler.post(() -> mOriginalCallback.onMessageSendSucceeded(messageId));
}
@Override
public void onMessageSendFail(int messageId, int reason) {
if (VDBG) Log.v(TAG, "onMessageSendFail: reason=" + reason);
mHandler.post(() -> mOriginalCallback.onMessageSendFailed(messageId));
}
@Override
public void onMessageReceived(int peerId, byte[] message) {
if (VDBG) {
Log.v(TAG, "onMessageReceived: peerId=" + peerId);
}
mHandler.post(() -> mOriginalCallback.onMessageReceived(new PeerHandle(peerId),
message));
}
@Override
public void onPairingSetupRequestReceived(int peerId, int requestId) {
mHandler.post(() ->
mOriginalCallback.onPairingSetupRequestReceived(new PeerHandle(peerId),
requestId));
}
@Override
public void onPairingSetupConfirmed(int peerId, boolean accept, String alias) {
if (accept) {
mHandler.post(() -> mOriginalCallback
.onPairingSetupSucceeded(new PeerHandle(peerId), alias));
} else {
mHandler.post(() -> mOriginalCallback
.onPairingSetupFailed(new PeerHandle(peerId)));
}
}
@Override
public void onPairingVerificationConfirmed(int peerId, boolean accept, String alias) {
if (accept) {
mHandler.post(() -> mOriginalCallback.onPairingVerificationSucceed(
new PeerHandle(peerId), alias));
} else {
mHandler.post(() -> mOriginalCallback
.onPairingVerificationFailed(new PeerHandle(peerId)));
}
}
@Override
public void onBootstrappingVerificationConfirmed(int peerId, boolean accept, int method) {
if (accept) {
mHandler.post(() -> mOriginalCallback.onBootstrappingSucceeded(
new PeerHandle(peerId), method));
} else {
mHandler.post(() -> mOriginalCallback.onBootstrappingFailed(
new PeerHandle(peerId)));
}
}
/*
* Proxies methods
*/
public void onProxySessionStarted(int sessionId) {
if (VDBG) Log.v(TAG, "Proxy: onSessionStarted: sessionId=" + sessionId);
if (mSession != null) {
Log.e(TAG,
"onSessionStarted: sessionId=" + sessionId + ": session already created!?");
throw new IllegalStateException(
"onSessionStarted: sessionId=" + sessionId + ": session already created!?");
}
WifiAwareManager mgr = mAwareManager.get();
if (mgr == null) {
Log.w(TAG, "onProxySessionStarted: mgr GC'd");
return;
}
if (mIsPublish) {
PublishDiscoverySession session = new PublishDiscoverySession(mgr,
mClientId, sessionId);
mSession = session;
mOriginalCallback.onPublishStarted(session);
} else {
SubscribeDiscoverySession
session = new SubscribeDiscoverySession(mgr, mClientId, sessionId);
mSession = session;
mOriginalCallback.onSubscribeStarted(session);
}
}
public void onProxySessionTerminated(int reason) {
if (VDBG) Log.v(TAG, "Proxy: onSessionTerminated: reason=" + reason);
if (mSession != null) {
mSession.setTerminated();
mSession = null;
} else {
Log.w(TAG, "Proxy: onSessionTerminated called but mSession is null!?");
}
mAwareManager.clear();
mOriginalCallback.onSessionTerminated();
}
}
/**
* Set Wi-Fi Aware protocol parameters.
* @hide
* @param params An object contain specified parameters. Use {@code null} to remove previously
* set configuration and restore default behavior.
*/
@SystemApi
@RequiresPermission(allOf = {OVERRIDE_WIFI_CONFIG,
CHANGE_WIFI_STATE})
public void setAwareParams(@Nullable AwareParams params) {
try {
mService.setAwareParams(params);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Set all Wi-Fi Aware sessions created by the calling app to be opportunistic. Opportunistic
* Wi-Fi Aware sessions are considered low priority and may be torn down (the sessions or the
* Aware interface) if there are resource conflicts.
*
* @param enabled True to configure all Wi-Fi Aware sessions by the calling app as
* Opportunistic. False by default.
*/
@RequiresPermission(CHANGE_WIFI_STATE)
public void setOpportunisticModeEnabled(boolean enabled) {
try {
mService.setOpportunisticModeEnabled(mContext.getOpPackageName(), enabled);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Indicate whether all Wi-Fi Aware sessions created by the calling app are opportunistic as
* defined and configured by {@link #setOpportunisticModeEnabled(boolean)}
*
* @param executor The executor on which callback will be invoked.
* @param resultsCallback An asynchronous callback that will return boolean
*/
@RequiresPermission(ACCESS_WIFI_STATE)
public void isOpportunisticModeEnabled(@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Boolean> resultsCallback) {
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(resultsCallback, "resultsCallback cannot be null");
try {
mService.isOpportunisticModeEnabled(mContext.getOpPackageName(),
new IBooleanListener.Stub() {
@Override
public void onResult(boolean value) {
Binder.clearCallingIdentity();
executor.execute(() -> {
resultsCallback.accept(value);
});
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Reset all paired devices setup by the caller by
* {@link DiscoverySession#initiatePairingRequest(PeerHandle, String, int, String)} and
* {@link DiscoverySession#acceptPairingRequest(int, PeerHandle, String, int, String)}
*/
@RequiresPermission(CHANGE_WIFI_STATE)
public void resetPairedDevices() {
try {
mService.resetPairedDevices(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Remove the target paired device setup by the caller by
* {@link DiscoverySession#initiatePairingRequest(PeerHandle, String, int, String)} and
* {@link DiscoverySession#acceptPairingRequest(int, PeerHandle, String, int, String)}
* @param alias The alias set by the caller
*/
@RequiresPermission(CHANGE_WIFI_STATE)
public void removePairedDevice(@NonNull String alias) {
try {
mService.removePairedDevice(mContext.getOpPackageName(), alias);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Get all the paired devices configured by the calling app.
* @param executor The executor on which callback will be invoked.
* @param resultsCallback An asynchronous callback that will return a list of paired devices'
* alias
*/
@RequiresPermission(ACCESS_WIFI_STATE)
public void getPairedDevices(@NonNull Executor executor,
@NonNull Consumer<List<String>> 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);
}
}