summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorQuang Luong <qal@google.com>2019-08-20 16:40:17 -0700
committerQuang Luong <qal@google.com>2019-09-23 10:19:29 -0700
commitda9087bbf8eb4f4ce3c013bc7003e5bd63ccb61c (patch)
treeac7995c346db77bbb2ea610815daf5619c95f913 /libs
parent5cb2d16dcc76b3f087b48cbf3fb1e0e7f48c8edc (diff)
Added StandardWifiEntry tracking
StandardWifiEntries (normal Wifi networks represented by an SSID) added to WifiTracker2 tracking with basic display information in wifi picker. Test: atest WifiTrackerLibTests, manual visual inspection of wifi settings Bug: 70983952 Change-Id: I416a33c9bedf0a2a59f392d4abbe46b788ea78c6
Diffstat (limited to 'libs')
-rw-r--r--libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java10
-rw-r--r--libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java274
-rw-r--r--libs/WifiTrackerLib/src/com/android/wifitrackerlib/Utils.java77
-rw-r--r--libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java59
-rw-r--r--libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java185
-rw-r--r--libs/WifiTrackerLib/tests/Android.bp4
-rw-r--r--libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/ScanResultUpdaterTest.java38
-rw-r--r--libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/StandardWifiEntryTest.java201
-rw-r--r--libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/TestUtils.java41
-rw-r--r--libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/UtilsTest.java54
-rw-r--r--libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java182
11 files changed, 1065 insertions, 60 deletions
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java
index addab0c08..6f5f53878 100644
--- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java
+++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java
@@ -18,6 +18,8 @@ package com.android.wifitrackerlib;
import android.net.wifi.ScanResult;
+import androidx.annotation.NonNull;
+
import java.time.Clock;
import java.util.ArrayList;
import java.util.HashMap;
@@ -55,7 +57,7 @@ public class ScanResultUpdater {
/**
* Updates scan result list and replaces older scans of the same BSSID.
*/
- public void update(List<ScanResult> newResults) {
+ public void update(@NonNull List<ScanResult> newResults) {
synchronized (mLock) {
evictOldScans();
@@ -71,6 +73,7 @@ public class ScanResultUpdater {
/**
* Returns all seen scan results merged by BSSID.
*/
+ @NonNull
public List<ScanResult> getScanResults() {
return getScanResults(mMaxScanAgeMillis);
}
@@ -79,6 +82,7 @@ public class ScanResultUpdater {
* Returns all seen scan results merged by BSSID and newer than maxScanAgeMillis.
* maxScanAgeMillis must be less than or equal to the mMaxScanAgeMillis field if it was set.
*/
+ @NonNull
public List<ScanResult> getScanResults(long maxScanAgeMillis) throws IllegalArgumentException {
if (maxScanAgeMillis > mMaxScanAgeMillis) {
throw new IllegalArgumentException(
@@ -87,7 +91,7 @@ public class ScanResultUpdater {
synchronized (mLock) {
List<ScanResult> ageFilteredResults = new ArrayList<>();
for (ScanResult result : mScanResultsByBssid.values()) {
- if (mClock.millis() - result.timestamp <= maxScanAgeMillis) {
+ if (mClock.millis() - result.timestamp / 1000 <= maxScanAgeMillis) {
ageFilteredResults.add(result);
}
}
@@ -98,7 +102,7 @@ public class ScanResultUpdater {
private void evictOldScans() {
synchronized (mLock) {
mScanResultsByBssid.entrySet().removeIf((entry) ->
- mClock.millis() - entry.getValue().timestamp > mMaxScanAgeMillis);
+ mClock.millis() - entry.getValue().timestamp / 1000 > mMaxScanAgeMillis);
}
}
}
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java
new file mode 100644
index 000000000..f4c4f05ed
--- /dev/null
+++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/StandardWifiEntry.java
@@ -0,0 +1,274 @@
+/*
+ * 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 com.android.wifitrackerlib;
+
+import static com.android.wifitrackerlib.Utils.getBestScanResultByLevel;
+import static com.android.wifitrackerlib.Utils.getSecurityFromScanResult;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * WifiEntry representation of a logical Wi-Fi network, uniquely identified by SSID and security.
+ *
+ * This type of WifiEntry can represent both open and saved networks.
+ */
+class StandardWifiEntry extends WifiEntry {
+ static final String KEY_PREFIX = "StandardWifiEntry:";
+
+ private final List<ScanResult> mCurrentScanResults = new ArrayList<>();
+
+ private final String mKey;
+ private final String mSsid;
+ private final @Security int mSecurity;
+
+ private int mLevel = WIFI_LEVEL_UNREACHABLE;
+
+ StandardWifiEntry(@NonNull Handler callbackHandler, @NonNull List<ScanResult> scanResults)
+ throws IllegalArgumentException {
+ super(callbackHandler);
+
+ if (scanResults.isEmpty()) {
+ throw new IllegalArgumentException("Cannot construct with empty ScanResult list!");
+ }
+ final ScanResult firstScan = scanResults.get(0);
+ mKey = createStandardWifiEntryKey(firstScan);
+ mSsid = firstScan.SSID;
+ mSecurity = getSecurityFromScanResult(firstScan);
+ updateScanResultInfo(scanResults);
+ }
+
+ @Override
+ public String getKey() {
+ return mKey;
+ }
+
+ @Override
+ @ConnectedState
+ public int getConnectedState() {
+ // TODO(b/70983952): Fill this method in
+ return CONNECTED_STATE_DISCONNECTED;
+ }
+
+ @Override
+ public String getTitle() {
+ return mSsid;
+ }
+
+ @Override
+ public String getSummary() {
+ // TODO(b/70983952): Fill this method in
+ return null;
+ }
+
+ @Override
+ public int getLevel() {
+ return mLevel;
+ }
+
+ @Override
+ @Security
+ public int getSecurity() {
+ // TODO(b/70983952): Fill this method in
+ return mSecurity;
+ }
+
+ @Override
+ public boolean isMetered() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ public ConnectedInfo getConnectedInfo() {
+ // TODO(b/70983952): Fill this method in
+ return null;
+ }
+
+ @Override
+ public boolean canConnect() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ public void connect() {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ @Override
+ public boolean canDisconnect() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ public void disconnect() {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ @Override
+ public boolean canForget() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ public void forget() {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ @Override
+ public boolean canSetPassword() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ public void setPassword(@NonNull String password) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ @Override
+ @MeteredChoice
+ public int getMeteredChoice() {
+ // TODO(b/70983952): Fill this method in
+ return METERED_CHOICE_UNMETERED;
+ }
+
+ @Override
+ public boolean canSetMeteredChoice() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ public void setMeteredChoice(int meteredChoice) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ @Override
+ public boolean canSetPrivacy() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ @Privacy
+ public int getPrivacy() {
+ // TODO(b/70983952): Fill this method in
+ return PRIVACY_RANDOMIZED_MAC;
+ }
+
+ @Override
+ public void setPrivacy(int privacy) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ @Override
+ public boolean isAutoJoinEnabled() {
+ // TODO(b/70983952): Fill this method in
+ return true;
+ }
+
+ @Override
+ public boolean canSetAutoJoinEnabled() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ public void setAutoJoinEnabled(boolean enabled) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ @Override
+ public ProxySettings getProxySettings() {
+ // TODO(b/70983952): Fill this method in
+ return null;
+ }
+
+ @Override
+ public boolean canSetProxySettings() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ public void setProxySettings(@NonNull ProxySettings proxySettings) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ @Override
+ public IpSettings getIpSettings() {
+ // TODO(b/70983952): Fill this method in
+ return null;
+ }
+
+ @Override
+ public boolean canSetIpSettings() {
+ // TODO(b/70983952): Fill this method in
+ return false;
+ }
+
+ @Override
+ public void setIpSettings(@NonNull IpSettings ipSettings) {
+ // TODO(b/70983952): Fill this method in
+ }
+
+ @WorkerThread
+ void updateScanResultInfo(@NonNull List<ScanResult> scanResults)
+ throws IllegalArgumentException {
+ if (scanResults.isEmpty()) {
+ throw new IllegalArgumentException("Cannot update with empty ScanResult list!");
+ }
+
+ for (ScanResult result : scanResults) {
+ if (!TextUtils.equals(result.SSID, mSsid)) {
+ throw new IllegalArgumentException(
+ "Attempted to update with wrong SSID! Expected: "
+ + mSsid + ", Actual: " + result.SSID + ", ScanResult: " + result);
+ }
+ int security = getSecurityFromScanResult(result);
+ if (security != mSecurity) {
+ throw new IllegalArgumentException(
+ "Attempted to update with wrong security type! Expected: "
+ + mSecurity + ", Actual: " + security + ", ScanResult: " + result);
+ }
+ }
+
+ mCurrentScanResults.clear();
+ mCurrentScanResults.addAll(scanResults);
+
+ final ScanResult bestScanResult = getBestScanResultByLevel(mCurrentScanResults);
+ mLevel = WifiManager.calculateSignalLevel(bestScanResult.level, WifiManager.RSSI_LEVELS);
+
+ notifyOnUpdated();
+ }
+
+ static String createStandardWifiEntryKey(ScanResult scan) {
+ return KEY_PREFIX + scan.SSID + "," + getSecurityFromScanResult(scan);
+ }
+}
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/Utils.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/Utils.java
new file mode 100644
index 000000000..aa7f45169
--- /dev/null
+++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/Utils.java
@@ -0,0 +1,77 @@
+/*
+ * 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 com.android.wifitrackerlib;
+
+import static com.android.wifitrackerlib.WifiEntry.SECURITY_EAP;
+import static com.android.wifitrackerlib.WifiEntry.SECURITY_EAP_SUITE_B;
+import static com.android.wifitrackerlib.WifiEntry.SECURITY_NONE;
+import static com.android.wifitrackerlib.WifiEntry.SECURITY_OWE;
+import static com.android.wifitrackerlib.WifiEntry.SECURITY_OWE_TRANSITION;
+import static com.android.wifitrackerlib.WifiEntry.SECURITY_PSK;
+import static com.android.wifitrackerlib.WifiEntry.SECURITY_PSK_SAE_TRANSITION;
+import static com.android.wifitrackerlib.WifiEntry.SECURITY_SAE;
+import static com.android.wifitrackerlib.WifiEntry.SECURITY_WEP;
+
+import static java.util.Comparator.comparingInt;
+
+import android.net.wifi.ScanResult;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility methods for WifiTrackerLib.
+ */
+class Utils {
+ // Returns the ScanResult with the best RSSI from a list of ScanResults.
+ @Nullable
+ static ScanResult getBestScanResultByLevel(@NonNull List<ScanResult> scanResults) {
+ if (scanResults.isEmpty()) return null;
+
+ return Collections.max(scanResults, comparingInt(scanResult -> scanResult.level));
+ }
+
+ // Returns the SECURITY type of a ScanResult
+ @WifiEntry.Security
+ static int getSecurityFromScanResult(@NonNull ScanResult result) {
+ if (result.capabilities == null) {
+ return SECURITY_NONE;
+ }
+
+ if (result.capabilities.contains("WEP")) {
+ return SECURITY_WEP;
+ } else if (result.capabilities.contains("PSK+SAE")) {
+ return SECURITY_PSK_SAE_TRANSITION;
+ } else if (result.capabilities.contains("SAE")) {
+ return SECURITY_SAE;
+ } else if (result.capabilities.contains("PSK")) {
+ return SECURITY_PSK;
+ } else if (result.capabilities.contains("EAP_SUITE_B_192")) {
+ return SECURITY_EAP_SUITE_B;
+ } else if (result.capabilities.contains("EAP")) {
+ return SECURITY_EAP;
+ } else if (result.capabilities.contains("OWE_TRANSITION")) {
+ return SECURITY_OWE_TRANSITION;
+ } else if (result.capabilities.contains("OWE")) {
+ return SECURITY_OWE;
+ }
+ return SECURITY_NONE;
+ }
+}
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java
index 3742e42e4..3148e6600 100644
--- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java
+++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java
@@ -16,7 +16,11 @@
package com.android.wifitrackerlib;
+import android.os.Handler;
+
+import androidx.annotation.AnyThread;
import androidx.annotation.IntDef;
+import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import java.lang.annotation.Retention;
@@ -120,7 +124,12 @@ public abstract class WifiEntry implements Comparable<WifiEntry> {
public static final int FREQUENCY_UNKNOWN = -1;
// Callback associated with this WifiEntry. Subclasses should call its methods appropriately.
- protected WifiEntryCallback mListener;
+ private WifiEntryCallback mListener;
+ private Handler mCallbackHandler;
+
+ WifiEntry(@NonNull Handler callbackHandler) throws IllegalArgumentException {
+ mCallbackHandler = callbackHandler;
+ }
// Info available for all WifiEntries //
@@ -299,6 +308,7 @@ public abstract class WifiEntry implements Comparable<WifiEntry> {
/**
* Listener for changes to the state of the WifiEntry or the result of actions on the WifiEntry.
+ * These callbacks will be invoked on the main thread.
*/
public interface WifiEntryCallback {
@Retention(RetentionPolicy.SOURCE)
@@ -340,21 +350,25 @@ public abstract class WifiEntry implements Comparable<WifiEntry> {
* Indicates the state of the WifiEntry has changed and clients may retrieve updates through
* the WifiEntry getter methods.
*/
+ @MainThread
void onUpdated();
/**
* Result of the connect request indicated by the CONNECT_STATUS constants.
*/
+ @MainThread
void onConnectResult(@ConnectStatus int status);
/**
* Result of the disconnect request indicated by the DISCONNECT_STATUS constants.
*/
+ @MainThread
void onDisconnectResult(@DisconnectStatus int status);
/**
* Result of the forget request indicated by the FORGET_STATUS constants.
*/
+ @MainThread
void onForgetResult(@ForgetStatus int status);
}
@@ -372,4 +386,47 @@ public abstract class WifiEntry implements Comparable<WifiEntry> {
if (!(other instanceof WifiEntry)) return false;
return getKey().equals(((WifiEntry) other).getKey());
}
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append(getKey())
+ .append(",title:")
+ .append(getTitle())
+ .append(",summary:")
+ .append(getSummary())
+ .append(",level:")
+ .append(getLevel())
+ .append(",security:")
+ .append(getSecurity())
+ .toString();
+ }
+
+ @AnyThread
+ protected void notifyOnUpdated() {
+ if (mListener != null) {
+ mCallbackHandler.post(() -> mListener.onUpdated());
+ }
+ }
+
+ @AnyThread
+ protected void notifyOnConnectResult(int status) {
+ if (mListener != null) {
+ mCallbackHandler.post(() -> mListener.onConnectResult(status));
+ }
+ }
+
+ @AnyThread
+ protected void notifyOnDisconnectResult(int status) {
+ if (mListener != null) {
+ mCallbackHandler.post(() -> mListener.onDisconnectResult(status));
+ }
+ }
+
+ @AnyThread
+ protected void notifyOnForgetResult(int status) {
+ if (mListener != null) {
+ mCallbackHandler.post(() -> mListener.onForgetResult(status));
+ }
+ }
}
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java
index 947b26409..a3cfa8af9 100644
--- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java
+++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java
@@ -16,16 +16,24 @@
package com.android.wifitrackerlib;
+import static com.android.wifitrackerlib.StandardWifiEntry.createStandardWifiEntryKey;
+
+import static java.util.stream.Collectors.groupingBy;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
+import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.AnyThread;
import androidx.annotation.GuardedBy;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
@@ -37,12 +45,29 @@ import androidx.lifecycle.OnLifecycleEvent;
import java.time.Clock;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Keeps track of the state of Wi-Fi and supplies {@link WifiEntry} for use in Wi-Fi picker lists.
*
* Clients should use WifiTracker2/WifiEntry for all information regarding Wi-Fi.
+ *
+ * This class runs on two threads:
+ *
+ * The main thread processes lifecycle events (onStart, onStop), as well as listener callbacks since
+ * these directly manipulate the UI.
+ *
+ * The worker thread is responsible for driving the periodic scan requests and updating internal
+ * data in reaction to system broadcasts. After a data update, the listener is notified on the main
+ * thread.
+ *
+ * To keep synchronization simple, this means that the vast majority of work is done within the
+ * worker thread. Synchronized blocks should then be used for updating/accessing only data that is
+ * consumed by the client listener.
*/
public class WifiTracker2 implements LifecycleObserver {
@@ -66,7 +91,6 @@ public class WifiTracker2 implements LifecycleObserver {
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
/**
* TODO (b/70983952): Add the rest of the broadcast handling.
- * WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
* WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
* WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
* WifiManager.NETWORK_STATE_CHANGED_ACTION);
@@ -78,23 +102,33 @@ public class WifiTracker2 implements LifecycleObserver {
String action = intent.getAction();
if (isVerboseLoggingEnabled()) {
- Log.i(TAG, "Received broadcast: " + action);
+ Log.v(TAG, "Received broadcast: " + action);
}
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
- updateWifiState();
+ if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
+ mScanner.start();
+ } else {
+ mScanner.stop();
+ }
+ notifyOnWifiStateChanged();
+ } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) {
+ final boolean lastScanSucceeded =
+ intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, true);
+ if (lastScanSucceeded) updateScanResults();
+ updateWifiEntries(lastScanSucceeded);
+ notifyOnWifiEntriesChanged();
}
}
};
-
private final ScanResultUpdater mScanResultUpdater;
+ private final Scanner mScanner;
// Lock object for mWifiEntries
private final Object mLock = new Object();
- private int mWifiState;
- @GuardedBy("mLock")
- private final List<WifiEntry> mWifiEntries;
+ @GuardedBy("mLock") private final List<WifiEntry> mWifiEntries = new ArrayList<>();
+ private final Map<String, StandardWifiEntry> mStandardWifiEntryCache = new HashMap<>();
/**
* Constructor for WifiTracker2.
@@ -132,10 +166,9 @@ public class WifiTracker2 implements LifecycleObserver {
mScanIntervalMillis = scanIntervalMillis;
mListener = listener;
- mScanResultUpdater = new ScanResultUpdater(clock, maxScanAgeMillis * 2);
-
- mWifiState = WifiManager.WIFI_STATE_DISABLED;
- mWifiEntries = new ArrayList<>();
+ mScanResultUpdater = new ScanResultUpdater(clock,
+ maxScanAgeMillis + scanIntervalMillis);
+ mScanner = new Scanner(workerHandler.getLooper());
sVerboseLogging = mWifiManager.getVerboseLoggingLevel() > 0;
}
@@ -147,7 +180,6 @@ public class WifiTracker2 implements LifecycleObserver {
@MainThread
public void onStart() {
// TODO (b/70983952): Register score cache and receivers for network callbacks.
- // TODO (b/70983952): Resume scanner here.
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
@@ -156,8 +188,16 @@ public class WifiTracker2 implements LifecycleObserver {
// Populate data now so we don't have to wait for the next broadcast.
mWorkerHandler.post(() -> {
- updateWifiState();
- // TODO (b/70983952): Update other info (eg ScanResults) driven by broadcasts here.
+ if (mWifiManager.getWifiState() == WifiManager.WIFI_STATE_ENABLED) {
+ mScanner.start();
+ } else {
+ mScanner.stop();
+ }
+ notifyOnWifiStateChanged();
+
+ updateScanResults();
+ updateWifiEntries(true);
+ notifyOnWifiEntriesChanged();
});
}
@@ -168,7 +208,7 @@ public class WifiTracker2 implements LifecycleObserver {
@MainThread
public void onStop() {
// TODO (b/70983952): Unregister score cache and receivers for network callbacks.
- // TODO (b/70983952): Pause scanner here.
+ mScanner.stop();
mContext.unregisterReceiver(mBroadcastReceiver);
}
@@ -181,13 +221,15 @@ public class WifiTracker2 implements LifecycleObserver {
* <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
* <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
*/
+ @AnyThread
public int getWifiState() {
- return mWifiState;
+ return mWifiManager.getWifiState();
}
/**
* Returns the WifiEntry representing the current connection.
*/
+ @AnyThread
public @Nullable WifiEntry getConnectedWifiEntry() {
// TODO (b/70983952): Fill in this method.
return null;
@@ -199,13 +241,17 @@ public class WifiTracker2 implements LifecycleObserver {
* The currently connected entry is omitted and may be accessed through
* {@link #getConnectedWifiEntry()}
*/
+ @AnyThread
public @NonNull List<WifiEntry> getWifiEntries() {
- return mWifiEntries;
+ synchronized (mLock) {
+ return new ArrayList<>(mWifiEntries);
+ }
}
/**
* Returns a list of WifiEntries representing saved networks.
*/
+ @AnyThread
public @NonNull List<WifiEntry> getSavedWifiEntries() {
// TODO (b/70983952): Fill in this method.
return new ArrayList<>();
@@ -214,23 +260,74 @@ public class WifiTracker2 implements LifecycleObserver {
/**
* Returns a list of WifiEntries representing network subscriptions.
*/
+ @AnyThread
public @NonNull List<WifiEntry> getSubscriptionEntries() {
// TODO (b/70983952): Fill in this method.
return new ArrayList<>();
}
+ @WorkerThread
+ private void updateScanResults() {
+ mScanResultUpdater.update(mWifiManager.getScanResults());
+ if (isVerboseLoggingEnabled()) {
+ Log.v(TAG, "Updated scans: " + Arrays.toString(
+ mScanResultUpdater.getScanResults(mMaxScanAgeMillis).toArray()));
+ }
+ }
+
+ @WorkerThread
+ private void updateWifiEntries(boolean lastScanSucceeded) {
+ updateStandardWifiEntryCache(lastScanSucceeded);
+ // TODO (b/70983952): Update Passpoint/Suggestion entries here.
+ // updatePasspointWifiEntries();
+ // updateCarrierWifiEntries();
+ // updateSuggestionWifiEntries();
+ synchronized (mLock) {
+ mWifiEntries.clear();
+ mWifiEntries.addAll(mStandardWifiEntryCache.values());
+ // mWifiEntries.addAll(mPasspointWifiEntries);
+ // mWifiEntries.addAll(mCarrierWifiEntries);
+ // mWifiEntries.addAll(mSuggestionWifiEntries);
+ Collections.sort(mWifiEntries);
+ if (isVerboseLoggingEnabled()) {
+ Log.v(TAG, "Updated WifiEntries: " + Arrays.toString(mWifiEntries.toArray()));
+ }
+ }
+ }
+
/**
- * Updates mWifiState and notifies the listener.
+ * Updates mStandardWifiEntryCache with fresh scans.
*/
@WorkerThread
- private void updateWifiState() {
- mWifiState = mWifiManager.getWifiState();
- notifyOnWifiStateChanged();
+ private void updateStandardWifiEntryCache(boolean lastScanSucceeded) {
+ // If the current scan failed, use results from the previous scan to prevent flicker.
+ final List<ScanResult> scanResults = mScanResultUpdater.getScanResults(
+ lastScanSucceeded ? mMaxScanAgeMillis : mMaxScanAgeMillis + mScanIntervalMillis);
+ final Map<String, StandardWifiEntry> updatedStandardWifiEntries = new HashMap<>();
+
+ // Group scans by StandardWifiEntry Key
+ final Map<String, List<ScanResult>> scanResultsByKey = scanResults.stream()
+ .filter(scanResult -> !TextUtils.isEmpty(scanResult.SSID))
+ .collect(groupingBy(StandardWifiEntry::createStandardWifiEntryKey));
+
+ // Create or get cached StandardWifiEntry by key
+ for (String key : scanResultsByKey.keySet()) {
+ StandardWifiEntry entry = mStandardWifiEntryCache.get(key);
+ if (entry == null) {
+ entry = new StandardWifiEntry(mMainHandler, scanResultsByKey.get(key));
+ } else {
+ entry.updateScanResultInfo(scanResultsByKey.get(key));
+ }
+ updatedStandardWifiEntries.put(key, entry);
+ }
+ mStandardWifiEntryCache.clear();
+ mStandardWifiEntryCache.putAll(updatedStandardWifiEntries);
}
/**
* Posts onWifiEntryChanged callback on the main thread.
*/
+ @WorkerThread
private void notifyOnWifiEntriesChanged() {
if (mListener != null) {
mMainHandler.post(mListener::onWifiEntriesChanged);
@@ -240,6 +337,7 @@ public class WifiTracker2 implements LifecycleObserver {
/**
* Posts onWifiStateChanged callback on the main thread.
*/
+ @WorkerThread
private void notifyOnWifiStateChanged() {
if (mListener != null) {
mMainHandler.post(mListener::onWifiStateChanged);
@@ -247,6 +345,51 @@ public class WifiTracker2 implements LifecycleObserver {
}
/**
+ * Scanner to handle starting scans every SCAN_INTERVAL_MILLIS
+ */
+ private class Scanner extends Handler {
+ private static final int SCAN_RETRY_TIMES = 3;
+
+ private int mRetry = 0;
+
+ private Scanner(Looper looper) {
+ super(looper);
+ }
+
+ @AnyThread
+ private void start() {
+ if (isVerboseLoggingEnabled()) {
+ Log.v(TAG, "Scanner start");
+ }
+ post(this::postScan);
+ }
+
+ @AnyThread
+ private void stop() {
+ if (isVerboseLoggingEnabled()) {
+ Log.v(TAG, "Scanner stop");
+ }
+ mRetry = 0;
+ removeCallbacksAndMessages(null);
+ }
+
+ @WorkerThread
+ private void postScan() {
+ if (mWifiManager.startScan()) {
+ mRetry = 0;
+ } else if (++mRetry >= SCAN_RETRY_TIMES) {
+ // TODO(b/70983952): See if toast is needed here
+ if (isVerboseLoggingEnabled()) {
+ Log.v(TAG, "Scanner failed to start scan " + mRetry + " times!");
+ }
+ mRetry = 0;
+ return;
+ }
+ postDelayed(this::postScan, mScanIntervalMillis);
+ }
+ }
+
+ /**
* Listener for changes to the Wi-Fi state or lists of WifiEntries.
*
* These callbacks must be run on the MainThread.
diff --git a/libs/WifiTrackerLib/tests/Android.bp b/libs/WifiTrackerLib/tests/Android.bp
index 97eefb7a2..0abd40148 100644
--- a/libs/WifiTrackerLib/tests/Android.bp
+++ b/libs/WifiTrackerLib/tests/Android.bp
@@ -17,7 +17,6 @@ android_test {
srcs: ["src/**/*.java"],
static_libs: [
- "android-support-test",
"androidx.test.rules",
"frameworks-base-testutils",
"mockito-target-minus-junit4",
@@ -27,10 +26,7 @@ android_test {
libs: [
"android.test.mock",
- "Robolectric_all-target",
],
platform_apis: true,
-
- test_suites: ["device-tests"],
}
diff --git a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/ScanResultUpdaterTest.java b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/ScanResultUpdaterTest.java
index b92a764d7..b46153bd2 100644
--- a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/ScanResultUpdaterTest.java
+++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/ScanResultUpdaterTest.java
@@ -16,6 +16,8 @@
package com.android.wifitrackerlib;
+import static com.android.wifitrackerlib.TestUtils.buildScanResult;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
@@ -33,6 +35,7 @@ import java.util.Arrays;
import java.util.List;
public class ScanResultUpdaterTest {
+ private static final String SSID = "ssid";
private static final String BSSID_1 = "11:11:11:11:11:11";
private static final String BSSID_2 = "22:22:22:22:22:22";
private static final String BSSID_3 = "33:33:33:33:33:33";
@@ -47,28 +50,13 @@ public class ScanResultUpdaterTest {
when(mMockClock.millis()).thenReturn(NOW_MILLIS);
}
- private static ScanResult buildScanResult(String bssid, long timestampMs) {
- return new ScanResult(
- null,
- bssid,
- 0, // hessid
- 0, //anqpDomainId
- null, // osuProviders
- "", // capabilities
- 0,
- 0, // frequency
- timestampMs /* microsecond timestamp */);
- }
-
/**
* Verify that scan results of the same BSSID are merged to latest one.
*/
@Test
public void testGetScanResults_mergeSameBssid() {
- ScanResult oldResult = buildScanResult(
- BSSID_1, 10);
- ScanResult newResult = buildScanResult(
- BSSID_1, 20);
+ ScanResult oldResult = buildScanResult(SSID, BSSID_1, 10);
+ ScanResult newResult = buildScanResult(SSID, BSSID_1, 20);
// Add initial scan result. List should have 1 scan.
ScanResultUpdater sru = new ScanResultUpdater(mMockClock);
@@ -91,8 +79,8 @@ public class ScanResultUpdaterTest {
public void testGetScanResults_filtersOldScans() {
long maxScanAge = 15_000;
- ScanResult oldResult = buildScanResult(BSSID_1, NOW_MILLIS - (maxScanAge + 1));
- ScanResult newResult = buildScanResult(BSSID_2, NOW_MILLIS);
+ ScanResult oldResult = buildScanResult(SSID, BSSID_1, NOW_MILLIS - (maxScanAge + 1));
+ ScanResult newResult = buildScanResult(SSID, BSSID_2, NOW_MILLIS);
// Add a new scan result and an out-of-date scan result.
ScanResultUpdater sru = new ScanResultUpdater(mMockClock);
@@ -124,9 +112,9 @@ public class ScanResultUpdaterTest {
public void testConstructor_maxScanAge_filtersOldScans() {
ScanResultUpdater sru = new ScanResultUpdater(mMockClock, 15_000);
- ScanResult scan1 = buildScanResult(BSSID_1, NOW_MILLIS - 10_000);
- ScanResult scan2 = buildScanResult(BSSID_2, NOW_MILLIS - 15_000);
- ScanResult scan3 = buildScanResult(BSSID_3, NOW_MILLIS - 20_000);
+ ScanResult scan1 = buildScanResult(SSID, BSSID_1, NOW_MILLIS - 10_000);
+ ScanResult scan2 = buildScanResult(SSID, BSSID_2, NOW_MILLIS - 15_000);
+ ScanResult scan3 = buildScanResult(SSID, BSSID_3, NOW_MILLIS - 20_000);
sru.update(Arrays.asList(scan1, scan2, scan3));
@@ -143,9 +131,9 @@ public class ScanResultUpdaterTest {
public void testGetScanResults_overridesConstructorMaxScanAge() {
ScanResultUpdater sru = new ScanResultUpdater(mMockClock, 15_000);
- ScanResult scan1 = buildScanResult(BSSID_1, NOW_MILLIS - 10_000);
- ScanResult scan2 = buildScanResult(BSSID_2, NOW_MILLIS - 15_000);
- ScanResult scan3 = buildScanResult(BSSID_3, NOW_MILLIS - 20_000);
+ ScanResult scan1 = buildScanResult(SSID, BSSID_1, NOW_MILLIS - 10_000);
+ ScanResult scan2 = buildScanResult(SSID, BSSID_2, NOW_MILLIS - 15_000);
+ ScanResult scan3 = buildScanResult(SSID, BSSID_3, NOW_MILLIS - 20_000);
sru.update(Arrays.asList(scan1, scan2, scan3));
diff --git a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/StandardWifiEntryTest.java b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/StandardWifiEntryTest.java
new file mode 100644
index 000000000..8c07cec83
--- /dev/null
+++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/StandardWifiEntryTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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 com.android.wifitrackerlib;
+
+import static com.android.wifitrackerlib.TestUtils.buildScanResult;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.verify;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class StandardWifiEntryTest {
+ public static final int GOOD_RSSI = -50;
+ public static final int OKAY_RSSI = -60;
+ public static final int BAD_RSSI = -70;
+
+ @Mock private WifiEntry.WifiEntryCallback mMockListener;
+
+ private TestLooper mTestLooper;
+ private Handler mTestHandler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTestLooper = new TestLooper();
+ mTestHandler = new Handler(mTestLooper.getLooper());
+ }
+
+ /**
+ * Tests that constructing with an empty list of scans throws an exception
+ */
+ @Test
+ public void testConstructor_emptyScanList_throwsException() {
+ try {
+ new StandardWifiEntry(mTestHandler, new ArrayList<>());
+ fail("Empty scan list should have thrown exception");
+ } catch (IllegalArgumentException e) {
+ // Test succeeded
+ }
+ }
+
+ /**
+ * Tests that constructing with a list of scans with differing SSIDs throws an exception
+ */
+ @Test
+ public void testConstructor_mismatchedSsids_throwsException() {
+ try {
+ new StandardWifiEntry(mTestHandler, Arrays.asList(
+ buildScanResult("ssid0", "bssid0", 0, GOOD_RSSI),
+ buildScanResult("ssid1", "bssid1", 0, GOOD_RSSI)));
+ fail("Scan list with different SSIDs should have thrown exception");
+ } catch (IllegalArgumentException e) {
+ // Test succeeded
+ }
+ }
+
+ /**
+ * Tests that the level is set to the level of the strongest scan
+ */
+ @Test
+ public void testConstructor_setsBestLevel() {
+ final StandardWifiEntry entry = new StandardWifiEntry(mTestHandler, Arrays.asList(
+ buildScanResult("ssid", "bssid0", 0, GOOD_RSSI),
+ buildScanResult("ssid", "bssid1", 0, OKAY_RSSI),
+ buildScanResult("ssid", "bssid2", 0, BAD_RSSI)));
+
+ assertThat(entry.getLevel()).isEqualTo(
+ WifiManager.calculateSignalLevel(GOOD_RSSI, WifiManager.RSSI_LEVELS));
+ }
+
+ /**
+ * Tests that the security is set to the security capabilities of the scan
+ */
+ @Test
+ public void testConstructor_setsSecurity() {
+ final ScanResult unsecureScan = buildScanResult("ssid", "bssid", 0, GOOD_RSSI);
+ final ScanResult secureScan = buildScanResult("ssid", "bssid", 0, GOOD_RSSI);
+ secureScan.capabilities = "EAP";
+
+ final StandardWifiEntry unsecureEntry = new StandardWifiEntry(mTestHandler,
+ Arrays.asList(unsecureScan));
+ final StandardWifiEntry secureEntry = new StandardWifiEntry(mTestHandler,
+ Arrays.asList(secureScan));
+
+ assertThat(unsecureEntry.getSecurity()).isEqualTo(WifiEntry.SECURITY_NONE);
+ assertThat(secureEntry.getSecurity()).isEqualTo(WifiEntry.SECURITY_EAP);
+ }
+
+ /**
+ * Tests that updating with an empty list of scans throws an exception.
+ */
+ @Test
+ public void testUpdateScanResultInfo_emptyScanList_throwsException() {
+ final StandardWifiEntry entry = new StandardWifiEntry(mTestHandler, Arrays.asList(
+ buildScanResult("ssid", "bssid", 0, GOOD_RSSI))
+ );
+
+ try {
+ entry.updateScanResultInfo(new ArrayList<>());
+ fail("Empty scan list should have thrown exception");
+ } catch (IllegalArgumentException e) {
+ // Test succeeded
+ }
+ }
+
+ /**
+ * Tests that updating with a list of scans with differing SSIDs throws an exception
+ */
+ @Test
+ public void testUpdateScanResultInfo_mismatchedSsids_throwsException() {
+ final StandardWifiEntry entry = new StandardWifiEntry(mTestHandler, Arrays.asList(
+ buildScanResult("ssid0", "bssid0", 0, GOOD_RSSI)));
+
+ try {
+ entry.updateScanResultInfo(Arrays.asList(
+ buildScanResult("ssid1", "bssid1", 0, GOOD_RSSI)));
+ fail("Scan list with different SSIDs should have thrown exception");
+ } catch (IllegalArgumentException e) {
+ // Test succeeded
+ }
+ }
+
+ /**
+ * Tests that updating with a list of scans with differing security types throws an exception.
+ */
+ @Test
+ public void testUpdateScanResultInfo_mismatchedSecurity_throwsException() {
+ final StandardWifiEntry entry = new StandardWifiEntry(mTestHandler, Arrays.asList(
+ buildScanResult("ssid0", "bssid0", 0, GOOD_RSSI)));
+
+ try {
+ final ScanResult differentSecurityScan =
+ buildScanResult("ssid0", "bssid0", 0, GOOD_RSSI);
+ differentSecurityScan.capabilities = "EAP";
+ entry.updateScanResultInfo(Arrays.asList(differentSecurityScan));
+ fail("Scan list with different SSIDs should have thrown exception");
+ } catch (IllegalArgumentException e) {
+ // Test succeeded
+ }
+ }
+
+ /**
+ * Tests that the listener is notified after an update to the scan results
+ */
+ @Test
+ public void testUpdateScanResultInfo_notifiesListener() {
+ final StandardWifiEntry entry = new StandardWifiEntry(mTestHandler, Arrays.asList(
+ buildScanResult("ssid", "bssid", 0)));
+ entry.setListener(mMockListener);
+
+ entry.updateScanResultInfo(Arrays.asList(buildScanResult("ssid", "bssid", 1)));
+ mTestLooper.dispatchAll();
+
+ verify(mMockListener).onUpdated();
+ }
+
+ /**
+ * Tests that the level is updated after an update to the scan results
+ */
+ @Test
+ public void testUpdateScanResultInfo_updatesLevel() {
+ final StandardWifiEntry entry = new StandardWifiEntry(mTestHandler, Arrays.asList(
+ buildScanResult("ssid", "bssid", 0, BAD_RSSI)));
+
+ assertThat(entry.getLevel()).isEqualTo(
+ WifiManager.calculateSignalLevel(BAD_RSSI, WifiManager.RSSI_LEVELS));
+
+ entry.updateScanResultInfo(Arrays.asList(buildScanResult("ssid", "bssid", 0, GOOD_RSSI)));
+
+ assertThat(entry.getLevel()).isEqualTo(
+ WifiManager.calculateSignalLevel(GOOD_RSSI, WifiManager.RSSI_LEVELS));
+ }
+}
diff --git a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/TestUtils.java b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/TestUtils.java
new file mode 100644
index 000000000..547a5721e
--- /dev/null
+++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/TestUtils.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.wifitrackerlib;
+
+import android.net.wifi.ScanResult;
+
+/**
+ * Utility methods for testing purposes.
+ */
+class TestUtils {
+ /**
+ * Creates a mock scan result with SSID, BSSID, and timestamp.
+ */
+ static ScanResult buildScanResult(String ssid, String bssid, long timestampMillis) {
+ final ScanResult result = new ScanResult();
+ result.SSID = ssid;
+ result.BSSID = bssid;
+ result.timestamp = timestampMillis * 1000;
+ return result;
+ }
+
+ static ScanResult buildScanResult(String ssid, String bssid, long timestampMillis, int rssi) {
+ final ScanResult result = buildScanResult(ssid, bssid, timestampMillis);
+ result.level = rssi;
+ return result;
+ }
+}
diff --git a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/UtilsTest.java b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/UtilsTest.java
new file mode 100644
index 000000000..5b33d0ca6
--- /dev/null
+++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/UtilsTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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 com.android.wifitrackerlib;
+
+import static com.android.wifitrackerlib.TestUtils.buildScanResult;
+import static com.android.wifitrackerlib.Utils.getBestScanResultByLevel;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.wifi.ScanResult;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class UtilsTest {
+
+ @Test
+ public void testGetBestScanResult_emptyList_returnsNull() {
+ assertThat(getBestScanResultByLevel(new ArrayList<>())).isNull();
+ }
+
+ @Test
+ public void testGetBestScanResult_returnsBestRssiScan() {
+ final ScanResult bestResult = buildScanResult("ssid", "bssid", 0, -50);
+ final ScanResult okayResult = buildScanResult("ssid", "bssid", 0, -60);
+ final ScanResult badResult = buildScanResult("ssid", "bssid", 0, -70);
+
+ assertThat(getBestScanResultByLevel(Arrays.asList(bestResult, okayResult, badResult)))
+ .isEqualTo(bestResult);
+ }
+
+ @Test
+ public void testGetBestScanResult_singleScan_returnsScan() {
+ final ScanResult scan = buildScanResult("ssid", "bssid", 0, -50);
+
+ assertThat(getBestScanResultByLevel(Arrays.asList(scan))).isEqualTo(scan);
+ }
+}
diff --git a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java
index 410f18277..8cbb04dab 100644
--- a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java
+++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java
@@ -16,6 +16,9 @@
package com.android.wifitrackerlib;
+import static com.android.wifitrackerlib.StandardWifiEntry.createStandardWifiEntryKey;
+import static com.android.wifitrackerlib.TestUtils.buildScanResult;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -28,6 +31,7 @@ import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
+import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.test.TestLooper;
@@ -41,9 +45,17 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.time.Clock;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
public class WifiTracker2Test {
+ private static final long START_MILLIS = 123_456_789;
+
+ private static final long MAX_SCAN_AGE_MILLIS = 15_000;
+ private static final long SCAN_INTERVAL_MILLIS = 10_000;
+
@Mock private Lifecycle mMockLifecycle;
@Mock private Context mMockContext;
@Mock private WifiManager mMockWifiManager;
@@ -52,7 +64,7 @@ public class WifiTracker2Test {
@Mock private Clock mMockClock;
@Mock private WifiTracker2.WifiTrackerCallback mMockWifiTrackerCallback;
- private TestLooper mTestLooper = new TestLooper();
+ private TestLooper mTestLooper;
private final ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
ArgumentCaptor.forClass(BroadcastReceiver.class);
@@ -67,14 +79,19 @@ public class WifiTracker2Test {
testHandler,
testHandler,
mMockClock,
- 15_000,
- 10_000,
+ MAX_SCAN_AGE_MILLIS,
+ SCAN_INTERVAL_MILLIS,
mMockWifiTrackerCallback);
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+
+ mTestLooper = new TestLooper();
+
+ when(mMockWifiManager.getScanResults()).thenReturn(new ArrayList<>());
+ when(mMockClock.millis()).thenReturn(START_MILLIS);
}
/**
@@ -82,17 +99,19 @@ public class WifiTracker2Test {
*/
@Test
public void testWifiStateChangeBroadcast_updatesWifiState() {
- WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ final WifiTracker2 wifiTracker2 = createTestWifiTracker2();
wifiTracker2.onStart();
verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
any(), any(), any());
+ // Set the wifi state to disabled
when(mMockWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
assertThat(wifiTracker2.getWifiState()).isEqualTo(WifiManager.WIFI_STATE_DISABLED);
+ // Change the wifi state to enabled
when(mMockWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
@@ -106,7 +125,7 @@ public class WifiTracker2Test {
*/
@Test
public void testWifiStateChangeBroadcast_NotifiesListener() {
- WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ final WifiTracker2 wifiTracker2 = createTestWifiTracker2();
wifiTracker2.onStart();
verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
any(), any(), any());
@@ -123,13 +142,16 @@ public class WifiTracker2Test {
*/
@Test
public void testOnStart_setsWifiState() {
- WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ final WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+
+ // Set the wifi state to disabled
when(mMockWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
wifiTracker2.onStart();
mTestLooper.dispatchAll();
assertThat(wifiTracker2.getWifiState()).isEqualTo(WifiManager.WIFI_STATE_DISABLED);
+ // Change the wifi state to enabled
wifiTracker2.onStop();
when(mMockWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
wifiTracker2.onStart();
@@ -137,4 +159,152 @@ public class WifiTracker2Test {
assertThat(wifiTracker2.getWifiState()).isEqualTo(WifiManager.WIFI_STATE_ENABLED);
}
+
+ /**
+ * Tests that receiving a scan results available broadcast notifies the listener.
+ */
+ @Test
+ public void testScanResultsAvailableAction_notifiesListener() {
+ final WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ wifiTracker2.onStart();
+ verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+ any(), any(), any());
+
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+ mTestLooper.dispatchAll();
+
+ verify(mMockWifiTrackerCallback, atLeastOnce()).onWifiEntriesChanged();
+ }
+
+ /**
+ * Tests that an empty list of WifiEntries is returned if no scans are available.
+ */
+ @Test
+ public void testGetWifiEntries_noScans_emptyList() {
+ final WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ wifiTracker2.onStart();
+ verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+ any(), any(), any());
+
+ when(mMockWifiManager.getScanResults()).thenReturn(new ArrayList<>());
+
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+
+ assertThat(wifiTracker2.getWifiEntries()).isEmpty();
+ }
+
+
+ /**
+ * Tests that a StandardWifiEntry is returned by getWifiEntries() for each non-null, non-empty
+ * SSID/Security pair in the tracked scan results.
+ */
+ @Test
+ public void testGetWifiEntries_wifiNetworkEntries_createdForEachSsidAndSecurityPair() {
+ final WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ wifiTracker2.onStart();
+ verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+ any(), any(), any());
+
+ final ScanResult openNetwork = buildScanResult("Open Network", "bssid0", START_MILLIS);
+ final ScanResult openNetworkDup = buildScanResult("Open Network", "bssid1", START_MILLIS);
+ final ScanResult secureNetwork = buildScanResult("Secure Network", "bssid2", START_MILLIS);
+ secureNetwork.capabilities = "EAP";
+
+ when(mMockWifiManager.getScanResults()).thenReturn(Arrays.asList(
+ openNetwork,
+ openNetworkDup,
+ secureNetwork,
+ // Ignore null and empty SSIDs
+ buildScanResult(null, "bssidNull", START_MILLIS),
+ buildScanResult("", "bssidEmpty", START_MILLIS)));
+
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+ List<String> seenKeys = new ArrayList<>();
+ for (WifiEntry wifiEntry : wifiTracker2.getWifiEntries()) {
+ seenKeys.add(wifiEntry.getKey());
+ }
+
+ assertThat(seenKeys).containsExactly(
+ createStandardWifiEntryKey(openNetwork),
+ createStandardWifiEntryKey(secureNetwork));
+ }
+
+ /**
+ * Tests that old WifiEntries are timed out if their scans are older than the max scan age.
+ */
+ @Test
+ public void testGetWifiEntries_wifiNetworkEntries_oldEntriesTimedOut() {
+ final WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ wifiTracker2.onStart();
+ verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+ any(), any(), any());
+
+ // Initial entries
+ when(mMockWifiManager.getScanResults()).thenReturn(Arrays.asList(
+ buildScanResult("ssid0", "bssid0", START_MILLIS),
+ buildScanResult("ssid1", "bssid1", START_MILLIS),
+ buildScanResult("ssid2", "bssid2", START_MILLIS),
+ buildScanResult("ssid3", "bssid3", START_MILLIS),
+ buildScanResult("ssid4", "bssid4", START_MILLIS)));
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+
+ // Advance clock to max scan age. Entries should still be valid.
+ when(mMockClock.millis()).thenReturn(START_MILLIS + MAX_SCAN_AGE_MILLIS);
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+ assertThat(wifiTracker2.getWifiEntries()).isNotEmpty();
+
+
+ // Advance the clock to time out old entries
+ when(mMockClock.millis()).thenReturn(START_MILLIS + MAX_SCAN_AGE_MILLIS + 1);
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+
+ // All entries timed out
+ assertThat(wifiTracker2.getWifiEntries()).isEmpty();
+ }
+
+ /**
+ * Tests that a failed scan will result in extending the max scan age by the scan interval.
+ * This is to allow the WifiEntry list to stay stable and not clear out if a single scan fails.
+ */
+ @Test
+ public void testGetWifiEntries_wifiNetworkEntries_useOldEntriesOnFailedScan() {
+ final WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ wifiTracker2.onStart();
+ verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+ any(), any(), any());
+
+ // Initial entries
+ when(mMockWifiManager.getScanResults()).thenReturn(Arrays.asList(
+ buildScanResult("ssid0", "bssid0", START_MILLIS),
+ buildScanResult("ssid1", "bssid1", START_MILLIS),
+ buildScanResult("ssid2", "bssid2", START_MILLIS),
+ buildScanResult("ssid3", "bssid3", START_MILLIS),
+ buildScanResult("ssid4", "bssid4", START_MILLIS)));
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
+ final List<WifiEntry> previousEntries = wifiTracker2.getWifiEntries();
+
+ // Advance the clock to time out old entries and simulate failed scan
+ when(mMockClock.millis())
+ .thenReturn(START_MILLIS + MAX_SCAN_AGE_MILLIS + SCAN_INTERVAL_MILLIS);
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
+ .putExtra(WifiManager.EXTRA_RESULTS_UPDATED, false));
+
+ // Failed scan should result in old WifiEntries still being shown
+ assertThat(previousEntries).containsExactlyElementsIn(wifiTracker2.getWifiEntries());
+
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
+ .putExtra(WifiManager.EXTRA_RESULTS_UPDATED, true));
+
+ // Successful scan should time out old entries.
+ assertThat(wifiTracker2.getWifiEntries()).isEmpty();
+ }
}