Merge "Wificond: transfer implementation to android.net.wifi"
diff --git a/Android.bp b/Android.bp
index 04b4e6e..8b5bbcf3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -247,6 +247,7 @@
":framework-telephony-sources",
":framework-wifi-sources",
":framework-wifi-non-updatable-sources",
+ ":libwificond_ipc_aidl",
":PacProcessor-aidl-sources",
":ProxyHandler-aidl-sources",
@@ -371,6 +372,9 @@
"com.android.sysprop.apex",
"PlatformProperties",
],
+ aidl: {
+ include_dirs: ["system/connectivity/wificond/aidl"],
+ },
sdk_version: "core_platform",
installable: false,
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index dd54d7c..bd948ec5 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -120,6 +120,7 @@
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
+import android.net.wifi.WifiCondManager;
import android.net.wifi.WifiFrameworkInitializer;
import android.nfc.NfcManager;
import android.os.BatteryManager;
@@ -696,6 +697,14 @@
return new EthernetManager(ctx.getOuterContext(), service);
}});
+ registerService(Context.WIFI_COND_SERVICE, WifiCondManager.class,
+ new CachedServiceFetcher<WifiCondManager>() {
+ @Override
+ public WifiCondManager createService(ContextImpl ctx) {
+ return new WifiCondManager(ctx.getOuterContext());
+ }
+ });
+
registerService(Context.WINDOW_SERVICE, WindowManager.class,
new CachedServiceFetcher<WindowManager>() {
@Override
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 7b580c3..fe774f8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3973,6 +3973,17 @@
public static final String WIFI_SERVICE = "wifi";
/**
+ * Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.net.wifi.WifiCondManager} for handling management of the Wi-Fi control
+ * daemon.
+ *
+ * @see #getSystemService(String)
+ * @see android.net.wifi.WifiCondManager
+ * @hide
+ */
+ public static final String WIFI_COND_SERVICE = "wificond";
+
+ /**
* Use with {@link #getSystemService(String)} to retrieve a {@link
* android.net.wifi.p2p.WifiP2pManager} for handling management of
* Wi-Fi peer-to-peer connections.
diff --git a/wifi/java/android/net/wifi/WifiCondManager.java b/wifi/java/android/net/wifi/WifiCondManager.java
new file mode 100644
index 0000000..9ae7e3a
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiCondManager.java
@@ -0,0 +1,983 @@
+/*
+ * Copyright (C) 2017 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.net.wifi.wificond.ChannelSettings;
+import android.net.wifi.wificond.HiddenNetwork;
+import android.net.wifi.wificond.NativeScanResult;
+import android.net.wifi.wificond.NativeWifiClient;
+import android.net.wifi.wificond.PnoSettings;
+import android.net.wifi.wificond.SingleScanSettings;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class provides methods for WifiNative to send control commands to wificond.
+ * NOTE: This class should only be used from WifiNative.
+ * @hide
+ */
+public class WifiCondManager implements IBinder.DeathRecipient {
+ private static final String TAG = "WifiCondManager";
+ private boolean mVerboseLoggingEnabled = false;
+
+ /**
+ * The {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}
+ * timeout, in milliseconds, after which
+ * {@link SendMgmtFrameCallback#onFailure(int)} will be called with reason
+ * {@link #SEND_MGMT_FRAME_ERROR_TIMEOUT}.
+ */
+ public static final int SEND_MGMT_FRAME_TIMEOUT_MS = 1000;
+
+ private static final String TIMEOUT_ALARM_TAG = TAG + " Send Management Frame Timeout";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SCAN_TYPE_"},
+ value = {SCAN_TYPE_SINGLE_SCAN,
+ SCAN_TYPE_PNO_SCAN})
+ public @interface ScanResultType {}
+
+ /** Get scan results for a single scan */
+ public static final int SCAN_TYPE_SINGLE_SCAN = 0;
+
+ /** Get scan results for Pno Scan */
+ public static final int SCAN_TYPE_PNO_SCAN = 1;
+
+ private AlarmManager mAlarmManager;
+ private Handler mEventHandler;
+
+ // Cached wificond binder handlers.
+ private IWificond mWificond;
+ private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
+ private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
+ private HashMap<String, IWifiScannerImpl> mWificondScanners = new HashMap<>();
+ private HashMap<String, IScanEvent> mScanEventHandlers = new HashMap<>();
+ private HashMap<String, IPnoScanEvent> mPnoScanEventHandlers = new HashMap<>();
+ private HashMap<String, IApInterfaceEventCallback> mApInterfaceListeners = new HashMap<>();
+ private Runnable mDeathEventHandler;
+ /**
+ * Ensures that no more than one sendMgmtFrame operation runs concurrently.
+ */
+ private AtomicBoolean mSendMgmtFrameInProgress = new AtomicBoolean(false);
+
+ /**
+ * Interface for a callback to be used to handle scan results.
+ */
+ public interface ScanEventCallback {
+ /**
+ * Called when scan results are available.
+ */
+ void onScanResultReady();
+
+ /**
+ * Called when a scan has failed.
+ */
+ void onScanFailed();
+ }
+
+ /**
+ * Interface for a callback to provide information about PNO scan request.
+ */
+ public interface PnoScanRequestCallback {
+ /**
+ * Called when the PNO scan is requested.
+ */
+ void onPnoRequestSucceeded();
+
+ /**
+ * Called when a PNO scan request fails.
+ */
+ void onPnoRequestFailed();
+ }
+
+ private class ScanEventHandler extends IScanEvent.Stub {
+ private ScanEventCallback mCallback;
+
+ ScanEventHandler(@NonNull ScanEventCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void OnScanResultReady() {
+ Log.d(TAG, "Scan result ready event");
+ mCallback.onScanResultReady();
+ }
+
+ @Override
+ public void OnScanFailed() {
+ Log.d(TAG, "Scan failed event");
+ mCallback.onScanFailed();
+ }
+ }
+
+ /**
+ * Result of a signal poll.
+ */
+ public static class SignalPollResult {
+ // RSSI value in dBM.
+ public int currentRssi;
+ //Transmission bit rate in Mbps.
+ public int txBitrate;
+ // Association frequency in MHz.
+ public int associationFrequency;
+ //Last received packet bit rate in Mbps.
+ public int rxBitrate;
+ }
+
+ /**
+ * WiFi interface transimission counters.
+ */
+ public static class TxPacketCounters {
+ // Number of successfully transmitted packets.
+ public int txSucceeded;
+ // Number of tramsmission failures.
+ public int txFailed;
+ }
+
+ /**
+ * Callbacks for SoftAp interface.
+ */
+ public interface SoftApListener {
+ /**
+ * Invoked when there is some fatal failure in the lower layers.
+ */
+ void onFailure();
+
+ /**
+ * Invoked when the associated stations changes.
+ */
+ void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected);
+
+ /**
+ * Invoked when the channel switch event happens.
+ */
+ void onSoftApChannelSwitched(int frequency, int bandwidth);
+ }
+
+ /**
+ * Callback to notify the results of a
+ * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()} call.
+ * Note: no callbacks will be triggered if the iface dies while sending a frame.
+ */
+ public interface SendMgmtFrameCallback {
+ /**
+ * Called when the management frame was successfully sent and ACKed by the recipient.
+ * @param elapsedTimeMs The elapsed time between when the management frame was sent and when
+ * the ACK was processed, in milliseconds, as measured by wificond.
+ * This includes the time that the send frame spent queuing before it
+ * was sent, any firmware retries, and the time the received ACK spent
+ * queuing before it was processed.
+ */
+ void onAck(int elapsedTimeMs);
+
+ /**
+ * Called when the send failed.
+ * @param reason The error code for the failure.
+ */
+ void onFailure(@SendMgmtFrameError int reason);
+ }
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SEND_MGMT_FRAME_ERROR_"},
+ value = {SEND_MGMT_FRAME_ERROR_UNKNOWN,
+ SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED,
+ SEND_MGMT_FRAME_ERROR_NO_ACK,
+ SEND_MGMT_FRAME_ERROR_TIMEOUT,
+ SEND_MGMT_FRAME_ERROR_ALREADY_STARTED})
+ public @interface SendMgmtFrameError {}
+
+ // Send management frame error codes
+
+ /**
+ * Unknown error occurred during call to
+ * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1;
+
+ /**
+ * Specifying the MCS rate in
+ * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()} is not
+ * supported by this device.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_MCS_UNSUPPORTED = 2;
+
+ /**
+ * Driver reported that no ACK was received for the frame transmitted using
+ * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_NO_ACK = 3;
+
+ /**
+ * Error code for when the driver fails to report on the status of the frame sent by
+ * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}
+ * after {@link #SEND_MGMT_FRAME_TIMEOUT_MS} milliseconds.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_TIMEOUT = 4;
+
+ /**
+ * An existing call to
+ * {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int) sendMgmtFrame()}
+ * is in progress. Another frame cannot be sent until the first call completes.
+ */
+ public static final int SEND_MGMT_FRAME_ERROR_ALREADY_STARTED = 5;
+
+
+ public WifiCondManager(Context context) {
+ mAlarmManager = (AlarmManager) context.getSystemService(AlarmManager.class);
+ mEventHandler = new Handler(context.getMainLooper());
+ }
+
+ @VisibleForTesting
+ public WifiCondManager(Context context, IWificond wificond) {
+ this(context);
+ mWificond = wificond;
+ }
+
+ private class PnoScanEventHandler extends IPnoScanEvent.Stub {
+ private ScanEventCallback mCallback;
+
+ PnoScanEventHandler(@NonNull ScanEventCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public void OnPnoNetworkFound() {
+ Log.d(TAG, "Pno scan result event");
+ mCallback.onScanResultReady();
+ }
+
+ @Override
+ public void OnPnoScanFailed() {
+ Log.d(TAG, "Pno Scan failed event");
+ mCallback.onScanFailed();
+ }
+ }
+
+ /**
+ * Listener for AP Interface events.
+ */
+ private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub {
+ private SoftApListener mSoftApListener;
+
+ ApInterfaceEventCallback(SoftApListener listener) {
+ mSoftApListener = listener;
+ }
+
+ @Override
+ public void onConnectedClientsChanged(NativeWifiClient client, boolean isConnected) {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "onConnectedClientsChanged called with "
+ + client.macAddress + " isConnected: " + isConnected);
+ }
+
+ mSoftApListener.onConnectedClientsChanged(client, isConnected);
+ }
+
+ @Override
+ public void onSoftApChannelSwitched(int frequency, int bandwidth) {
+ mSoftApListener.onSoftApChannelSwitched(frequency, bandwidth);
+ }
+ }
+
+ /**
+ * Callback triggered by wificond.
+ */
+ private class SendMgmtFrameEvent extends ISendMgmtFrameEvent.Stub {
+ private SendMgmtFrameCallback mCallback;
+ private AlarmManager.OnAlarmListener mTimeoutCallback;
+ /**
+ * ensures that mCallback is only called once
+ */
+ private boolean mWasCalled;
+
+ private void runIfFirstCall(Runnable r) {
+ if (mWasCalled) return;
+ mWasCalled = true;
+
+ mSendMgmtFrameInProgress.set(false);
+ r.run();
+ }
+
+ SendMgmtFrameEvent(@NonNull SendMgmtFrameCallback callback) {
+ mCallback = callback;
+ // called in main thread
+ mTimeoutCallback = () -> runIfFirstCall(() -> {
+ if (mVerboseLoggingEnabled) {
+ Log.e(TAG, "Timed out waiting for ACK");
+ }
+ mCallback.onFailure(SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ });
+ mWasCalled = false;
+
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime() + SEND_MGMT_FRAME_TIMEOUT_MS,
+ TIMEOUT_ALARM_TAG, mTimeoutCallback, mEventHandler);
+ }
+
+ // called in binder thread
+ @Override
+ public void OnAck(int elapsedTimeMs) {
+ // post to main thread
+ mEventHandler.post(() -> runIfFirstCall(() -> {
+ mAlarmManager.cancel(mTimeoutCallback);
+ mCallback.onAck(elapsedTimeMs);
+ }));
+ }
+
+ // called in binder thread
+ @Override
+ public void OnFailure(int reason) {
+ // post to main thread
+ mEventHandler.post(() -> runIfFirstCall(() -> {
+ mAlarmManager.cancel(mTimeoutCallback);
+ mCallback.onFailure(reason);
+ }));
+ }
+ }
+
+ /**
+ * Called by the binder subsystem upon remote object death.
+ * Invoke all the register death handlers and clear state.
+ */
+ @Override
+ public void binderDied() {
+ mEventHandler.post(() -> {
+ Log.e(TAG, "Wificond died!");
+ clearState();
+ // Invalidate the global wificond handle on death. Will be refreshed
+ // on the next setup call.
+ mWificond = null;
+ if (mDeathEventHandler != null) {
+ mDeathEventHandler.run();
+ }
+ });
+ }
+
+ /** Enable or disable verbose logging of WificondControl.
+ * @param enable True to enable verbose logging. False to disable verbose logging.
+ */
+ public void enableVerboseLogging(boolean enable) {
+ mVerboseLoggingEnabled = enable;
+ }
+
+ /**
+ * Initializes wificond & registers a death notification for wificond.
+ * This method clears any existing state in wificond daemon.
+ *
+ * @return Returns true on success.
+ */
+ public boolean initialize(@NonNull Runnable deathEventHandler) {
+ if (mDeathEventHandler != null) {
+ Log.e(TAG, "Death handler already present");
+ }
+ mDeathEventHandler = deathEventHandler;
+ tearDownInterfaces();
+ return true;
+ }
+
+ /**
+ * Helper method to retrieve the global wificond handle and register for
+ * death notifications.
+ */
+ private boolean retrieveWificondAndRegisterForDeath() {
+ if (mWificond != null) {
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "Wificond handle already retrieved");
+ }
+ // We already have a wificond handle.
+ return true;
+ }
+ IBinder binder = ServiceManager.getService(Context.WIFI_COND_SERVICE);
+ mWificond = IWificond.Stub.asInterface(binder);
+ if (mWificond == null) {
+ Log.e(TAG, "Failed to get reference to wificond");
+ return false;
+ }
+ try {
+ mWificond.asBinder().linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to register death notification for wificond");
+ // The remote has already died.
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Setup interface for client mode via wificond.
+ * @return true on success.
+ */
+ public boolean setupInterfaceForClientMode(@NonNull String ifaceName,
+ @NonNull ScanEventCallback scanCallback, @NonNull ScanEventCallback pnoScanCallback) {
+ Log.d(TAG, "Setting up interface for client mode");
+ if (!retrieveWificondAndRegisterForDeath()) {
+ return false;
+ }
+
+ IClientInterface clientInterface = null;
+ try {
+ clientInterface = mWificond.createClientInterface(ifaceName);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to get IClientInterface due to remote exception");
+ return false;
+ }
+
+ if (clientInterface == null) {
+ Log.e(TAG, "Could not get IClientInterface instance from wificond");
+ return false;
+ }
+ Binder.allowBlocking(clientInterface.asBinder());
+
+ // Refresh Handlers
+ mClientInterfaces.put(ifaceName, clientInterface);
+ try {
+ IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();
+ if (wificondScanner == null) {
+ Log.e(TAG, "Failed to get WificondScannerImpl");
+ return false;
+ }
+ mWificondScanners.put(ifaceName, wificondScanner);
+ Binder.allowBlocking(wificondScanner.asBinder());
+ ScanEventHandler scanEventHandler = new ScanEventHandler(scanCallback);
+ mScanEventHandlers.put(ifaceName, scanEventHandler);
+ wificondScanner.subscribeScanEvents(scanEventHandler);
+ PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(pnoScanCallback);
+ mPnoScanEventHandlers.put(ifaceName, pnoScanEventHandler);
+ wificondScanner.subscribePnoScanEvents(pnoScanEventHandler);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
+ }
+
+ return true;
+ }
+
+ /**
+ * Teardown a specific STA interface configured in wificond.
+ *
+ * @return Returns true on success.
+ */
+ public boolean tearDownClientInterface(@NonNull String ifaceName) {
+ if (getClientInterface(ifaceName) == null) {
+ Log.e(TAG, "No valid wificond client interface handler");
+ return false;
+ }
+ try {
+ IWifiScannerImpl scannerImpl = mWificondScanners.get(ifaceName);
+ if (scannerImpl != null) {
+ scannerImpl.unsubscribeScanEvents();
+ scannerImpl.unsubscribePnoScanEvents();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to unsubscribe wificond scanner due to remote exception");
+ return false;
+ }
+
+ if (mWificond == null) {
+ Log.e(TAG, "Reference to wifiCond is null");
+ return false;
+ }
+
+ boolean success;
+ try {
+ success = mWificond.tearDownClientInterface(ifaceName);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to teardown client interface due to remote exception");
+ return false;
+ }
+ if (!success) {
+ Log.e(TAG, "Failed to teardown client interface");
+ return false;
+ }
+
+ mClientInterfaces.remove(ifaceName);
+ mWificondScanners.remove(ifaceName);
+ mScanEventHandlers.remove(ifaceName);
+ mPnoScanEventHandlers.remove(ifaceName);
+ return true;
+ }
+
+ /**
+ * Setup interface for softAp mode via wificond.
+ * @return true on success.
+ */
+ public boolean setupInterfaceForSoftApMode(@NonNull String ifaceName) {
+ Log.d(TAG, "Setting up interface for soft ap mode");
+ if (!retrieveWificondAndRegisterForDeath()) {
+ return false;
+ }
+
+ IApInterface apInterface = null;
+ try {
+ apInterface = mWificond.createApInterface(ifaceName);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to get IApInterface due to remote exception");
+ return false;
+ }
+
+ if (apInterface == null) {
+ Log.e(TAG, "Could not get IApInterface instance from wificond");
+ return false;
+ }
+ Binder.allowBlocking(apInterface.asBinder());
+
+ // Refresh Handlers
+ mApInterfaces.put(ifaceName, apInterface);
+ return true;
+ }
+
+ /**
+ * Teardown a specific AP interface configured in wificond.
+ *
+ * @return Returns true on success.
+ */
+ public boolean tearDownSoftApInterface(@NonNull String ifaceName) {
+ if (getApInterface(ifaceName) == null) {
+ Log.e(TAG, "No valid wificond ap interface handler");
+ return false;
+ }
+
+ if (mWificond == null) {
+ Log.e(TAG, "Reference to wifiCond is null");
+ return false;
+ }
+
+ boolean success;
+ try {
+ success = mWificond.tearDownApInterface(ifaceName);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to teardown AP interface due to remote exception");
+ return false;
+ }
+ if (!success) {
+ Log.e(TAG, "Failed to teardown AP interface");
+ return false;
+ }
+ mApInterfaces.remove(ifaceName);
+ mApInterfaceListeners.remove(ifaceName);
+ return true;
+ }
+
+ /**
+ * Teardown all interfaces configured in wificond.
+ * @return Returns true on success.
+ */
+ public boolean tearDownInterfaces() {
+ Log.d(TAG, "tearing down interfaces in wificond");
+ // Explicitly refresh the wificodn handler because |tearDownInterfaces()|
+ // could be used to cleanup before we setup any interfaces.
+ if (!retrieveWificondAndRegisterForDeath()) {
+ return false;
+ }
+
+ try {
+ for (Map.Entry<String, IWifiScannerImpl> entry : mWificondScanners.entrySet()) {
+ entry.getValue().unsubscribeScanEvents();
+ entry.getValue().unsubscribePnoScanEvents();
+ }
+ mWificond.tearDownInterfaces();
+ clearState();
+ return true;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to tear down interfaces due to remote exception");
+ }
+
+ return false;
+ }
+
+ /** Helper function to look up the interface handle using name */
+ private IClientInterface getClientInterface(@NonNull String ifaceName) {
+ return mClientInterfaces.get(ifaceName);
+ }
+
+ /**
+ * Request signal polling to wificond.
+ * @param ifaceName Name of the interface.
+ * Returns an SignalPollResult object.
+ * Returns null on failure.
+ */
+ public SignalPollResult signalPoll(@NonNull String ifaceName) {
+ IClientInterface iface = getClientInterface(ifaceName);
+ if (iface == null) {
+ Log.e(TAG, "No valid wificond client interface handler");
+ return null;
+ }
+
+ int[] resultArray;
+ try {
+ resultArray = iface.signalPoll();
+ if (resultArray == null || resultArray.length != 4) {
+ Log.e(TAG, "Invalid signal poll result from wificond");
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to do signal polling due to remote exception");
+ return null;
+ }
+ SignalPollResult pollResult = new SignalPollResult();
+ pollResult.currentRssi = resultArray[0];
+ pollResult.txBitrate = resultArray[1];
+ pollResult.associationFrequency = resultArray[2];
+ pollResult.rxBitrate = resultArray[3];
+ return pollResult;
+ }
+
+ /**
+ * Fetch TX packet counters on current connection from wificond.
+ * @param ifaceName Name of the interface.
+ * Returns an TxPacketCounters object.
+ * Returns null on failure.
+ */
+ public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) {
+ IClientInterface iface = getClientInterface(ifaceName);
+ if (iface == null) {
+ Log.e(TAG, "No valid wificond client interface handler");
+ return null;
+ }
+
+ int[] resultArray;
+ try {
+ resultArray = iface.getPacketCounters();
+ if (resultArray == null || resultArray.length != 2) {
+ Log.e(TAG, "Invalid signal poll result from wificond");
+ return null;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to do signal polling due to remote exception");
+ return null;
+ }
+ TxPacketCounters counters = new TxPacketCounters();
+ counters.txSucceeded = resultArray[0];
+ counters.txFailed = resultArray[1];
+ return counters;
+ }
+
+ /** Helper function to look up the scanner impl handle using name */
+ private IWifiScannerImpl getScannerImpl(@NonNull String ifaceName) {
+ return mWificondScanners.get(ifaceName);
+ }
+
+ /**
+ * Fetch the latest scan result from kernel via wificond.
+ * @param ifaceName Name of the interface.
+ * @return Returns an array of native scan results or an empty array on failure.
+ */
+ @NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName,
+ @ScanResultType int scanType) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return new ArrayList<>();
+ }
+ List<NativeScanResult> results = null;
+ try {
+ if (scanType == SCAN_TYPE_SINGLE_SCAN) {
+ results = Arrays.asList(scannerImpl.getScanResults());
+ } else {
+ results = Arrays.asList(scannerImpl.getPnoScanResults());
+ }
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to create ScanDetail ArrayList");
+ }
+ if (results == null) {
+ results = new ArrayList<>();
+ }
+ if (mVerboseLoggingEnabled) {
+ Log.d(TAG, "get " + results.size() + " scan results from wificond");
+ }
+
+ return results;
+ }
+
+ /**
+ * Return scan type for the parcelable {@link SingleScanSettings}
+ */
+ private static int getScanType(@WifiScanner.ScanType int scanType) {
+ switch (scanType) {
+ case WifiScanner.SCAN_TYPE_LOW_LATENCY:
+ return IWifiScannerImpl.SCAN_TYPE_LOW_SPAN;
+ case WifiScanner.SCAN_TYPE_LOW_POWER:
+ return IWifiScannerImpl.SCAN_TYPE_LOW_POWER;
+ case WifiScanner.SCAN_TYPE_HIGH_ACCURACY:
+ return IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ default:
+ throw new IllegalArgumentException("Invalid scan type " + scanType);
+ }
+ }
+
+ /**
+ * Start a scan using wificond for the given parameters.
+ * @param ifaceName Name of the interface.
+ * @param scanType Type of scan to perform.
+ * @param freqs list of frequencies to scan for, if null scan all supported channels.
+ * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
+ * @return Returns true on success.
+ */
+ public boolean scan(@NonNull String ifaceName, @WifiScanner.ScanType int scanType,
+ Set<Integer> freqs, List<byte[]> hiddenNetworkSSIDs) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return false;
+ }
+ SingleScanSettings settings = new SingleScanSettings();
+ try {
+ settings.scanType = getScanType(scanType);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "Invalid scan type ", e);
+ return false;
+ }
+ settings.channelSettings = new ArrayList<>();
+ settings.hiddenNetworks = new ArrayList<>();
+
+ if (freqs != null) {
+ for (Integer freq : freqs) {
+ ChannelSettings channel = new ChannelSettings();
+ channel.frequency = freq;
+ settings.channelSettings.add(channel);
+ }
+ }
+ if (hiddenNetworkSSIDs != null) {
+ for (byte[] ssid : hiddenNetworkSSIDs) {
+ HiddenNetwork network = new HiddenNetwork();
+ network.ssid = ssid;
+
+ // settings.hiddenNetworks is expected to be very small, so this shouldn't cause
+ // any performance issues.
+ if (!settings.hiddenNetworks.contains(network)) {
+ settings.hiddenNetworks.add(network);
+ }
+ }
+ }
+
+ try {
+ return scannerImpl.scan(settings);
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to request scan due to remote exception");
+ }
+ return false;
+ }
+
+ /**
+ * Start PNO scan.
+ * @param ifaceName Name of the interface.
+ * @param pnoSettings Pno scan configuration.
+ * @return true on success.
+ */
+ public boolean startPnoScan(@NonNull String ifaceName, PnoSettings pnoSettings,
+ PnoScanRequestCallback callback) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return false;
+ }
+
+ try {
+ boolean success = scannerImpl.startPnoScan(pnoSettings);
+ if (success) {
+ callback.onPnoRequestSucceeded();
+ } else {
+ callback.onPnoRequestFailed();
+ }
+ return success;
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to start pno scan due to remote exception");
+ }
+ return false;
+ }
+
+ /**
+ * Stop PNO scan.
+ * @param ifaceName Name of the interface.
+ * @return true on success.
+ */
+ public boolean stopPnoScan(@NonNull String ifaceName) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return false;
+ }
+ try {
+ return scannerImpl.stopPnoScan();
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to stop pno scan due to remote exception");
+ }
+ return false;
+ }
+
+ /**
+ * Abort ongoing single scan.
+ * @param ifaceName Name of the interface.
+ */
+ public void abortScan(@NonNull String ifaceName) {
+ IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+ if (scannerImpl == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return;
+ }
+ try {
+ scannerImpl.abortScan();
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to request abortScan due to remote exception");
+ }
+ }
+
+ /**
+ * Query the list of valid frequencies for the provided band.
+ * The result depends on the on the country code that has been set.
+ *
+ * @param band as specified by one of the WifiScanner.WIFI_BAND_* constants.
+ * The following bands are supported {@link @WifiScanner.WifiBandBasic}:
+ * WifiScanner.WIFI_BAND_24_GHZ
+ * WifiScanner.WIFI_BAND_5_GHZ
+ * WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY
+ * WifiScanner.WIFI_BAND_6_GHZ
+ * @return frequencies vector of valid frequencies (MHz), or null for error.
+ * @throws IllegalArgumentException if band is not recognized.
+ */
+ public int [] getChannelsForBand(@WifiScanner.WifiBandBasic int band) {
+ if (mWificond == null) {
+ Log.e(TAG, "No valid wificond scanner interface handler");
+ return null;
+ }
+ try {
+ switch (band) {
+ case WifiScanner.WIFI_BAND_24_GHZ:
+ return mWificond.getAvailable2gChannels();
+ case WifiScanner.WIFI_BAND_5_GHZ:
+ return mWificond.getAvailable5gNonDFSChannels();
+ case WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY:
+ return mWificond.getAvailableDFSChannels();
+ case WifiScanner.WIFI_BAND_6_GHZ:
+ return mWificond.getAvailable6gChannels();
+ default:
+ throw new IllegalArgumentException("unsupported band " + band);
+ }
+ } catch (RemoteException e1) {
+ Log.e(TAG, "Failed to request getChannelsForBand due to remote exception");
+ }
+ return null;
+ }
+
+ /** Helper function to look up the interface handle using name */
+ private IApInterface getApInterface(@NonNull String ifaceName) {
+ return mApInterfaces.get(ifaceName);
+ }
+
+ /**
+ * Register the provided listener for SoftAp events.
+ *
+ * @param ifaceName Name of the interface.
+ * @param listener Callback for AP events.
+ * @return true on success, false otherwise.
+ */
+ public boolean registerApListener(@NonNull String ifaceName, SoftApListener listener) {
+ IApInterface iface = getApInterface(ifaceName);
+ if (iface == null) {
+ Log.e(TAG, "No valid ap interface handler");
+ return false;
+ }
+ try {
+ IApInterfaceEventCallback callback = new ApInterfaceEventCallback(listener);
+ mApInterfaceListeners.put(ifaceName, callback);
+ boolean success = iface.registerCallback(callback);
+ if (!success) {
+ Log.e(TAG, "Failed to register ap callback.");
+ return false;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception in registering AP callback: " + e);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * See {@link #sendMgmtFrame(String, byte[], SendMgmtFrameCallback, int)}
+ */
+ public void sendMgmtFrame(@NonNull String ifaceName, @NonNull byte[] frame,
+ @NonNull SendMgmtFrameCallback callback, int mcs) {
+
+ if (callback == null) {
+ Log.e(TAG, "callback cannot be null!");
+ return;
+ }
+
+ if (frame == null) {
+ Log.e(TAG, "frame cannot be null!");
+ callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ return;
+ }
+
+ // TODO (b/112029045) validate mcs
+ IClientInterface clientInterface = getClientInterface(ifaceName);
+ if (clientInterface == null) {
+ Log.e(TAG, "No valid wificond client interface handler");
+ callback.onFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ return;
+ }
+
+ if (!mSendMgmtFrameInProgress.compareAndSet(false, true)) {
+ Log.e(TAG, "An existing management frame transmission is in progress!");
+ callback.onFailure(SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
+ return;
+ }
+
+ SendMgmtFrameEvent sendMgmtFrameEvent = new SendMgmtFrameEvent(callback);
+ try {
+ clientInterface.SendMgmtFrame(frame, sendMgmtFrameEvent, mcs);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception while starting link probe: " + e);
+ // Call sendMgmtFrameEvent.OnFailure() instead of callback.onFailure() so that
+ // sendMgmtFrameEvent can clean up internal state, such as cancelling the timer.
+ sendMgmtFrameEvent.OnFailure(SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ }
+ }
+
+ /**
+ * Clear all internal handles.
+ */
+ private void clearState() {
+ // Refresh handlers
+ mClientInterfaces.clear();
+ mWificondScanners.clear();
+ mPnoScanEventHandlers.clear();
+ mScanEventHandlers.clear();
+ mApInterfaces.clear();
+ mApInterfaceListeners.clear();
+ mSendMgmtFrameInProgress.set(false);
+ }
+}
diff --git a/wifi/java/android/net/wifi/wificond/ChannelSettings.java b/wifi/java/android/net/wifi/wificond/ChannelSettings.java
new file mode 100644
index 0000000..c2d65b5
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/ChannelSettings.java
@@ -0,0 +1,95 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * ChannelSettings for wificond
+ *
+ * @hide
+ */
+public class ChannelSettings implements Parcelable {
+ private static final String TAG = "ChannelSettings";
+
+ public int frequency;
+
+ /** public constructor */
+ public ChannelSettings() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof ChannelSettings)) {
+ return false;
+ }
+ ChannelSettings channel = (ChannelSettings) rhs;
+ if (channel == null) {
+ return false;
+ }
+ return frequency == channel.frequency;
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(frequency);
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flags| is ignored.
+ **/
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(frequency);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<ChannelSettings> CREATOR =
+ new Parcelable.Creator<ChannelSettings>() {
+ /**
+ * Caller is responsible for providing a valid parcel.
+ */
+ @Override
+ public ChannelSettings createFromParcel(Parcel in) {
+ ChannelSettings result = new ChannelSettings();
+ result.frequency = in.readInt();
+ if (in.dataAvail() != 0) {
+ Log.e(TAG, "Found trailing data after parcel parsing.");
+ }
+
+ return result;
+ }
+
+ @Override
+ public ChannelSettings[] newArray(int size) {
+ return new ChannelSettings[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/HiddenNetwork.java b/wifi/java/android/net/wifi/wificond/HiddenNetwork.java
new file mode 100644
index 0000000..38dacea
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/HiddenNetwork.java
@@ -0,0 +1,87 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * HiddenNetwork for wificond
+ *
+ * @hide
+ */
+public class HiddenNetwork implements Parcelable {
+ private static final String TAG = "HiddenNetwork";
+
+ public byte[] ssid;
+
+ /** public constructor */
+ public HiddenNetwork() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof HiddenNetwork)) {
+ return false;
+ }
+ HiddenNetwork network = (HiddenNetwork) rhs;
+ return Arrays.equals(ssid, network.ssid);
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(ssid);
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flags| is ignored.
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeByteArray(ssid);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<HiddenNetwork> CREATOR =
+ new Parcelable.Creator<HiddenNetwork>() {
+ /**
+ * Caller is responsible for providing a valid parcel.
+ */
+ @Override
+ public HiddenNetwork createFromParcel(Parcel in) {
+ HiddenNetwork result = new HiddenNetwork();
+ result.ssid = in.createByteArray();
+ return result;
+ }
+
+ @Override
+ public HiddenNetwork[] newArray(int size) {
+ return new HiddenNetwork[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
new file mode 100644
index 0000000..ff8e935
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
@@ -0,0 +1,115 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+
+/**
+ * ScanResult from wificond
+ *
+ * @hide
+ */
+public class NativeScanResult implements Parcelable {
+ private static final int CAPABILITY_SIZE = 16;
+
+ public byte[] ssid;
+ public byte[] bssid;
+ public byte[] infoElement;
+ public int frequency;
+ public int signalMbm;
+ public long tsf;
+ public BitSet capability;
+ public boolean associated;
+ public List<RadioChainInfo> radioChainInfos;
+
+ /** public constructor */
+ public NativeScanResult() { }
+
+ /** copy constructor */
+ public NativeScanResult(NativeScanResult source) {
+ ssid = source.ssid.clone();
+ bssid = source.bssid.clone();
+ infoElement = source.infoElement.clone();
+ frequency = source.frequency;
+ signalMbm = source.signalMbm;
+ tsf = source.tsf;
+ capability = (BitSet) source.capability.clone();
+ associated = source.associated;
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeByteArray(ssid);
+ out.writeByteArray(bssid);
+ out.writeByteArray(infoElement);
+ out.writeInt(frequency);
+ out.writeInt(signalMbm);
+ out.writeLong(tsf);
+ int capabilityInt = 0;
+ for (int i = 0; i < CAPABILITY_SIZE; i++) {
+ if (capability.get(i)) {
+ capabilityInt |= 1 << i;
+ }
+ }
+ out.writeInt(capabilityInt);
+ out.writeInt(associated ? 1 : 0);
+ out.writeTypedList(radioChainInfos);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<NativeScanResult> CREATOR =
+ new Parcelable.Creator<NativeScanResult>() {
+ @Override
+ public NativeScanResult createFromParcel(Parcel in) {
+ NativeScanResult result = new NativeScanResult();
+ result.ssid = in.createByteArray();
+ result.bssid = in.createByteArray();
+ result.infoElement = in.createByteArray();
+ result.frequency = in.readInt();
+ result.signalMbm = in.readInt();
+ result.tsf = in.readLong();
+ int capabilityInt = in.readInt();
+ result.capability = new BitSet(CAPABILITY_SIZE);
+ for (int i = 0; i < CAPABILITY_SIZE; i++) {
+ if ((capabilityInt & (1 << i)) != 0) {
+ result.capability.set(i);
+ }
+ }
+ result.associated = (in.readInt() != 0);
+ result.radioChainInfos = new ArrayList<>();
+ in.readTypedList(result.radioChainInfos, RadioChainInfo.CREATOR);
+ return result;
+ }
+
+ @Override
+ public NativeScanResult[] newArray(int size) {
+ return new NativeScanResult[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/NativeWifiClient.java b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
new file mode 100644
index 0000000..4994ebd
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/NativeWifiClient.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+
+/**
+ * NativeWifiClient for wificond
+ *
+ * @hide
+ */
+public class NativeWifiClient implements Parcelable {
+ public byte[] macAddress;
+
+ /** public constructor */
+ public NativeWifiClient() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof NativeWifiClient)) {
+ return false;
+ }
+ NativeWifiClient other = (NativeWifiClient) rhs;
+ return Arrays.equals(macAddress, other.macAddress);
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(macAddress);
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flag| is ignored.
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeByteArray(macAddress);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<NativeWifiClient> CREATOR =
+ new Parcelable.Creator<NativeWifiClient>() {
+ @Override
+ public NativeWifiClient createFromParcel(Parcel in) {
+ NativeWifiClient result = new NativeWifiClient();
+ result.macAddress = in.createByteArray();
+ return result;
+ }
+
+ @Override
+ public NativeWifiClient[] newArray(int size) {
+ return new NativeWifiClient[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/PnoNetwork.java b/wifi/java/android/net/wifi/wificond/PnoNetwork.java
new file mode 100644
index 0000000..f923fd3
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/PnoNetwork.java
@@ -0,0 +1,95 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * PnoNetwork for wificond
+ *
+ * @hide
+ */
+public class PnoNetwork implements Parcelable {
+
+ public boolean isHidden;
+ public byte[] ssid;
+ public int[] frequencies;
+
+ /** public constructor */
+ public PnoNetwork() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof PnoNetwork)) {
+ return false;
+ }
+ PnoNetwork network = (PnoNetwork) rhs;
+ return Arrays.equals(ssid, network.ssid)
+ && Arrays.equals(frequencies, network.frequencies)
+ && isHidden == network.isHidden;
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ isHidden,
+ Arrays.hashCode(ssid),
+ Arrays.hashCode(frequencies));
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flag| is ignored.
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(isHidden ? 1 : 0);
+ out.writeByteArray(ssid);
+ out.writeIntArray(frequencies);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<PnoNetwork> CREATOR =
+ new Parcelable.Creator<PnoNetwork>() {
+ @Override
+ public PnoNetwork createFromParcel(Parcel in) {
+ PnoNetwork result = new PnoNetwork();
+ result.isHidden = in.readInt() != 0 ? true : false;
+ result.ssid = in.createByteArray();
+ result.frequencies = in.createIntArray();
+ return result;
+ }
+
+ @Override
+ public PnoNetwork[] newArray(int size) {
+ return new PnoNetwork[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/PnoSettings.java b/wifi/java/android/net/wifi/wificond/PnoSettings.java
new file mode 100644
index 0000000..96cf24f
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/PnoSettings.java
@@ -0,0 +1,105 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * PnoSettings for wificond
+ *
+ * @hide
+ */
+public class PnoSettings implements Parcelable {
+ public int intervalMs;
+ public int min2gRssi;
+ public int min5gRssi;
+ public int min6gRssi;
+ public ArrayList<PnoNetwork> pnoNetworks;
+
+ /** public constructor */
+ public PnoSettings() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof PnoSettings)) {
+ return false;
+ }
+ PnoSettings settings = (PnoSettings) rhs;
+ if (settings == null) {
+ return false;
+ }
+ return intervalMs == settings.intervalMs
+ && min2gRssi == settings.min2gRssi
+ && min5gRssi == settings.min5gRssi
+ && min6gRssi == settings.min6gRssi
+ && pnoNetworks.equals(settings.pnoNetworks);
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(intervalMs, min2gRssi, min5gRssi, min6gRssi, pnoNetworks);
+ }
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flag| is ignored.
+ **/
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(intervalMs);
+ out.writeInt(min2gRssi);
+ out.writeInt(min5gRssi);
+ out.writeInt(min6gRssi);
+ out.writeTypedList(pnoNetworks);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<PnoSettings> CREATOR =
+ new Parcelable.Creator<PnoSettings>() {
+ @Override
+ public PnoSettings createFromParcel(Parcel in) {
+ PnoSettings result = new PnoSettings();
+ result.intervalMs = in.readInt();
+ result.min2gRssi = in.readInt();
+ result.min5gRssi = in.readInt();
+ result.min6gRssi = in.readInt();
+
+ result.pnoNetworks = new ArrayList<PnoNetwork>();
+ in.readTypedList(result.pnoNetworks, PnoNetwork.CREATOR);
+
+ return result;
+ }
+
+ @Override
+ public PnoSettings[] newArray(int size) {
+ return new PnoSettings[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/RadioChainInfo.java b/wifi/java/android/net/wifi/wificond/RadioChainInfo.java
new file mode 100644
index 0000000..2b03450
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/RadioChainInfo.java
@@ -0,0 +1,100 @@
+/*
+ * 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.wificond;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * RadioChainInfo for wificond
+ *
+ * @hide
+ */
+public class RadioChainInfo implements Parcelable {
+ private static final String TAG = "RadioChainInfo";
+
+ public int chainId;
+ public int level;
+
+
+ /** public constructor */
+ public RadioChainInfo() { }
+
+ public RadioChainInfo(int chainId, int level) {
+ this.chainId = chainId;
+ this.level = level;
+ }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof RadioChainInfo)) {
+ return false;
+ }
+ RadioChainInfo chainInfo = (RadioChainInfo) rhs;
+ if (chainInfo == null) {
+ return false;
+ }
+ return chainId == chainInfo.chainId && level == chainInfo.level;
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(chainId, level);
+ }
+
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flags| is ignored.
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(chainId);
+ out.writeInt(level);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<RadioChainInfo> CREATOR =
+ new Parcelable.Creator<RadioChainInfo>() {
+ /**
+ * Caller is responsible for providing a valid parcel.
+ */
+ @Override
+ public RadioChainInfo createFromParcel(Parcel in) {
+ RadioChainInfo result = new RadioChainInfo();
+ result.chainId = in.readInt();
+ result.level = in.readInt();
+ return result;
+ }
+
+ @Override
+ public RadioChainInfo[] newArray(int size) {
+ return new RadioChainInfo[size];
+ }
+ };
+}
diff --git a/wifi/java/android/net/wifi/wificond/SingleScanSettings.java b/wifi/java/android/net/wifi/wificond/SingleScanSettings.java
new file mode 100644
index 0000000..8065c01
--- /dev/null
+++ b/wifi/java/android/net/wifi/wificond/SingleScanSettings.java
@@ -0,0 +1,119 @@
+/*
+ * 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.wificond;
+
+import android.net.wifi.IWifiScannerImpl;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * SingleScanSettings for wificond
+ *
+ * @hide
+ */
+public class SingleScanSettings implements Parcelable {
+ private static final String TAG = "SingleScanSettings";
+
+ public int scanType;
+ public ArrayList<ChannelSettings> channelSettings;
+ public ArrayList<HiddenNetwork> hiddenNetworks;
+
+ /** public constructor */
+ public SingleScanSettings() { }
+
+ /** override comparator */
+ @Override
+ public boolean equals(Object rhs) {
+ if (this == rhs) return true;
+ if (!(rhs instanceof SingleScanSettings)) {
+ return false;
+ }
+ SingleScanSettings settings = (SingleScanSettings) rhs;
+ if (settings == null) {
+ return false;
+ }
+ return scanType == settings.scanType
+ && channelSettings.equals(settings.channelSettings)
+ && hiddenNetworks.equals(settings.hiddenNetworks);
+ }
+
+ /** override hash code */
+ @Override
+ public int hashCode() {
+ return Objects.hash(scanType, channelSettings, hiddenNetworks);
+ }
+
+
+ /** implement Parcelable interface */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private static boolean isValidScanType(int scanType) {
+ return scanType == IWifiScannerImpl.SCAN_TYPE_LOW_SPAN
+ || scanType == IWifiScannerImpl.SCAN_TYPE_LOW_POWER
+ || scanType == IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ }
+
+ /**
+ * implement Parcelable interface
+ * |flags| is ignored.
+ */
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (!isValidScanType(scanType)) {
+ Log.wtf(TAG, "Invalid scan type " + scanType);
+ }
+ out.writeInt(scanType);
+ out.writeTypedList(channelSettings);
+ out.writeTypedList(hiddenNetworks);
+ }
+
+ /** implement Parcelable interface */
+ public static final Parcelable.Creator<SingleScanSettings> CREATOR =
+ new Parcelable.Creator<SingleScanSettings>() {
+ /**
+ * Caller is responsible for providing a valid parcel.
+ */
+ @Override
+ public SingleScanSettings createFromParcel(Parcel in) {
+ SingleScanSettings result = new SingleScanSettings();
+ result.scanType = in.readInt();
+ if (!isValidScanType(result.scanType)) {
+ Log.wtf(TAG, "Invalid scan type " + result.scanType);
+ }
+ result.channelSettings = new ArrayList<ChannelSettings>();
+ in.readTypedList(result.channelSettings, ChannelSettings.CREATOR);
+ result.hiddenNetworks = new ArrayList<HiddenNetwork>();
+ in.readTypedList(result.hiddenNetworks, HiddenNetwork.CREATOR);
+ if (in.dataAvail() != 0) {
+ Log.e(TAG, "Found trailing data after parcel parsing.");
+ }
+ return result;
+ }
+
+ @Override
+ public SingleScanSettings[] newArray(int size) {
+ return new SingleScanSettings[size];
+ }
+ };
+}
diff --git a/wifi/tests/src/android/net/wifi/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/WifiCondManagerTest.java
new file mode 100644
index 0000000..48a9afa
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiCondManagerTest.java
@@ -0,0 +1,1281 @@
+/*
+ * Copyright (C) 2017 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.net.wifi.util.HexEncoding;
+import android.net.wifi.wificond.ChannelSettings;
+import android.net.wifi.wificond.HiddenNetwork;
+import android.net.wifi.wificond.NativeWifiClient;
+import android.net.wifi.wificond.PnoNetwork;
+import android.net.wifi.wificond.PnoSettings;
+import android.net.wifi.wificond.SingleScanSettings;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.AdditionalMatchers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link android.net.wifi.WifiCondManager}.
+ */
+@SmallTest
+public class WifiCondManagerTest {
+ @Mock
+ private IWificond mWificond;
+ @Mock
+ private IBinder mWifiCondBinder;
+ @Mock
+ private IClientInterface mClientInterface;
+ @Mock
+ private IWifiScannerImpl mWifiScannerImpl;
+ @Mock
+ private IApInterface mApInterface;
+ @Mock
+ private WifiCondManager.SoftApListener mSoftApListener;
+ @Mock
+ private WifiCondManager.SendMgmtFrameCallback mSendMgmtFrameCallback;
+ @Mock
+ private WifiCondManager.ScanEventCallback mNormalScanCallback;
+ @Mock
+ private WifiCondManager.ScanEventCallback mPnoScanCallback;
+ @Mock
+ private WifiCondManager.PnoScanRequestCallback mPnoScanRequestCallback;
+ @Mock
+ private Context mContext;
+ private TestLooper mLooper;
+ private TestAlarmManager mTestAlarmManager;
+ private AlarmManager mAlarmManager;
+ private WifiCondManager mWificondControl;
+ private static final String TEST_INTERFACE_NAME = "test_wlan_if";
+ private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
+ private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
+ private static final byte[] TEST_SSID =
+ new byte[]{'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_PSK =
+ new byte[]{'T', 'e', 's', 't'};
+
+ private static final Set<Integer> SCAN_FREQ_SET =
+ new HashSet<Integer>() {{
+ add(2410);
+ add(2450);
+ add(5050);
+ add(5200);
+ }};
+ private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
+ private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
+ private static final int[] TEST_FREQUENCIES_1 = {};
+ private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+
+ private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
+ new ArrayList<byte[]>() {{
+ add(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
+ add(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
+ }};
+
+ private static final PnoSettings TEST_PNO_SETTINGS =
+ new PnoSettings() {{
+ intervalMs = 6000;
+ pnoNetworks = new ArrayList<>();
+ PnoNetwork network = new PnoNetwork();
+ network.ssid = LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1));
+ network.isHidden = true;
+ network.frequencies = TEST_FREQUENCIES_1;
+ pnoNetworks.add(network);
+ network.ssid = LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2));
+ network.isHidden = false;
+ network.frequencies = TEST_FREQUENCIES_2;
+ pnoNetworks.add(network);
+ }};
+
+ private static final int TEST_MCS_RATE = 5;
+ private static final int TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS = 100;
+ private static final byte[] TEST_PROBE_FRAME = {
+ 0x40, 0x00, 0x3c, 0x00, (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b,
+ 0x33, 0x72, (byte) 0xf4, (byte) 0xf5, (byte) 0xe8, 0x51, (byte) 0x9e, 0x09,
+ (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b, 0x33, 0x72, (byte) 0xb0, 0x66,
+ 0x00, 0x00
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ // Setup mocks for successful WificondControl operation. Failure case mocks should be
+ // created in specific tests
+ MockitoAnnotations.initMocks(this);
+
+ mTestAlarmManager = new TestAlarmManager();
+ mAlarmManager = mTestAlarmManager.getAlarmManager();
+ when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn(Context.ALARM_SERVICE);
+ when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
+ mLooper = new TestLooper();
+ when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+
+ when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
+ when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+ when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
+ when(mWificond.createApInterface(any())).thenReturn(mApInterface);
+ when(mWificond.tearDownClientInterface(any())).thenReturn(true);
+ when(mWificond.tearDownApInterface(any())).thenReturn(true);
+ when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+ when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
+ mWificondControl = new WifiCondManager(mContext, mWificond);
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ mNormalScanCallback, mPnoScanCallback));
+ }
+
+ /**
+ * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testSetupInterfaceForClientMode() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+ verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls subscribeScanEvents().
+ */
+ @Test
+ public void testSetupInterfaceForClientModeCallsScanEventSubscripiton() throws Exception {
+ verify(mWifiScannerImpl).subscribeScanEvents(any(IScanEvent.class));
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterface() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+ assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+ verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterfaceOnInvalidIface() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+ assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME1));
+ verify(mWifiScannerImpl, never()).unsubscribeScanEvents();
+ verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+ verify(mWificond, never()).tearDownClientInterface(any());
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterfaceFailDueToExceptionScannerUnsubscribe() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+ doThrow(new RemoteException()).when(mWifiScannerImpl).unsubscribeScanEvents();
+
+ assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+ verify(mWificond, never()).tearDownClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterfaceErrorWhenWificondFailed() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+ assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+ verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that the client handles are cleared after teardown.
+ */
+ @Test
+ public void testTeardownClientInterfaceClearsHandles() throws Exception {
+ testTeardownClientInterface();
+
+ assertNull(mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+ verify(mClientInterface, never()).signalPoll();
+
+ assertFalse(mWificondControl.scan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl, never()).scan(any());
+ }
+
+ /**
+ * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) calls wificond.
+ */
+ @Test
+ public void testSetupInterfaceForSoftApMode() throws Exception {
+ when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(mApInterface);
+
+ assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+ verify(mWificond).createApInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that setupInterfaceForSoftAp() returns null when wificond is not started.
+ */
+ @Test
+ public void testSetupInterfaceForSoftApModeErrorWhenWificondIsNotStarted() throws Exception {
+ // Invoke wificond death handler to clear the handle.
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+
+ assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) returns null when wificond
+ * failed to setup AP interface.
+ */
+ @Test
+ public void testSetupInterfaceForSoftApModeErrorWhenWificondFailedToSetupInterface()
+ throws Exception {
+ when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(null);
+
+ assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownSoftApInterface() throws Exception {
+ testSetupInterfaceForSoftApMode();
+ when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+ assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+ verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that tearDownSoftapInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownSoftApInterfaceOnInvalidIface() throws Exception {
+ testSetupInterfaceForSoftApMode();
+ when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+ assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+ verify(mWificond, never()).tearDownApInterface(any());
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownSoftApInterfaceErrorWhenWificondFailed() throws Exception {
+ testSetupInterfaceForSoftApMode();
+ when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+ assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+ verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that the SoftAp handles are cleared after teardown.
+ */
+ @Test
+ public void testTeardownSoftApInterfaceClearsHandles() throws Exception {
+ testTeardownSoftApInterface();
+
+ assertFalse(mWificondControl.registerApListener(
+ TEST_INTERFACE_NAME, mSoftApListener));
+ verify(mApInterface, never()).registerCallback(any());
+ }
+
+ /**
+ * Verifies that we can setup concurrent interfaces.
+ */
+ @Test
+ public void testSetupMultipleInterfaces() throws Exception {
+ when(mWificond.createApInterface(TEST_INTERFACE_NAME1)).thenReturn(mApInterface);
+
+ assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME1));
+
+ verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+ verify(mWificond).createApInterface(TEST_INTERFACE_NAME1);
+ }
+
+ /**
+ * Verifies that we can setup concurrent interfaces.
+ */
+ @Test
+ public void testTeardownMultipleInterfaces() throws Exception {
+ testSetupMultipleInterfaces();
+ assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+
+ verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+ verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME1);
+ }
+
+ /**
+ * Verifies that tearDownInterfaces() calls wificond.
+ */
+ @Test
+ public void testTearDownInterfaces() throws Exception {
+ assertTrue(mWificondControl.tearDownInterfaces());
+ verify(mWificond).tearDownInterfaces();
+ }
+
+ /**
+ * Verifies that tearDownInterfaces() calls unsubscribeScanEvents() when there was
+ * a configured client interface.
+ */
+ @Test
+ public void testTearDownInterfacesRemovesScanEventSubscription() throws Exception {
+ assertTrue(mWificondControl.tearDownInterfaces());
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ }
+
+
+ /**
+ * Verifies that tearDownInterfaces() returns false when wificond is not started.
+ */
+ @Test
+ public void testTearDownInterfacesErrorWhenWificondIsNotStarterd() throws Exception {
+ // Invoke wificond death handler to clear the handle.
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+ assertFalse(mWificondControl.tearDownInterfaces());
+ }
+
+ /**
+ * Verifies that signalPoll() calls wificond.
+ */
+ @Test
+ public void testSignalPoll() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, mNormalScanCallback,
+ mPnoScanCallback);
+ mWificondControl.signalPoll(TEST_INTERFACE_NAME);
+ verify(mClientInterface).signalPoll();
+ }
+
+ /**
+ * Verifies that signalPoll() returns null when there is no configured client interface.
+ */
+ @Test
+ public void testSignalPollErrorWhenNoClientInterfaceConfigured() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ // Configure client interface.
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ mNormalScanCallback, mPnoScanCallback));
+
+ // Tear down interfaces.
+ assertTrue(mWificondControl.tearDownInterfaces());
+
+ // Signal poll should fail.
+ assertEquals(null, mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that getTxPacketCounters() calls wificond.
+ */
+ @Test
+ public void testGetTxPacketCounters() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, mNormalScanCallback,
+ mPnoScanCallback);
+ mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME);
+ verify(mClientInterface).getPacketCounters();
+ }
+
+ /**
+ * Verifies that getTxPacketCounters() returns null when there is no configured client
+ * interface.
+ */
+ @Test
+ public void testGetTxPacketCountersErrorWhenNoClientInterfaceConfigured() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ // Configure client interface.
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ mNormalScanCallback, mPnoScanCallback));
+
+ // Tear down interfaces.
+ assertTrue(mWificondControl.tearDownInterfaces());
+
+ // Signal poll should fail.
+ assertEquals(null, mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that getScanResults() returns null when there is no configured client
+ * interface.
+ */
+ @Test
+ public void testGetScanResultsErrorWhenNoClientInterfaceConfigured() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ // Configure client interface.
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ mNormalScanCallback, mPnoScanCallback));
+
+ // Tear down interfaces.
+ assertTrue(mWificondControl.tearDownInterfaces());
+
+ // getScanResults should fail.
+ assertEquals(0,
+ mWificondControl.getScanResults(TEST_INTERFACE_NAME,
+ WifiCondManager.SCAN_TYPE_SINGLE_SCAN).size());
+ }
+
+ /**
+ * Verifies that Scan() can convert input parameters to SingleScanSettings correctly.
+ */
+ @Test
+ public void testScan() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+ assertTrue(mWificondControl.scan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+ IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+ }
+
+ /**
+ * Verifies that Scan() removes duplicates hiddenSsids passed in from input.
+ */
+ @Test
+ public void testScanWithDuplicateHiddenSsids() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+ // Create a list of hiddenSsid that has a duplicate element
+ List<byte[]> hiddenSsidWithDup = new ArrayList<>(SCAN_HIDDEN_NETWORK_SSID_LIST);
+ hiddenSsidWithDup.add(SCAN_HIDDEN_NETWORK_SSID_LIST.get(0));
+ assertEquals(hiddenSsidWithDup.get(0),
+ hiddenSsidWithDup.get(hiddenSsidWithDup.size() - 1));
+ // Pass the List with duplicate elements into scan()
+ assertTrue(mWificondControl.scan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, hiddenSsidWithDup));
+ // But the argument passed down should have the duplicate removed.
+ verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+ IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+ }
+
+ /**
+ * Verifies that Scan() can handle null input parameters correctly.
+ */
+ @Test
+ public void testScanNullParameters() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+ assertTrue(mWificondControl.scan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, null, null));
+ verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+ IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY, null, null)));
+ }
+
+ /**
+ * Verifies that Scan() can handle wificond scan failure.
+ */
+ @Test
+ public void testScanFailure() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(false);
+ assertFalse(mWificondControl.scan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl).scan(any(SingleScanSettings.class));
+ }
+
+ /**
+ * Verifies that Scan() can handle invalid type.
+ */
+ @Test
+ public void testScanFailureDueToInvalidType() throws Exception {
+ assertFalse(mWificondControl.scan(
+ TEST_INTERFACE_NAME, 100,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl, never()).scan(any(SingleScanSettings.class));
+ }
+
+ /**
+ * Verifies that startPnoScan() can convert input parameters to PnoSettings correctly.
+ */
+ @Test
+ public void testStartPnoScan() throws Exception {
+ when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(true);
+ assertTrue(mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS,
+ mPnoScanRequestCallback));
+ verify(mWifiScannerImpl).startPnoScan(argThat(new PnoScanMatcher(TEST_PNO_SETTINGS)));
+ verify(mPnoScanRequestCallback).onPnoRequestSucceeded();
+ }
+
+ /**
+ * Verifies that stopPnoScan() calls underlying wificond.
+ */
+ @Test
+ public void testStopPnoScan() throws Exception {
+ when(mWifiScannerImpl.stopPnoScan()).thenReturn(true);
+ assertTrue(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).stopPnoScan();
+ }
+
+ /**
+ * Verifies that stopPnoScan() can handle wificond failure.
+ */
+ @Test
+ public void testStopPnoScanFailure() throws Exception {
+
+ when(mWifiScannerImpl.stopPnoScan()).thenReturn(false);
+ assertFalse(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).stopPnoScan();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+ * result event.
+ */
+ @Test
+ public void testScanResultEvent() throws Exception {
+ ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+ verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+ IScanEvent scanEvent = messageCaptor.getValue();
+ assertNotNull(scanEvent);
+ scanEvent.OnScanResultReady();
+
+ verify(mNormalScanCallback).onScanResultReady();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+ * failed event.
+ */
+ @Test
+ public void testScanFailedEvent() throws Exception {
+ ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+ verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+ IScanEvent scanEvent = messageCaptor.getValue();
+ assertNotNull(scanEvent);
+ scanEvent.OnScanFailed();
+
+ verify(mNormalScanCallback).onScanFailed();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon pno scan
+ * result event.
+ */
+ @Test
+ public void testPnoScanResultEvent() throws Exception {
+ ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+ verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+ IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+ assertNotNull(pnoScanEvent);
+ pnoScanEvent.OnPnoNetworkFound();
+ verify(mPnoScanCallback).onScanResultReady();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMetrics pno scan count methods upon pno event.
+ */
+ @Test
+ public void testPnoScanEventsForMetrics() throws Exception {
+ ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+ verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+ IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+ assertNotNull(pnoScanEvent);
+
+ pnoScanEvent.OnPnoNetworkFound();
+ verify(mPnoScanCallback).onScanResultReady();
+
+ pnoScanEvent.OnPnoScanFailed();
+ verify(mPnoScanCallback).onScanFailed();
+ }
+
+ /**
+ * Verifies that startPnoScan() can invoke WifiMetrics pno scan count methods correctly.
+ */
+ @Test
+ public void testStartPnoScanForMetrics() throws Exception {
+ when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(false);
+
+ assertFalse(mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS,
+ mPnoScanRequestCallback));
+ verify(mPnoScanRequestCallback).onPnoRequestFailed();
+ }
+
+ /**
+ * Verifies that abortScan() calls underlying wificond.
+ */
+ @Test
+ public void testAbortScan() throws Exception {
+ mWificondControl.abortScan(TEST_INTERFACE_NAME);
+ verify(mWifiScannerImpl).abortScan();
+ }
+
+ /**
+ * Ensures that the Ap interface callbacks are forwarded to the
+ * SoftApListener used for starting soft AP.
+ */
+ @Test
+ public void testSoftApListenerInvocation() throws Exception {
+ testSetupInterfaceForSoftApMode();
+
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+
+ when(mApInterface.registerCallback(any())).thenReturn(true);
+
+ final ArgumentCaptor<IApInterfaceEventCallback> apInterfaceCallbackCaptor =
+ ArgumentCaptor.forClass(IApInterfaceEventCallback.class);
+
+ assertTrue(mWificondControl.registerApListener(
+ TEST_INTERFACE_NAME, mSoftApListener));
+ verify(mApInterface).registerCallback(apInterfaceCallbackCaptor.capture());
+
+ final NativeWifiClient testClient = new NativeWifiClient();
+ apInterfaceCallbackCaptor.getValue().onConnectedClientsChanged(testClient, true);
+ verify(mSoftApListener).onConnectedClientsChanged(eq(testClient), eq(true));
+
+ int channelFrequency = 2437;
+ int channelBandwidth = IApInterfaceEventCallback.BANDWIDTH_20;
+ apInterfaceCallbackCaptor.getValue().onSoftApChannelSwitched(channelFrequency,
+ channelBandwidth);
+ verify(mSoftApListener).onSoftApChannelSwitched(eq(channelFrequency), eq(channelBandwidth));
+ }
+
+ /**
+ * Verifies registration and invocation of wificond death handler.
+ */
+ @Test
+ public void testRegisterDeathHandler() throws Exception {
+ Runnable deathHandler = mock(Runnable.class);
+ assertTrue(mWificondControl.initialize(deathHandler));
+ verify(mWificond).tearDownInterfaces();
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+ verify(deathHandler).run();
+ }
+
+ /**
+ * Verifies handling of wificond death and ensures that all internal state is cleared and
+ * handlers are invoked.
+ */
+ @Test
+ public void testDeathHandling() throws Exception {
+ Runnable deathHandler = mock(Runnable.class);
+ assertTrue(mWificondControl.initialize(deathHandler));
+
+ testSetupInterfaceForClientMode();
+
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+ verify(deathHandler).run();
+
+ // The handles should be cleared after death.
+ assertNull(mWificondControl.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ));
+ verify(mWificond, never()).getAvailable5gNonDFSChannels();
+ }
+
+ /**
+ * sendMgmtFrame() should fail if a null callback is passed in.
+ */
+ @Test
+ public void testSendMgmtFrameNullCallback() throws Exception {
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, null, TEST_MCS_RATE);
+
+ verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+ }
+
+ /**
+ * sendMgmtFrame() should fail if a null frame is passed in.
+ */
+ @Test
+ public void testSendMgmtFrameNullFrame() throws Exception {
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, null,
+ mSendMgmtFrameCallback, TEST_MCS_RATE);
+
+ verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+ verify(mSendMgmtFrameCallback).onFailure(anyInt());
+ }
+
+ /**
+ * sendMgmtFrame() should fail if an interface name that does not exist is passed in.
+ */
+ @Test
+ public void testSendMgmtFrameInvalidInterfaceName() throws Exception {
+ mWificondControl.sendMgmtFrame(TEST_INVALID_INTERFACE_NAME, TEST_PROBE_FRAME,
+ mSendMgmtFrameCallback, TEST_MCS_RATE);
+
+ verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+ verify(mSendMgmtFrameCallback).onFailure(anyInt());
+ }
+
+ /**
+ * sendMgmtFrame() should fail if it is called a second time before the first call completed.
+ */
+ @Test
+ public void testSendMgmtFrameCalledTwiceBeforeFinished() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb1 = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+ WifiCondManager.SendMgmtFrameCallback cb2 = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb1, TEST_MCS_RATE);
+ verify(cb1, never()).onFailure(anyInt());
+ verify(mClientInterface, times(1))
+ .SendMgmtFrame(AdditionalMatchers.aryEq(TEST_PROBE_FRAME),
+ any(), eq(TEST_MCS_RATE));
+
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb2, TEST_MCS_RATE);
+ verify(cb2).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
+ // verify SendMgmtFrame() still was only called once i.e. not called again
+ verify(mClientInterface, times(1))
+ .SendMgmtFrame(any(), any(), anyInt());
+ }
+
+ /**
+ * Tests that when a RemoteException is triggered on AIDL call, onFailure() is called only once.
+ */
+ @Test
+ public void testSendMgmtFrameThrowsException() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+
+ doThrow(new RemoteException()).when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME,
+ cb, TEST_MCS_RATE);
+ mLooper.dispatchAll();
+
+ verify(cb).onFailure(anyInt());
+ verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+ sendMgmtFrameEventCaptor.getValue().OnFailure(
+ WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ mLooper.dispatchAll();
+
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+
+ verifyNoMoreInteractions(cb);
+ }
+
+ /**
+ * Tests that the onAck() callback is triggered correctly.
+ */
+ @Test
+ public void testSendMgmtFrameSuccess() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb, TEST_MCS_RATE);
+
+ sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+ mLooper.dispatchAll();
+ verify(cb).onAck(eq(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS));
+ verify(cb, never()).onFailure(anyInt());
+ verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+ // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+ // triggered again
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+ verify(cb, times(1)).onAck(anyInt());
+ verify(cb, never()).onFailure(anyInt());
+ }
+
+ /**
+ * Tests that the onFailure() callback is triggered correctly.
+ */
+ @Test
+ public void testSendMgmtFrameFailure() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb, TEST_MCS_RATE);
+
+ sendMgmtFrameEventCaptor.getValue().OnFailure(
+ WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+ // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+ // triggered again
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb, times(1)).onFailure(anyInt());
+ }
+
+ /**
+ * Tests that the onTimeout() callback is triggered correctly.
+ */
+ @Test
+ public void testSendMgmtFrameTimeout() throws Exception {
+ WifiCondManager.SendMgmtFrameCallback cb = mock(
+ WifiCondManager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, cb, TEST_MCS_RATE);
+
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+
+ // verify that even if onAck() callback is triggered after timeout,
+ // SendMgmtFrameCallback is not triggered again
+ sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb, times(1)).onFailure(anyInt());
+ }
+
+ /**
+ * Tests every possible test outcome followed by every other test outcome to ensure that the
+ * internal state is reset correctly between calls.
+ * i.e. (success, success), (success, failure), (success, timeout),
+ * (failure, failure), (failure, success), (failure, timeout),
+ * (timeout, timeout), (timeout, success), (timeout, failure)
+ *
+ * Also tests that internal state is reset correctly after a transient AIDL RemoteException.
+ */
+ @Test
+ public void testSendMgmtFrameMixed() throws Exception {
+ testSendMgmtFrameThrowsException();
+ testSendMgmtFrameSuccess();
+ testSendMgmtFrameSuccess();
+ testSendMgmtFrameFailure();
+ testSendMgmtFrameFailure();
+ testSendMgmtFrameTimeout();
+ testSendMgmtFrameTimeout();
+ testSendMgmtFrameSuccess();
+ testSendMgmtFrameTimeout();
+ testSendMgmtFrameFailure();
+ testSendMgmtFrameSuccess();
+ }
+
+ /**
+ * Tests that OnAck() does not perform any non-thread-safe operations on the binder thread.
+ *
+ * The sequence of instructions are:
+ * 1. post onAlarm() onto main thread
+ * 2. OnAck()
+ * 3. mLooper.dispatchAll()
+ *
+ * The actual order of execution is:
+ * 1. binder thread portion of OnAck()
+ * 2. onAlarm() (which purely executes on the main thread)
+ * 3. main thread portion of OnAck()
+ *
+ * If the binder thread portion of OnAck() is not thread-safe, it can possibly mess up
+ * onAlarm(). Tests that this does not occur.
+ */
+ @Test
+ public void testSendMgmtFrameTimeoutAckThreadSafe() throws Exception {
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME,
+ mSendMgmtFrameCallback, TEST_MCS_RATE);
+
+ // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+ // triggering onAlarm() ourselves during the test, manually post onto handler
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ // OnAck posts to the handler
+ sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+ mLooper.dispatchAll();
+ verify(mSendMgmtFrameCallback, never()).onAck(anyInt());
+ verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ }
+
+ /**
+ * See {@link #testSendMgmtFrameTimeoutAckThreadSafe()}. This test replaces OnAck() with
+ * OnFailure().
+ */
+ @Test
+ public void testSendMgmtFrameTimeoutFailureThreadSafe() throws Exception {
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME,
+ mSendMgmtFrameCallback, TEST_MCS_RATE);
+
+ // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+ // triggering onAlarm() ourselves during the test, manually post onto handler
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ // OnFailure posts to the handler
+ sendMgmtFrameEventCaptor.getValue().OnFailure(
+ WifiCondManager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ mLooper.dispatchAll();
+ verify(mSendMgmtFrameCallback).onFailure(WifiCondManager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ }
+
+ // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
+ // matches the provided frequency set and ssid set.
+ private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {
+ int mExpectedScanType;
+ private final Set<Integer> mExpectedFreqs;
+ private final List<byte[]> mExpectedSsids;
+
+ ScanMatcher(int expectedScanType, Set<Integer> expectedFreqs, List<byte[]> expectedSsids) {
+ this.mExpectedScanType = expectedScanType;
+ this.mExpectedFreqs = expectedFreqs;
+ this.mExpectedSsids = expectedSsids;
+ }
+
+ @Override
+ public boolean matches(SingleScanSettings settings) {
+ if (settings.scanType != mExpectedScanType) {
+ return false;
+ }
+ ArrayList<ChannelSettings> channelSettings = settings.channelSettings;
+ ArrayList<HiddenNetwork> hiddenNetworks = settings.hiddenNetworks;
+ if (mExpectedFreqs != null) {
+ Set<Integer> freqSet = new HashSet<Integer>();
+ for (ChannelSettings channel : channelSettings) {
+ freqSet.add(channel.frequency);
+ }
+ if (!mExpectedFreqs.equals(freqSet)) {
+ return false;
+ }
+ } else {
+ if (channelSettings != null && channelSettings.size() > 0) {
+ return false;
+ }
+ }
+
+ if (mExpectedSsids != null) {
+ List<byte[]> ssidSet = new ArrayList<>();
+ for (HiddenNetwork network : hiddenNetworks) {
+ ssidSet.add(network.ssid);
+ }
+ if (!mExpectedSsids.equals(ssidSet)) {
+ return false;
+ }
+
+ } else {
+ if (hiddenNetworks != null && hiddenNetworks.size() > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "ScanMatcher{mExpectedFreqs=" + mExpectedFreqs
+ + ", mExpectedSsids=" + mExpectedSsids + '}';
+ }
+ }
+
+ // Create a ArgumentMatcher which captures a PnoSettings parameter and checks if it
+ // matches the WifiNative.PnoSettings;
+ private class PnoScanMatcher implements ArgumentMatcher<PnoSettings> {
+ private final PnoSettings mExpectedPnoSettings;
+
+ PnoScanMatcher(PnoSettings expectedPnoSettings) {
+ this.mExpectedPnoSettings = expectedPnoSettings;
+ }
+
+ @Override
+ public boolean matches(PnoSettings settings) {
+ if (mExpectedPnoSettings == null) {
+ return false;
+ }
+ if (settings.intervalMs != mExpectedPnoSettings.intervalMs
+ || settings.min2gRssi != mExpectedPnoSettings.min2gRssi
+ || settings.min5gRssi != mExpectedPnoSettings.min5gRssi
+ || settings.min6gRssi != mExpectedPnoSettings.min6gRssi) {
+ return false;
+ }
+ if (settings.pnoNetworks == null || mExpectedPnoSettings.pnoNetworks == null) {
+ return false;
+ }
+ if (settings.pnoNetworks.size() != mExpectedPnoSettings.pnoNetworks.size()) {
+ return false;
+ }
+
+ for (int i = 0; i < settings.pnoNetworks.size(); i++) {
+ if (!Arrays.equals(settings.pnoNetworks.get(i).ssid,
+ mExpectedPnoSettings.pnoNetworks.get(i).ssid)) {
+ return false;
+ }
+ if (settings.pnoNetworks.get(i).isHidden != mExpectedPnoSettings.pnoNetworks.get(
+ i).isHidden) {
+ return false;
+ }
+ if (!Arrays.equals(settings.pnoNetworks.get(i).frequencies,
+ mExpectedPnoSettings.pnoNetworks.get(i).frequencies)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "PnoScanMatcher{" + "mExpectedPnoSettings=" + mExpectedPnoSettings + '}';
+ }
+ }
+
+ private static class LocalNativeUtil {
+ private static final int SSID_BYTES_MAX_LEN = 32;
+
+ /**
+ * Converts an ArrayList<Byte> of UTF_8 byte values to string.
+ * The string will either be:
+ * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non
+ * null),
+ * or
+ * b) Hex string with no delimiters.
+ *
+ * @param bytes List of bytes for ssid.
+ * @throws IllegalArgumentException for null bytes.
+ */
+ public static String bytesToHexOrQuotedString(ArrayList<Byte> bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("null ssid bytes");
+ }
+ byte[] byteArray = byteArrayFromArrayList(bytes);
+ // Check for 0's in the byte stream in which case we cannot convert this into a string.
+ if (!bytes.contains(Byte.valueOf((byte) 0))) {
+ CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
+ try {
+ CharBuffer decoded = decoder.decode(ByteBuffer.wrap(byteArray));
+ return "\"" + decoded.toString() + "\"";
+ } catch (CharacterCodingException cce) {
+ }
+ }
+ return hexStringFromByteArray(byteArray);
+ }
+
+ /**
+ * Converts an ssid string to an arraylist of UTF_8 byte values.
+ * These forms are acceptable:
+ * a) UTF-8 String encapsulated in quotes, or
+ * b) Hex string with no delimiters.
+ *
+ * @param ssidStr String to be converted.
+ * @throws IllegalArgumentException for null string.
+ */
+ public static ArrayList<Byte> decodeSsid(String ssidStr) {
+ ArrayList<Byte> ssidBytes = hexOrQuotedStringToBytes(ssidStr);
+ if (ssidBytes.size() > SSID_BYTES_MAX_LEN) {
+ throw new IllegalArgumentException(
+ "ssid bytes size out of range: " + ssidBytes.size());
+ }
+ return ssidBytes;
+ }
+
+ /**
+ * Convert from an array list of Byte to an array of primitive bytes.
+ */
+ public static byte[] byteArrayFromArrayList(ArrayList<Byte> bytes) {
+ byte[] byteArray = new byte[bytes.size()];
+ int i = 0;
+ for (Byte b : bytes) {
+ byteArray[i++] = b;
+ }
+ return byteArray;
+ }
+
+ /**
+ * Converts a byte array to hex string.
+ *
+ * @param bytes List of bytes for ssid.
+ * @throws IllegalArgumentException for null bytes.
+ */
+ public static String hexStringFromByteArray(byte[] bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("null hex bytes");
+ }
+ return new String(HexEncoding.encode(bytes)).toLowerCase();
+ }
+
+ /**
+ * Converts an string to an arraylist of UTF_8 byte values.
+ * These forms are acceptable:
+ * a) UTF-8 String encapsulated in quotes, or
+ * b) Hex string with no delimiters.
+ *
+ * @param str String to be converted.
+ * @throws IllegalArgumentException for null string.
+ */
+ public static ArrayList<Byte> hexOrQuotedStringToBytes(String str) {
+ if (str == null) {
+ throw new IllegalArgumentException("null string");
+ }
+ int length = str.length();
+ if ((length > 1) && (str.charAt(0) == '"') && (str.charAt(length - 1) == '"')) {
+ str = str.substring(1, str.length() - 1);
+ return stringToByteArrayList(str);
+ } else {
+ return byteArrayToArrayList(hexStringToByteArray(str));
+ }
+ }
+
+ /**
+ * Convert the string to byte array list.
+ *
+ * @return the UTF_8 char byte values of str, as an ArrayList.
+ * @throws IllegalArgumentException if a null or unencodable string is sent.
+ */
+ public static ArrayList<Byte> stringToByteArrayList(String str) {
+ if (str == null) {
+ throw new IllegalArgumentException("null string");
+ }
+ // Ensure that the provided string is UTF_8 encoded.
+ CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+ try {
+ ByteBuffer encoded = encoder.encode(CharBuffer.wrap(str));
+ byte[] byteArray = new byte[encoded.remaining()];
+ encoded.get(byteArray);
+ return byteArrayToArrayList(byteArray);
+ } catch (CharacterCodingException cce) {
+ throw new IllegalArgumentException("cannot be utf-8 encoded", cce);
+ }
+ }
+
+ /**
+ * Convert from an array of primitive bytes to an array list of Byte.
+ */
+ public static ArrayList<Byte> byteArrayToArrayList(byte[] bytes) {
+ ArrayList<Byte> byteList = new ArrayList<>();
+ for (Byte b : bytes) {
+ byteList.add(b);
+ }
+ return byteList;
+ }
+
+ /**
+ * Converts a hex string to byte array.
+ *
+ * @param hexStr String to be converted.
+ * @throws IllegalArgumentException for null string.
+ */
+ public static byte[] hexStringToByteArray(String hexStr) {
+ if (hexStr == null) {
+ throw new IllegalArgumentException("null hex string");
+ }
+ return HexEncoding.decode(hexStr.toCharArray(), false);
+ }
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
new file mode 100644
index 0000000..06f12f7
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 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.wificond;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.NativeScanResult}.
+ */
+@SmallTest
+public class NativeScanResultTest {
+
+ private static final byte[] TEST_SSID =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_BSSID =
+ new byte[] {(byte) 0x12, (byte) 0xef, (byte) 0xa1,
+ (byte) 0x2c, (byte) 0x97, (byte) 0x8b};
+ private static final byte[] TEST_INFO_ELEMENT =
+ new byte[] {(byte) 0x01, (byte) 0x03, (byte) 0x12, (byte) 0xbe, (byte) 0xff};
+ private static final int TEST_FREQUENCY = 2456;
+ private static final int TEST_SIGNAL_MBM = -45;
+ private static final long TEST_TSF = 34455441;
+ private static final BitSet TEST_CAPABILITY = new BitSet(16) {{ set(2); set(5); }};
+ private static final boolean TEST_ASSOCIATED = true;
+ private static final int[] RADIO_CHAIN_IDS = { 0, 1 };
+ private static final int[] RADIO_CHAIN_LEVELS = { -56, -65 };
+
+ /**
+ * NativeScanResult object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ NativeScanResult scanResult = new NativeScanResult();
+ scanResult.ssid = TEST_SSID;
+ scanResult.bssid = TEST_BSSID;
+ scanResult.infoElement = TEST_INFO_ELEMENT;
+ scanResult.frequency = TEST_FREQUENCY;
+ scanResult.signalMbm = TEST_SIGNAL_MBM;
+ scanResult.tsf = TEST_TSF;
+ scanResult.capability = TEST_CAPABILITY;
+ scanResult.associated = TEST_ASSOCIATED;
+ scanResult.radioChainInfos = new ArrayList<>(Arrays.asList(
+ new RadioChainInfo(RADIO_CHAIN_IDS[0], RADIO_CHAIN_LEVELS[0]),
+ new RadioChainInfo(RADIO_CHAIN_IDS[1], RADIO_CHAIN_LEVELS[1])));
+ Parcel parcel = Parcel.obtain();
+ scanResult.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ NativeScanResult scanResultDeserialized = NativeScanResult.CREATOR.createFromParcel(parcel);
+
+ assertArrayEquals(scanResult.ssid, scanResultDeserialized.ssid);
+ assertArrayEquals(scanResult.bssid, scanResultDeserialized.bssid);
+ assertArrayEquals(scanResult.infoElement, scanResultDeserialized.infoElement);
+ assertEquals(scanResult.frequency, scanResultDeserialized.frequency);
+ assertEquals(scanResult.signalMbm, scanResultDeserialized.signalMbm);
+ assertEquals(scanResult.tsf, scanResultDeserialized.tsf);
+ assertEquals(scanResult.capability, scanResultDeserialized.capability);
+ assertEquals(scanResult.associated, scanResultDeserialized.associated);
+ assertTrue(scanResult.radioChainInfos.containsAll(scanResultDeserialized.radioChainInfos));
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/wificond/PnoSettingsTest.java b/wifi/tests/src/android/net/wifi/wificond/PnoSettingsTest.java
new file mode 100644
index 0000000..775acc7
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/PnoSettingsTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 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.wificond;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.PnoSettingsResult}.
+ */
+@SmallTest
+public class PnoSettingsTest {
+
+ private static final byte[] TEST_SSID_1 =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_SSID_2 =
+ new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+ private static final int[] TEST_FREQUENCIES_1 = {};
+ private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+ private static final int TEST_INTERVAL_MS = 30000;
+ private static final int TEST_MIN_2G_RSSI = -60;
+ private static final int TEST_MIN_5G_RSSI = -65;
+ private static final int TEST_VALUE = 42;
+
+ private PnoNetwork mPnoNetwork1;
+ private PnoNetwork mPnoNetwork2;
+
+ @Before
+ public void setUp() {
+ mPnoNetwork1 = new PnoNetwork();
+ mPnoNetwork1.ssid = TEST_SSID_1;
+ mPnoNetwork1.isHidden = true;
+ mPnoNetwork1.frequencies = TEST_FREQUENCIES_1;
+
+ mPnoNetwork2 = new PnoNetwork();
+ mPnoNetwork2.ssid = TEST_SSID_2;
+ mPnoNetwork2.isHidden = false;
+ mPnoNetwork2.frequencies = TEST_FREQUENCIES_2;
+ }
+
+ /**
+ * PnoSettings object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ PnoSettings pnoSettings = new PnoSettings();
+ pnoSettings.intervalMs = TEST_INTERVAL_MS;
+ pnoSettings.min2gRssi = TEST_MIN_2G_RSSI;
+ pnoSettings.min5gRssi = TEST_MIN_5G_RSSI;
+ pnoSettings.pnoNetworks = new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2));
+
+ Parcel parcel = Parcel.obtain();
+ pnoSettings.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ PnoSettings pnoSettingsDeserialized = PnoSettings.CREATOR.createFromParcel(parcel);
+
+ assertEquals(pnoSettings, pnoSettingsDeserialized);
+ assertEquals(pnoSettings.hashCode(), pnoSettingsDeserialized.hashCode());
+ }
+
+ /**
+ * Tests usage of {@link PnoSettings} as a HashMap key type.
+ */
+ @Test
+ public void testAsHashMapKey() {
+ PnoSettings pnoSettings1 = new PnoSettings();
+ pnoSettings1.intervalMs = TEST_INTERVAL_MS;
+ pnoSettings1.min2gRssi = TEST_MIN_2G_RSSI;
+ pnoSettings1.min5gRssi = TEST_MIN_5G_RSSI;
+ pnoSettings1.pnoNetworks = new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2));
+
+ PnoSettings pnoSettings2 = new PnoSettings();
+ pnoSettings2.intervalMs = TEST_INTERVAL_MS;
+ pnoSettings2.min2gRssi = TEST_MIN_2G_RSSI;
+ pnoSettings2.min5gRssi = TEST_MIN_5G_RSSI;
+ pnoSettings2.pnoNetworks = new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2));
+
+ assertEquals(pnoSettings1, pnoSettings2);
+ assertEquals(pnoSettings1.hashCode(), pnoSettings2.hashCode());
+
+ HashMap<PnoSettings, Integer> map = new HashMap<>();
+ map.put(pnoSettings1, TEST_VALUE);
+
+ assertEquals(TEST_VALUE, map.get(pnoSettings2).intValue());
+ }
+}
diff --git a/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java b/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java
new file mode 100644
index 0000000..ef59839
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/wificond/SingleScanSettingsTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017 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.wificond;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.wifi.IWifiScannerImpl;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.wificond.SingleScanSettingsResult}.
+ */
+@SmallTest
+public class SingleScanSettingsTest {
+
+ private static final byte[] TEST_SSID_1 =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_SSID_2 =
+ new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+ private static final int TEST_FREQUENCY_1 = 2456;
+ private static final int TEST_FREQUENCY_2 = 5215;
+ private static final int TEST_VALUE = 42;
+
+ private ChannelSettings mChannelSettings1;
+ private ChannelSettings mChannelSettings2;
+ private HiddenNetwork mHiddenNetwork1;
+ private HiddenNetwork mHiddenNetwork2;
+
+ @Before
+ public void setUp() {
+ mChannelSettings1 = new ChannelSettings();
+ mChannelSettings1.frequency = TEST_FREQUENCY_1;
+ mChannelSettings2 = new ChannelSettings();
+ mChannelSettings2.frequency = TEST_FREQUENCY_2;
+
+ mHiddenNetwork1 = new HiddenNetwork();
+ mHiddenNetwork1.ssid = TEST_SSID_1;
+ mHiddenNetwork2 = new HiddenNetwork();
+ mHiddenNetwork2.ssid = TEST_SSID_2;
+ }
+
+ /**
+ * SingleScanSettings object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ SingleScanSettings scanSettings = new SingleScanSettings();
+ scanSettings.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+
+ scanSettings.channelSettings =
+ new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+ scanSettings.hiddenNetworks =
+ new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+ Parcel parcel = Parcel.obtain();
+ scanSettings.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ SingleScanSettings scanSettingsDeserialized =
+ SingleScanSettings.CREATOR.createFromParcel(parcel);
+
+ assertEquals(scanSettings, scanSettingsDeserialized);
+ assertEquals(scanSettings.hashCode(), scanSettingsDeserialized.hashCode());
+ }
+
+ /**
+ * Tests usage of {@link SingleScanSettings} as a HashMap key type.
+ */
+ @Test
+ public void testAsHashMapKey() {
+ SingleScanSettings scanSettings1 = new SingleScanSettings();
+ scanSettings1.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ scanSettings1.channelSettings =
+ new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+ scanSettings1.hiddenNetworks =
+ new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+ SingleScanSettings scanSettings2 = new SingleScanSettings();
+ scanSettings2.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ scanSettings2.channelSettings =
+ new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+ scanSettings2.hiddenNetworks =
+ new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+ assertEquals(scanSettings1, scanSettings2);
+ assertEquals(scanSettings1.hashCode(), scanSettings2.hashCode());
+
+ HashMap<SingleScanSettings, Integer> map = new HashMap<>();
+ map.put(scanSettings1, TEST_VALUE);
+
+ assertEquals(TEST_VALUE, map.get(scanSettings2).intValue());
+ }
+}