summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuang Luong <qal@google.com>2019-07-03 10:58:54 -0700
committerQuang Luong <qal@google.com>2019-08-23 10:48:21 -0700
commita0a007f771192ed6f09adf28219f209876cac6bd (patch)
tree39345d8ade3361c311149db6463af90fbe9eb1e3
parent6f1f6d00fcb241035d8d183b502482aa2a568a1b (diff)
Created WifiTracker2 and WifiEntry
Created WifiTracker2 and WifiEntry to replace WifiTracker/AccessPoint. Implemented WifiTracker2 wifi state change callbacks. Use Clock instead of SystemClock in ScanResultUpdater. Test: atest WifiTracker2Test, atest ScanResultUpdaterTest Bug: 70983952 Change-Id: Iac1c73b54b5df9d50aed0480cbf483be66c7eeb9
-rw-r--r--libs/WifiTrackerLib/Android.bp5
-rw-r--r--libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java20
-rw-r--r--libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java375
-rw-r--r--libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java272
-rw-r--r--libs/WifiTrackerLib/tests/Android.bp8
-rw-r--r--libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/ScanResultUpdaterTest.java73
-rw-r--r--libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java140
7 files changed, 849 insertions, 44 deletions
diff --git a/libs/WifiTrackerLib/Android.bp b/libs/WifiTrackerLib/Android.bp
index 501a956d2..412f182dd 100644
--- a/libs/WifiTrackerLib/Android.bp
+++ b/libs/WifiTrackerLib/Android.bp
@@ -1,4 +1,9 @@
android_library {
name: "WifiTrackerLib",
srcs: ["src/**/*.java"],
+ static_libs: [
+ "androidx.preference_preference",
+ ],
+
+ min_sdk_version: "21",
}
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java
index 6df4f266c..addab0c08 100644
--- a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java
+++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/ScanResultUpdater.java
@@ -17,8 +17,8 @@
package com.android.wifitrackerlib;
import android.net.wifi.ScanResult;
-import android.os.SystemClock;
+import java.time.Clock;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -30,22 +30,26 @@ import java.util.List;
*/
public class ScanResultUpdater {
private HashMap<String, ScanResult> mScanResultsByBssid = new HashMap<>();
- private long mMaxScanAgeMillis;
- private Object mLock = new Object();
+ private final long mMaxScanAgeMillis;
+ private final Object mLock = new Object();
+ private final Clock mClock;
/**
* Creates a ScanResultUpdater with no max scan age.
+ *
+ * @param clock Elapsed real time Clock to compare with ScanResult timestamps.
*/
- public ScanResultUpdater() {
- this(Long.MAX_VALUE);
+ public ScanResultUpdater(Clock clock) {
+ this(clock, Long.MAX_VALUE);
}
/**
* Creates a ScanResultUpdater with a max scan age in milliseconds. Scans older than this limit
* will be pruned upon update/retrieval to keep the size of the scan list down.
*/
- public ScanResultUpdater(long maxScanAgeMillis) {
+ public ScanResultUpdater(Clock clock, long maxScanAgeMillis) {
mMaxScanAgeMillis = maxScanAgeMillis;
+ mClock = clock;
}
/**
@@ -83,7 +87,7 @@ public class ScanResultUpdater {
synchronized (mLock) {
List<ScanResult> ageFilteredResults = new ArrayList<>();
for (ScanResult result : mScanResultsByBssid.values()) {
- if (SystemClock.elapsedRealtime() - result.timestamp <= maxScanAgeMillis) {
+ if (mClock.millis() - result.timestamp <= maxScanAgeMillis) {
ageFilteredResults.add(result);
}
}
@@ -94,7 +98,7 @@ public class ScanResultUpdater {
private void evictOldScans() {
synchronized (mLock) {
mScanResultsByBssid.entrySet().removeIf((entry) ->
- SystemClock.elapsedRealtime() - entry.getValue().timestamp > mMaxScanAgeMillis);
+ mClock.millis() - entry.getValue().timestamp > mMaxScanAgeMillis);
}
}
}
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java
new file mode 100644
index 000000000..3742e42e4
--- /dev/null
+++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiEntry.java
@@ -0,0 +1,375 @@
+/*
+ * 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 androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Abstract base class for an entry representing a Wi-Fi network in a Wi-Fi picker.
+ *
+ * Clients implementing a Wi-Fi picker should receive WifiEntry objects from WifiTracker2, and rely
+ * on the given API for all user-displayable information and actions on the represented network.
+ */
+public abstract class WifiEntry implements Comparable<WifiEntry> {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ SECURITY_NONE,
+ SECURITY_WEP,
+ SECURITY_PSK,
+ SECURITY_EAP,
+ SECURITY_OWE,
+ SECURITY_SAE,
+ SECURITY_EAP_SUITE_B,
+ SECURITY_PSK_SAE_TRANSITION,
+ SECURITY_OWE_TRANSITION,
+ SECURITY_MAX_VAL
+ })
+
+ public @interface Security {}
+
+ public static final int SECURITY_NONE = 0;
+ public static final int SECURITY_WEP = 1;
+ public static final int SECURITY_PSK = 2;
+ public static final int SECURITY_EAP = 3;
+ public static final int SECURITY_OWE = 4;
+ public static final int SECURITY_SAE = 5;
+ public static final int SECURITY_EAP_SUITE_B = 6;
+ public static final int SECURITY_PSK_SAE_TRANSITION = 7;
+ public static final int SECURITY_OWE_TRANSITION = 8;
+ public static final int SECURITY_MAX_VAL = 9; // Has to be the last
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ CONNECTED_STATE_DISCONNECTED,
+ CONNECTED_STATE_CONNECTED,
+ CONNECTED_STATE_CONNECTING
+ })
+
+ public @interface ConnectedState {}
+
+ public static final int CONNECTED_STATE_DISCONNECTED = 0;
+ public static final int CONNECTED_STATE_CONNECTING = 1;
+ public static final int CONNECTED_STATE_CONNECTED = 2;
+
+ // Wi-Fi signal levels for displaying signal strength.
+ public static final int WIFI_LEVEL_MIN = 0;
+ public static final int WIFI_LEVEL_MAX = 4;
+ public static final int WIFI_LEVEL_UNREACHABLE = -1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ METERED_CHOICE_AUTO,
+ METERED_CHOICE_METERED,
+ METERED_CHOICE_UNMETERED,
+ METERED_CHOICE_UNKNOWN
+ })
+
+ public @interface MeteredChoice {}
+
+ // User's choice whether to treat a network as metered.
+ public static final int METERED_CHOICE_AUTO = 0;
+ public static final int METERED_CHOICE_METERED = 1;
+ public static final int METERED_CHOICE_UNMETERED = 2;
+ public static final int METERED_CHOICE_UNKNOWN = 3;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ PRIVACY_DEVICE_MAC,
+ PRIVACY_RANDOMIZED_MAC,
+ PRIVACY_UNKNOWN
+ })
+
+ public @interface Privacy {}
+
+ public static final int PRIVACY_DEVICE_MAC = 0;
+ public static final int PRIVACY_RANDOMIZED_MAC = 1;
+ public static final int PRIVACY_UNKNOWN = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ FREQUENCY_2_4_GHZ,
+ FREQUENCY_5_GHZ,
+ FREQUENCY_6_GHZ,
+ FREQUENCY_UNKNOWN
+ })
+
+ public @interface Frequency {}
+
+ public static final int FREQUENCY_2_4_GHZ = 2_400;
+ public static final int FREQUENCY_5_GHZ = 5_000;
+ public static final int FREQUENCY_6_GHZ = 6_000;
+ public static final int FREQUENCY_UNKNOWN = -1;
+
+ // Callback associated with this WifiEntry. Subclasses should call its methods appropriately.
+ protected WifiEntryCallback mListener;
+
+ // Info available for all WifiEntries //
+
+ /** The unique key defining a WifiEntry */
+ public abstract String getKey();
+
+ /** Returns connection state of the network defined by the CONNECTED_STATE constants */
+ @ConnectedState
+ public abstract int getConnectedState();
+
+ /** Returns the display title. This is most commonly the SSID of a network. */
+ public abstract String getTitle();
+
+ /** Returns the display summary */
+ public abstract String getSummary();
+
+ /**
+ * Returns the signal strength level within [WIFI_LEVEL_MIN, WIFI_LEVEL_MAX].
+ * A value of WIFI_LEVEL_UNREACHABLE indicates an out of range network.
+ */
+ public abstract int getLevel();
+
+ /** Returns the security type defined by the SECURITY constants */
+ @Security
+ public abstract int getSecurity();
+
+ /**
+ * Indicates when a network is metered or the user marked the network as metered.
+ */
+ public abstract boolean isMetered();
+
+ /**
+ * Returns the ConnectedInfo object pertaining to an active connection.
+ *
+ * Returns null if getConnectedState() != CONNECTED_STATE_CONNECTED.
+ */
+ public abstract ConnectedInfo getConnectedInfo();
+
+ /**
+ * Info associated with the active connection.
+ */
+ public static class ConnectedInfo {
+ @Frequency
+ public int frequencyMhz;
+ public List<String> dnsServers;
+ public int linkSpeedMbps;
+ public String ipAddress;
+ public List<String> ipv6Addresses;
+ public String gateway;
+ public String subnetMask;
+ }
+
+ // User actions on a network
+
+ /** Returns whether the entry should show a connect option */
+ public abstract boolean canConnect();
+ /** Connects to the network */
+ public abstract void connect();
+
+ /** Returns whether the entry should show a disconnect option */
+ public abstract boolean canDisconnect();
+ /** Disconnects from the network */
+ public abstract void disconnect();
+
+ /** Returns whether the entry should show a forget option */
+ public abstract boolean canForget();
+ /** Forgets the network */
+ public abstract void forget();
+
+ // Modifiable settings
+
+ /** Returns whether the entry should show a password input */
+ public abstract boolean canSetPassword();
+ /** Sets the user's password to a network */
+ public abstract void setPassword(@NonNull String password);
+
+ /**
+ * Returns the user's choice whether to treat a network as metered,
+ * defined by the METERED_CHOICE constants
+ */
+ @MeteredChoice
+ public abstract int getMeteredChoice();
+ /** Returns whether the entry should let the user choose the metered treatment of a network */
+ public abstract boolean canSetMeteredChoice();
+ /**
+ * Sets the user's choice for treating a network as metered,
+ * defined by the METERED_CHOICE constants
+ */
+ public abstract void setMeteredChoice(@MeteredChoice int meteredChoice);
+
+ /** Returns whether the entry should let the user choose the MAC randomization setting */
+ public abstract boolean canSetPrivacy();
+ /** Returns the MAC randomization setting defined by the PRIVACY constants */
+ @Privacy
+ public abstract int getPrivacy();
+ /** Sets the user's choice for MAC randomization defined by the PRIVACY constants */
+ public abstract void setPrivacy(@Privacy int privacy);
+
+ /** Returns whether the network has auto-join enabled */
+ public abstract boolean isAutoJoinEnabled();
+ /** Returns whether the user can enable/disable auto-join */
+ public abstract boolean canSetAutoJoinEnabled();
+ /** Sets whether a network will be auto-joined or not */
+ public abstract void setAutoJoinEnabled(boolean enabled);
+
+ /** Returns the ProxySettings for the network */
+ public abstract ProxySettings getProxySettings();
+ /** Returns whether the user can modify the ProxySettings for the network */
+ public abstract boolean canSetProxySettings();
+ /** Sets the ProxySettinsg for the network */
+ public abstract void setProxySettings(@NonNull ProxySettings proxySettings);
+
+ /** Returns the IpSettings for the network */
+ public abstract IpSettings getIpSettings();
+ /** Returns whether the user can set the IpSettings for the network */
+ public abstract boolean canSetIpSettings();
+ /** Sets the IpSettings for the network */
+ public abstract void setIpSettings(@NonNull IpSettings ipSettings);
+
+ /**
+ * Data class used for proxy settings
+ */
+ public static class ProxySettings {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ TYPE_NONE,
+ TYPE_MANUAL,
+ TYPE_PROXY_AUTOCONFIG
+ })
+
+ public @interface Type {}
+
+ public static final int TYPE_NONE = 0;
+ public static final int TYPE_MANUAL = 1;
+ public static final int TYPE_PROXY_AUTOCONFIG = 2;
+
+ @Type
+ public int type;
+ public String hostname;
+ public String port;
+ public String bypassAddresses;
+ public String pacUrl;
+ }
+
+ /**
+ * Data class used for IP settings
+ */
+ public static class IpSettings {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ TYPE_DCHP,
+ TYPE_STATIC
+ })
+
+ public @interface Type {}
+
+ public static final int TYPE_DCHP = 0;
+ public static final int TYPE_STATIC = 1;
+
+ @Type
+ public int type;
+ public String ipAddress;
+ public String gateway;
+ public int prefixLength;
+ public String dns1;
+ public String dns2;
+ }
+
+ /**
+ * Sets the callback listener for WifiEntryCallback methods.
+ * Subsequent calls will overwrite the previous listener.
+ */
+ public void setListener(WifiEntryCallback listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Listener for changes to the state of the WifiEntry or the result of actions on the WifiEntry.
+ */
+ public interface WifiEntryCallback {
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ CONNECT_STATUS_SUCCESS,
+ CONNECT_STATUS_FAILURE_NO_PASSWORD,
+ CONNECT_STATUS_FAILURE_UNKNOWN
+ })
+
+ public @interface ConnectStatus {}
+
+ int CONNECT_STATUS_SUCCESS = 0;
+ int CONNECT_STATUS_FAILURE_NO_PASSWORD = 1;
+ int CONNECT_STATUS_FAILURE_UNKNOWN = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ CONNECT_STATUS_SUCCESS,
+ CONNECT_STATUS_FAILURE_UNKNOWN
+ })
+
+ public @interface DisconnectStatus {}
+
+ int DISCONNECT_STATUS_SUCCESS = 0;
+ int DISCONNECT_STATUS_FAILURE_UNKNOWN = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ FORGET_STATUS_SUCCESS,
+ FORGET_STATUS_FAILURE_UNKNOWN
+ })
+
+ public @interface ForgetStatus {}
+
+ int FORGET_STATUS_SUCCESS = 0;
+ int FORGET_STATUS_FAILURE_UNKNOWN = 1;
+
+ /**
+ * Indicates the state of the WifiEntry has changed and clients may retrieve updates through
+ * the WifiEntry getter methods.
+ */
+ void onUpdated();
+
+ /**
+ * Result of the connect request indicated by the CONNECT_STATUS constants.
+ */
+ void onConnectResult(@ConnectStatus int status);
+
+ /**
+ * Result of the disconnect request indicated by the DISCONNECT_STATUS constants.
+ */
+ void onDisconnectResult(@DisconnectStatus int status);
+
+ /**
+ * Result of the forget request indicated by the FORGET_STATUS constants.
+ */
+ void onForgetResult(@ForgetStatus int status);
+ }
+
+ // TODO (b/70983952) Come up with a sorting scheme that does the right thing.
+ @Override
+ public int compareTo(@NonNull WifiEntry other) {
+ if (getLevel() > other.getLevel()) return -1;
+ if (getLevel() < other.getLevel()) return 1;
+
+ return getTitle().compareTo(other.getTitle());
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (!(other instanceof WifiEntry)) return false;
+ return getKey().equals(((WifiEntry) other).getKey());
+ }
+}
diff --git a/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java
new file mode 100644
index 000000000..947b26409
--- /dev/null
+++ b/libs/WifiTrackerLib/src/com/android/wifitrackerlib/WifiTracker2.java
@@ -0,0 +1,272 @@
+/*
+ * 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.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.WifiManager;
+import android.os.Handler;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.OnLifecycleEvent;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 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.
+ */
+public class WifiTracker2 implements LifecycleObserver {
+
+ private static final String TAG = "WifiTracker2";
+
+ public static boolean sVerboseLogging;
+
+ private static boolean isVerboseLoggingEnabled() {
+ return WifiTracker2.sVerboseLogging || Log.isLoggable(TAG, Log.VERBOSE);
+ }
+
+ private final Context mContext;
+ private final WifiManager mWifiManager;
+ private final ConnectivityManager mConnectivityManager;
+ private final NetworkScoreManager mNetworkScoreManager;
+ private final Handler mMainHandler;
+ private final Handler mWorkerHandler;
+ private final long mMaxScanAgeMillis;
+ private final long mScanIntervalMillis;
+ private final WifiTrackerCallback mListener;
+ 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);
+ * WifiManager.RSSI_CHANGED_ACTION);
+ */
+ @Override
+ @WorkerThread
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if (isVerboseLoggingEnabled()) {
+ Log.i(TAG, "Received broadcast: " + action);
+ }
+
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+ updateWifiState();
+ }
+ }
+ };
+
+ private final ScanResultUpdater mScanResultUpdater;
+
+ // Lock object for mWifiEntries
+ private final Object mLock = new Object();
+
+ private int mWifiState;
+ @GuardedBy("mLock")
+ private final List<WifiEntry> mWifiEntries;
+
+ /**
+ * Constructor for WifiTracker2.
+ *
+ * @param lifecycle Lifecycle this is tied to for lifecycle callbacks.
+ * @param context Context to retrieve WifiManager and resource strings.
+ * @param wifiManager Provides all Wi-Fi info.
+ * @param connectivityManager Provides network info.
+ * @param networkScoreManager Provides network scores for network badging.
+ * @param mainHandler Handler for processing listener callbacks.
+ * @param workerHandler Handler for processing all broadcasts and running the Scanner.
+ * @param clock Clock used for evaluating the age of scans
+ * @param maxScanAgeMillis Max age for tracked WifiEntries.
+ * @param scanIntervalMillis Interval between initiating scans.
+ * @param listener WifiTrackerCallback listening on changes to WifiTracker2 data.
+ */
+ public WifiTracker2(@NonNull Lifecycle lifecycle, @NonNull Context context,
+ @NonNull WifiManager wifiManager,
+ @NonNull ConnectivityManager connectivityManager,
+ @NonNull NetworkScoreManager networkScoreManager,
+ @NonNull Handler mainHandler,
+ @NonNull Handler workerHandler,
+ @NonNull Clock clock,
+ long maxScanAgeMillis,
+ long scanIntervalMillis,
+ @Nullable WifiTrackerCallback listener) {
+ lifecycle.addObserver(this);
+ mContext = context;
+ mWifiManager = wifiManager;
+ mConnectivityManager = connectivityManager;
+ mNetworkScoreManager = networkScoreManager;
+ mMainHandler = mainHandler;
+ mWorkerHandler = workerHandler;
+ mMaxScanAgeMillis = maxScanAgeMillis;
+ mScanIntervalMillis = scanIntervalMillis;
+ mListener = listener;
+
+ mScanResultUpdater = new ScanResultUpdater(clock, maxScanAgeMillis * 2);
+
+ mWifiState = WifiManager.WIFI_STATE_DISABLED;
+ mWifiEntries = new ArrayList<>();
+
+ sVerboseLogging = mWifiManager.getVerboseLoggingLevel() > 0;
+ }
+
+ /**
+ * Registers the broadcast receiver and network callbacks and starts the scanning mechanism.
+ */
+ @OnLifecycleEvent(Lifecycle.Event.ON_START)
+ @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);
+ mContext.registerReceiver(mBroadcastReceiver, filter,
+ /* broadcastPermission */ null, mWorkerHandler);
+
+ // 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.
+ });
+ }
+
+ /**
+ * Unregisters the broadcast receiver, network callbacks, and pauses the scanning mechanism.
+ */
+ @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+ @MainThread
+ public void onStop() {
+ // TODO (b/70983952): Unregister score cache and receivers for network callbacks.
+ // TODO (b/70983952): Pause scanner here.
+ mContext.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ /**
+ * Returns the state of Wi-Fi as one of the following values.
+ *
+ * <li>{@link WifiManager#WIFI_STATE_DISABLED}</li>
+ * <li>{@link WifiManager#WIFI_STATE_ENABLED}</li>
+ * <li>{@link WifiManager#WIFI_STATE_DISABLING}</li>
+ * <li>{@link WifiManager#WIFI_STATE_ENABLING}</li>
+ * <li>{@link WifiManager#WIFI_STATE_UNKNOWN}</li>
+ */
+ public int getWifiState() {
+ return mWifiState;
+ }
+
+ /**
+ * Returns the WifiEntry representing the current connection.
+ */
+ public @Nullable WifiEntry getConnectedWifiEntry() {
+ // TODO (b/70983952): Fill in this method.
+ return null;
+ }
+
+ /**
+ * Returns a list of in-range WifiEntries.
+ *
+ * The currently connected entry is omitted and may be accessed through
+ * {@link #getConnectedWifiEntry()}
+ */
+ public @NonNull List<WifiEntry> getWifiEntries() {
+ return mWifiEntries;
+ }
+
+ /**
+ * Returns a list of WifiEntries representing saved networks.
+ */
+ public @NonNull List<WifiEntry> getSavedWifiEntries() {
+ // TODO (b/70983952): Fill in this method.
+ return new ArrayList<>();
+ }
+
+ /**
+ * Returns a list of WifiEntries representing network subscriptions.
+ */
+ public @NonNull List<WifiEntry> getSubscriptionEntries() {
+ // TODO (b/70983952): Fill in this method.
+ return new ArrayList<>();
+ }
+
+ /**
+ * Updates mWifiState and notifies the listener.
+ */
+ @WorkerThread
+ private void updateWifiState() {
+ mWifiState = mWifiManager.getWifiState();
+ notifyOnWifiStateChanged();
+ }
+
+ /**
+ * Posts onWifiEntryChanged callback on the main thread.
+ */
+ private void notifyOnWifiEntriesChanged() {
+ if (mListener != null) {
+ mMainHandler.post(mListener::onWifiEntriesChanged);
+ }
+ }
+
+ /**
+ * Posts onWifiStateChanged callback on the main thread.
+ */
+ private void notifyOnWifiStateChanged() {
+ if (mListener != null) {
+ mMainHandler.post(mListener::onWifiStateChanged);
+ }
+ }
+
+ /**
+ * Listener for changes to the Wi-Fi state or lists of WifiEntries.
+ *
+ * These callbacks must be run on the MainThread.
+ *
+ * TODO (b/70983952): Investigate need for callbacks for saved/subscription entry updates.
+ */
+ public interface WifiTrackerCallback {
+ /**
+ * Called when there are changes to
+ * {@link #getConnectedWifiEntry()}
+ * {@link #getWifiEntries()}
+ */
+ @MainThread
+ void onWifiEntriesChanged();
+
+ /**
+ * Called when the state of Wi-Fi has changed. The new value can be read through
+ * {@link #getWifiState()}
+ */
+ @MainThread
+ void onWifiStateChanged();
+ }
+}
diff --git a/libs/WifiTrackerLib/tests/Android.bp b/libs/WifiTrackerLib/tests/Android.bp
index e8f854893..97eefb7a2 100644
--- a/libs/WifiTrackerLib/tests/Android.bp
+++ b/libs/WifiTrackerLib/tests/Android.bp
@@ -19,9 +19,17 @@ android_test {
static_libs: [
"android-support-test",
"androidx.test.rules",
+ "frameworks-base-testutils",
+ "mockito-target-minus-junit4",
+ "truth-prebuilt",
"WifiTrackerLib",
],
+ 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 39d6bf3f8..b92a764d7 100644
--- a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/ScanResultUpdaterTest.java
+++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/ScanResultUpdaterTest.java
@@ -16,16 +16,19 @@
package com.android.wifitrackerlib;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.equalTo;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
import android.net.wifi.ScanResult;
-import android.os.SystemClock;
+import org.junit.Before;
import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import java.time.Clock;
import java.util.Arrays;
import java.util.List;
@@ -33,6 +36,16 @@ public class ScanResultUpdaterTest {
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";
+ private static final long NOW_MILLIS = 123_456_789;
+
+ @Mock private Clock mMockClock;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mMockClock.millis()).thenReturn(NOW_MILLIS);
+ }
private static ScanResult buildScanResult(String bssid, long timestampMs) {
return new ScanResult(
@@ -58,20 +71,17 @@ public class ScanResultUpdaterTest {
BSSID_1, 20);
// Add initial scan result. List should have 1 scan.
- ScanResultUpdater sru = new ScanResultUpdater();
+ ScanResultUpdater sru = new ScanResultUpdater(mMockClock);
sru.update(Arrays.asList(oldResult));
- assertThat(sru.getScanResults().size(), equalTo(1));
- assertThat(sru.getScanResults().get(0), equalTo(oldResult));
+ assertThat(sru.getScanResults()).containsExactly(oldResult);
// Add new scan result. Old scan result should be replaced.
sru.update(Arrays.asList(newResult));
- assertThat(sru.getScanResults().size(), equalTo(1));
- assertThat(sru.getScanResults().get(0), equalTo(newResult));
+ assertThat(sru.getScanResults()).containsExactly(newResult);
// Add old scan result back. New scan result should still remain.
sru.update(Arrays.asList(oldResult));
- assertThat(sru.getScanResults().size(), equalTo(1));
- assertThat(sru.getScanResults().get(0), equalTo(newResult));
+ assertThat(sru.getScanResults()).containsExactly(newResult);
}
/**
@@ -79,21 +89,17 @@ public class ScanResultUpdaterTest {
*/
@Test
public void testGetScanResults_filtersOldScans() {
- long now = SystemClock.elapsedRealtime();
long maxScanAge = 15_000;
- ScanResult oldResult = buildScanResult(
- BSSID_1, now - (maxScanAge + 1));
- ScanResult newResult = buildScanResult(
- BSSID_2, now);
+ ScanResult oldResult = buildScanResult(BSSID_1, NOW_MILLIS - (maxScanAge + 1));
+ ScanResult newResult = buildScanResult(BSSID_2, NOW_MILLIS);
// Add a new scan result and an out-of-date scan result.
- ScanResultUpdater sru = new ScanResultUpdater();
+ ScanResultUpdater sru = new ScanResultUpdater(mMockClock);
sru.update(Arrays.asList(newResult, oldResult));
// New scan result should remain and out-of-date scan result should not be returned.
- assertThat(sru.getScanResults(maxScanAge).size(), equalTo(1));
- assertThat(sru.getScanResults(maxScanAge).get(0), equalTo(newResult));
+ assertThat(sru.getScanResults(maxScanAge)).containsExactly(newResult);
}
/**
@@ -102,7 +108,7 @@ public class ScanResultUpdaterTest {
*/
@Test
public void testGetScanResults_invalidMaxScanAgeMillis_throwsException() {
- ScanResultUpdater sru = new ScanResultUpdater(15_000);
+ ScanResultUpdater sru = new ScanResultUpdater(mMockClock, 15_000);
try {
sru.getScanResults(20_000);
fail("Should have thrown exception for maxScanAgeMillis too large.");
@@ -116,19 +122,17 @@ public class ScanResultUpdaterTest {
*/
@Test
public void testConstructor_maxScanAge_filtersOldScans() {
- ScanResultUpdater sru = new ScanResultUpdater(15_000);
- long now = SystemClock.elapsedRealtime();
+ ScanResultUpdater sru = new ScanResultUpdater(mMockClock, 15_000);
- ScanResult scan1 = buildScanResult(BSSID_1, now - 10_000);
- ScanResult scan2 = buildScanResult(BSSID_2, now - 12_000);
- ScanResult scan3 = buildScanResult(BSSID_3, now - 20_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);
sru.update(Arrays.asList(scan1, scan2, scan3));
List<ScanResult> scanResults = sru.getScanResults();
- assertThat(scanResults.size(), equalTo(2));
- assertThat(scanResults, contains(scan1, scan2));
+ assertThat(scanResults).containsExactly(scan1, scan2);
}
/**
@@ -137,23 +141,20 @@ public class ScanResultUpdaterTest {
*/
@Test
public void testGetScanResults_overridesConstructorMaxScanAge() {
- ScanResultUpdater sru = new ScanResultUpdater(15_000);
- long now = SystemClock.elapsedRealtime();
+ ScanResultUpdater sru = new ScanResultUpdater(mMockClock, 15_000);
- ScanResult scan1 = buildScanResult(BSSID_1, now - 10_000);
- ScanResult scan2 = buildScanResult(BSSID_2, now - 12_000);
- ScanResult scan3 = buildScanResult(BSSID_3, now - 20_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);
sru.update(Arrays.asList(scan1, scan2, scan3));
// Aged getScanResults should override the constructor max scan age.
List<ScanResult> scanResults = sru.getScanResults(11_000);
- assertThat(scanResults.size(), equalTo(1));
- assertThat(scanResults, contains(scan1));
+ assertThat(scanResults).containsExactly(scan1);
// Non-aged getScanResults should revert to the constructor max scan age.
scanResults = sru.getScanResults();
- assertThat(scanResults.size(), equalTo(2));
- assertThat(scanResults, contains(scan1, scan2));
+ assertThat(scanResults).containsExactly(scan1, scan2);
}
}
diff --git a/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java
new file mode 100644
index 000000000..410f18277
--- /dev/null
+++ b/libs/WifiTrackerLib/tests/src/com/android/wifitrackerlib/WifiTracker2Test.java
@@ -0,0 +1,140 @@
+/*
+ * 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.lifecycle.Lifecycle;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.time.Clock;
+
+public class WifiTracker2Test {
+
+ @Mock private Lifecycle mMockLifecycle;
+ @Mock private Context mMockContext;
+ @Mock private WifiManager mMockWifiManager;
+ @Mock private ConnectivityManager mMockConnectivityManager;
+ @Mock private NetworkScoreManager mMockNetworkScoreManager;
+ @Mock private Clock mMockClock;
+ @Mock private WifiTracker2.WifiTrackerCallback mMockWifiTrackerCallback;
+
+ private TestLooper mTestLooper = new TestLooper();
+
+ private final ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+ private WifiTracker2 createTestWifiTracker2() {
+ final Handler testHandler = new Handler(mTestLooper.getLooper());
+
+ return new WifiTracker2(mMockLifecycle, mMockContext,
+ mMockWifiManager,
+ mMockConnectivityManager,
+ mMockNetworkScoreManager,
+ testHandler,
+ testHandler,
+ mMockClock,
+ 15_000,
+ 10_000,
+ mMockWifiTrackerCallback);
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Tests that receiving a wifi state change broadcast updates getWifiState().
+ */
+ @Test
+ public void testWifiStateChangeBroadcast_updatesWifiState() {
+ WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ wifiTracker2.onStart();
+ verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+ any(), any(), any());
+
+ 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);
+
+ when(mMockWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
+
+ assertThat(wifiTracker2.getWifiState()).isEqualTo(WifiManager.WIFI_STATE_ENABLED);
+
+ }
+
+ /**
+ * Tests that receiving a wifi state change broadcast notifies the listener.
+ */
+ @Test
+ public void testWifiStateChangeBroadcast_NotifiesListener() {
+ WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ wifiTracker2.onStart();
+ verify(mMockContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
+ any(), any(), any());
+
+ mBroadcastReceiverCaptor.getValue().onReceive(mMockContext,
+ new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
+ mTestLooper.dispatchAll();
+
+ verify(mMockWifiTrackerCallback, atLeastOnce()).onWifiStateChanged();
+ }
+
+ /**
+ * Tests that the wifi state is set correctly after onStart, even if no broadcast was received.
+ */
+ @Test
+ public void testOnStart_setsWifiState() {
+ WifiTracker2 wifiTracker2 = createTestWifiTracker2();
+ when(mMockWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
+ wifiTracker2.onStart();
+ mTestLooper.dispatchAll();
+
+ assertThat(wifiTracker2.getWifiState()).isEqualTo(WifiManager.WIFI_STATE_DISABLED);
+
+ wifiTracker2.onStop();
+ when(mMockWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
+ wifiTracker2.onStart();
+ mTestLooper.dispatchAll();
+
+ assertThat(wifiTracker2.getWifiState()).isEqualTo(WifiManager.WIFI_STATE_ENABLED);
+ }
+}