Add OWNERS in packages/services/NetworkRecommendation am: 87a0ebe5a0
am: a455993a49

Change-Id: I691abcf21f782706f3de52f7e1276f2f60ce8adc
diff --git a/Android.mk b/Android.mk
index 48bce25..8692412 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,8 +6,9 @@
     guava \
     jsr305 \
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+# Don't build anything, the needed system APIs have been removed.
+#LOCAL_SRC_FILES := $(call all-java-files-under, src)
+#LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PACKAGE_NAME := NetworkRecommendation
 LOCAL_CERTIFICATE := platform
@@ -24,4 +25,4 @@
 include $(BUILD_PACKAGE)
 
 # This finds and builds the test apk as well, so a single make does both.
-include $(call all-makefiles-under,$(LOCAL_PATH))
+#include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 881c8c5..3c61f98 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -27,12 +27,17 @@
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
 
-    <application android:allowBackup="false"
+    <application android:name=".NetworkRecommendationApp"
+                 android:allowBackup="false"
                  android:directBootAware="true">
 
         <!-- Only the platform can bind to this service -->
-        <service android:name=".DefaultNetworkRecommendationService"
+        <service android:name=".NetworkRecommendationService"
                  android:permission="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE">
+            <meta-data android:name="android.net.scoring.recommendation_service_label"
+                       android:value="@string/network_recommendation_service_label"/>
+            <meta-data android:name="android.net.wifi.notification_channel_id_network_available"
+                       android:value="com.android.networkrecommendation.Notifications.WifiMessageGroup.NetworkAvailableChannel" />
             <intent-filter>
                 <action android:name="android.net.action.RECOMMEND_NETWORKS" />
             </intent-filter>
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 2673295..c0da365 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,5 @@
 [Hook Scripts]
-checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+google_java_format = true
 
 [Builtin Hooks]
 commit_msg_bug_field = true
diff --git a/proguard.flags b/proguard.flags
index f925f1f..2d236f7 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -1 +1 @@
--keep class com.android.networkrecommendation.DefaultNetworkRecommendationService* { *; }
+-keep class com.android.networkrecommendation.NetworkRecommendationService* { *; }
diff --git a/res/drawable/ic_signal_wifi_statusbar_not_connected.xml b/res/drawable/ic_signal_wifi_statusbar_not_connected.xml
new file mode 100644
index 0000000..d74c31b
--- /dev/null
+++ b/res/drawable/ic_signal_wifi_statusbar_not_connected.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="26dp"
+    android:height="24dp"
+    android:viewportWidth="26"
+    android:viewportHeight="24">
+
+    <path android:pathData="M0 0h26v24H0z" />
+    <path android:fillColor="#FFFFFF"
+          android:fillAlpha=".3"
+          android:pathData="M21 8.5c.85 0 1.64 .23 2.34 .62 l2.24-2.79C25.1 5.96 20.26 2 13 2S.9 5.96 .42 6.32l12.57 15.66 .01 .02 .01 -.01 4.21-5.24c-.76-.87-1.22-2-1.22-3.25 0-2.76 2.24-5 5-5z" />
+    <path android:fillColor="#FFFFFF"
+          android:pathData="M21 10c-1.93 0-3.5 1.57-3.5 3.5h1.75c0-.97 .78 -1.75 1.75-1.75s1.75 .78 1.75 1.75c0 .48-.2 .92 -.51 1.24l-1.09 1.1c-.63 .63 -1.02 1.51-1.02 2.47v.44h1.75c0-1.31 .39 -1.84 1.03-2.47l.78-.8c.5-.5 .82 -1.2 .82 -1.97C24.5 11.57 22.93 10 21 10zm-.95 11.95h1.9v-1.9h-1.9v1.9z" />
+</vector>
+
diff --git a/res/values/config.xml b/res/values/config.xml
index 3762e90..4167ebd 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -18,9 +18,13 @@
     <integer translatable="false" name="config_netrec_5GHz_preference_boost_factor">40</integer>
     <integer translatable="false" name="config_netrec_RSSI_SCORE_OFFSET">85</integer>
     <integer translatable="false" name="config_netrec_RSSI_SCORE_SLOPE">4</integer>
+    <integer translatable="false" name="config_netrec_SAME_BSSID_AWARD">24</integer>
+    <integer translatable="false" name="config_netrec_LAST_SELECTION_AWARD">480</integer>
     <integer translatable="false" name="config_netrec_PASSPOINT_SECURITY_AWARD">40</integer>
     <integer translatable="false" name="config_netrec_SECURITY_AWARD">80</integer>
     <integer translatable="false" name="config_netrec_wifi_score_low_rssi_threshold_5GHz">-70</integer>
     <integer translatable="false" name="config_netrec_wifi_score_low_rssi_threshold_24GHz">-73</integer>
     <integer translatable="false" name="config_netrec_wifi_score_good_rssi_threshold_24GHz">-60</integer>
+    <integer translatable="false" name="config_netrec_score_good_rssi_threshold">-60</integer>
+    <integer translatable="false" name="config_netrec_current_network_boost">16</integer>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c70c1d2..e208b4c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -15,19 +15,36 @@
 -->
 
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- A notification is shown when there is a high quality open wireless network nearby.  This is the notification's title. -->
-    <string name="wifi_available">Connect to Wi\u2011Fi Network</string>
+    <!-- The service label for network recommendation service. [CHAR LIMIT=40] -->
+    <string name="network_recommendation_service_label">System default</string>
 
-    <!-- A notification is shown when there is a high quality open wireless network nearby. These are the different notification actions. -->
-    <string name="wifi_available_options">Options</string>
-    <string name="wifi_available_connect">Connect</string>
-    <string name="wifi_available_connecting">Connecting</string>
-    <string name="wifi_available_connected">Connected</string>
-    <string name="wifi_available_failed">FAILED TO CONNECT</string>
+    <!-- Notification title when there is a high quality open wireless network nearby.-->
+    <string name="wifi_available_title">Connect to open Wi\u2011Fi network</string>
+    <!-- Notification title when the system is connecting to a high quality open network. -->
+    <string name="wifi_available_title_connecting">Connecting to open Wi\u2011Fi network</string>
+    <!-- Notification title when the system has connected to the high quality open network. -->
+    <string name="wifi_available_title_connected">Connected to Wi\u2011Fi network</string>
+    <!-- Notification title when the system failed to connect to the high quality open network. -->
+    <string name="wifi_available_title_failed">Couldn\u0027t connect to Wi\u2011Fi network</string>
+    <!-- Notification content informing the user that tapping on this notification will open the wifi picker. -->
+    <string name="wifi_available_content_failed">Tap to see all networks</string>
 
-    <string name="android_system_label">Android System</string>
+    <!-- Notification action name for connecting to the network specified in the notification body. -->
+    <string name="wifi_available_action_connect">Connect</string>
+    <!-- Notification action name for opening the wifi picker, showing the user all the nearby networks. -->
+    <string name="wifi_available_action_all_networks">All Networks</string>
 
     <!-- A notification is shown when Wi-Fi Wakeup enables Wi-Fi. -->
-    <string name="wifi_wakeup_enabled_notification_title">Wi\u2011Fi was turned on automatically</string>
-    <string name="wifi_wakeup_enabled_notification_context">"Wi\u2011Fi turned on because you are near saved network, %1$s."</string>
+    <string name="wifi_wakeup_enabled_notification_title">Wi\u2011Fi turned on automatically</string>
+    <string name="wifi_wakeup_enabled_notification_context">You\u0027re near a saved network: %1$s</string>
+
+    <!-- Text used to indicate that a connection attempt is ongoing [CHAR LIMIT=20] -->
+    <string name="common_connecting">Connecting&#8230;</string>
+
+    <!-- Name of the notification channel group of our Wi-Fi messages -->
+    <string name="notification_channel_group_name">Wi\u2011Fi messages</string>
+    <!-- Name of the notification channel for a notification shown when there is a high quality open wireless network nearby. -->
+    <string name="notification_channel_wakeup_name">Wi\u2011Fi turned on</string>
+    <!-- Name of the notification channel for a notification shown when Wi-Fi Wakeup enables Wi-Fi.. -->
+    <string name="notification_channel_network_available">Network available</string>
 </resources>
diff --git a/src/com/android/networkrecommendation/Constants.java b/src/com/android/networkrecommendation/Constants.java
new file mode 100644
index 0000000..d680f9f
--- /dev/null
+++ b/src/com/android/networkrecommendation/Constants.java
@@ -0,0 +1,26 @@
+/*
+ * 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 com.android.networkrecommendation;
+
+/**
+ * Created by jlapenna on 2/14/17.
+ */
+public final class Constants {
+
+    public static final String TAG = "NetRec";
+
+    private Constants() {}
+}
diff --git a/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java b/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java
index 355d2b8..2f8afa8 100644
--- a/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java
+++ b/src/com/android/networkrecommendation/DefaultNetworkRecommendationProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.networkrecommendation;
 
+import android.content.Context;
 import android.net.NetworkKey;
 import android.net.NetworkRecommendationProvider;
 import android.net.NetworkScoreManager;
@@ -27,16 +28,18 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.os.Bundle;
-import android.os.Handler;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.Log;
+
+import com.android.networkrecommendation.util.Blog;
+import com.android.networkrecommendation.util.SsidUtil;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 import javax.annotation.concurrent.GuardedBy;
 
@@ -49,13 +52,13 @@
  * <p>This recommender is not yet recommended for non-development devices.
  *
  * <p>To debug:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService
+ * $ adb shell dumpsys activity service NetworkRecommendationService
  *
  * <p>Clear stored scores:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService clear
+ * $ adb shell dumpsys activity service NetworkRecommendationService clear
  *
  * <p>Score a network:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService addScore $SCORE
+ * $ adb shell dumpsys activity service NetworkRecommendationService addScore $SCORE
  *
  * <p>SCORE: "Quoted SSID",bssid|$RSSI_CURVE|metered|captivePortal|BADGE
  *
@@ -67,17 +70,17 @@
  *
  * <p>All commands should be executed on one line, no spaces between each line of the command..
  * <p>Eg, A high quality, paid network with captive portal:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService addScore \
+ * $ adb shell dumpsys activity service NetworkRecommendationService addScore \
  * '\"Metered\",aa:bb:cc:dd:ee:ff\|
  * 10,-128,-128,-128,-128,-128,-128,-128,-128,27,27,27,27,27,-128\|1\|1'
  *
  * <p>Eg, A high quality, unmetered network with captive portal:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService addScore \
+ * $ adb shell dumpsys activity service NetworkRecommendationService addScore \
  * '\"Captive\",aa:bb:cc:dd:ee:ff\|
  * 10,-128,-128,-128,-128,-128,-128,-128,-128,28,28,28,28,28,-128\|0\|1'
  *
  * <p>Eg, A high quality, unmetered network with any bssid:
- * $ adb shell dumpsys activity service DefaultNetworkRecommendationService addScore \
+ * $ adb shell dumpsys activity service NetworkRecommendationService addScore \
  * '\"AnySsid\",00:00:00:00:00:00\|
  * 10,-128,-128,-128,-128,-128,-128,-128,-128,29,29,29,29,29,-128\|0\|0'
  */
@@ -85,8 +88,6 @@
 public class DefaultNetworkRecommendationProvider
         extends NetworkRecommendationProvider implements SynchronousNetworkRecommendationProvider {
     static final String TAG = "DefaultNetRecProvider";
-    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
     private static final String WILDCARD_MAC = "00:00:00:00:00:00";
 
@@ -132,9 +133,10 @@
     @GuardedBy("mStatsLock")
     private int mScoreCounter = 0;
 
-    public DefaultNetworkRecommendationProvider(Handler handler,
+
+    public DefaultNetworkRecommendationProvider(Context context, Executor executor,
             NetworkScoreManager scoreManager, ScoreStorage storage) {
-        super(handler);
+        super(context, executor);
         mScoreManager = scoreManager;
         mStorage = storage;
     }
@@ -159,39 +161,39 @@
         if (results != null) {
             for (int i = 0; i < results.length; i++) {
                 final ScanResult scanResult = results[i];
-                if (VERBOSE) Log.v(TAG, "Scan: " + scanResult + " " + i);
+                Blog.v(TAG, "Scan: " + scanResult + " " + i);
 
                 // We only want to recommend open networks. This check is taken from
                 // places like WifiNotificationController and will be extracted to ScanResult in
                 // a future CL.
                 if (!"[ESS]".equals(scanResult.capabilities)) {
-                    if (VERBOSE) Log.v(TAG, "Discarding closed network: " + scanResult);
+                    Blog.v(TAG, "Discarding closed network: " + scanResult);
                     continue;
                 }
 
                 final NetworkKey networkKey = new NetworkKey(
-                        new WifiKey(ScanResultUtil.createQuotedSSID(scanResult.SSID),
+                        new WifiKey(SsidUtil.quoteSsid(scanResult.SSID),
                                 scanResult.BSSID));
-                if (VERBOSE) Log.v(TAG, "Evaluating network: " + networkKey);
+                Blog.v(TAG, "Evaluating network: " + networkKey);
 
                 // We will only score networks we know about.
                 final ScoredNetwork network = mStorage.get(networkKey);
                 if (network == null) {
-                    if (VERBOSE) Log.v(TAG, "Discarding unscored network: " + scanResult);
+                    Blog.v(TAG, "Discarding unscored network: " + scanResult);
                     continue;
                 }
 
                 final int score = network.rssiCurve.lookupScore(scanResult.level);
-                if (VERBOSE) Log.d(TAG, "Scored " + scanResult + ": " + score);
+                Blog.v(TAG, "Scored " + scanResult + ": " + score);
                 if (score > recommendedScore) {
                     recommendedScanResult = scanResult;
                     recommendedScore = score;
-                    if (VERBOSE) Log.d(TAG, "New recommended network: " + scanResult);
+                    Blog.v(TAG, "New recommended network: " + scanResult);
                     continue;
                 }
             }
         } else {
-            Log.w(TAG, "Received null scan results in request.");
+            Blog.w(TAG, "Received null scan results in request.");
         }
 
         // If we ended up without a recommendation, recommend the provided configuration
@@ -208,7 +210,7 @@
         } else {
             // Build a configuration based on the scan.
             WifiConfiguration recommendedConfig = new WifiConfiguration();
-            recommendedConfig.SSID = ScanResultUtil.createQuotedSSID(recommendedScanResult.SSID);
+            recommendedConfig.SSID = SsidUtil.quoteSsid(recommendedScanResult.SSID);
             recommendedConfig.BSSID = recommendedScanResult.BSSID;
             recommendedConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
             recommendationResult = RecommendationResult
@@ -217,7 +219,7 @@
         synchronized (mStatsLock) {
             mLastRecommended = recommendationResult.getWifiConfiguration();
             mRecommendationCounter++;
-            if (DEBUG) Log.d(TAG, "Recommending network: " + configToString(mLastRecommended));
+            Blog.d(TAG, "Recommending network: " + configToString(mLastRecommended));
         }
         return recommendationResult;
     }
@@ -254,7 +256,7 @@
             return;
         }
 
-        if (DEBUG) Log.d(TAG, "Scored networks: " + scoredNetworks);
+        Blog.d(TAG, "Scored networks: " + scoredNetworks);
         safelyUpdateScores(scoredNetworks.toArray(new ScoredNetwork[scoredNetworks.size()]));
     }
 
@@ -303,7 +305,7 @@
         try {
             mScoreManager.updateScores(networkScores);
         } catch (SecurityException e) {
-            Log.w(TAG, "Tried to update scores when not the active scorer.");
+            Blog.w(TAG, "Tried to update scores when not the active scorer.");
         }
     }
 
@@ -313,7 +315,7 @@
         try {
             mScoreManager.clearScores();
         } catch (SecurityException e) {
-            Log.w(TAG, "Tried to update scores when not the active scorer.");
+            Blog.w(TAG, "Tried to update scores when not the active scorer.");
         }
     }
 
@@ -380,7 +382,7 @@
          *     score as applying to any bssid with the provided ssid.
          */
         public void addScore(ScoredNetwork scoredNetwork) {
-            if (DEBUG) Log.d(TAG, "addScore: " + scoredNetwork);
+            Blog.d(TAG, "addScore: " + scoredNetwork);
             synchronized (mScores) {
                 mScores.put(scoredNetwork.networkKey, scoredNetwork);
             }
@@ -427,5 +429,4 @@
     public ScoredNetwork getCachedScoredNetwork(NetworkKey networkKey) {
         return mStorage.get(networkKey);
     }
-
 }
diff --git a/src/com/android/networkrecommendation/DefaultNetworkRecommendationService.java b/src/com/android/networkrecommendation/DefaultNetworkRecommendationService.java
deleted file mode 100644
index 64e4f13..0000000
--- a/src/com/android/networkrecommendation/DefaultNetworkRecommendationService.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.ContentResolver;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.NetworkScoreManager;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Provides network recommendations for the platform.
- */
-public class DefaultNetworkRecommendationService extends Service {
-
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
-    private DefaultNetworkRecommendationProvider mProvider;
-    private WifiNotificationController mWifiNotificationController;
-    private WifiWakeupController mWifiWakeupController;
-
-    @Override
-    public void onCreate() {
-        mHandlerThread = new HandlerThread("RecommendationProvider");
-        mHandlerThread.start();
-        Looper looper = mHandlerThread.getLooper();
-        mHandler = new Handler(looper);
-        NetworkScoreManager networkScoreManager = getSystemService(NetworkScoreManager.class);
-        mProvider = new DefaultNetworkRecommendationProvider(mHandler,
-                networkScoreManager, new DefaultNetworkRecommendationProvider.ScoreStorage());
-        NotificationManager notificationManager = getSystemService(NotificationManager.class);
-        WifiManager wifiManager = getSystemService(WifiManager.class);
-        Resources resources = getResources();
-        ContentResolver contentResolver = getContentResolver();
-        mWifiNotificationController = new WifiNotificationController(
-                this, contentResolver, new Handler(looper), mProvider,
-                wifiManager, notificationManager,
-                new WifiNotificationHelper(this, mProvider));
-        WifiWakeupNetworkSelector wifiWakeupNetworkSelector =
-                new WifiWakeupNetworkSelector(resources);
-        WifiWakeupNotificationHelper wifiWakeupNotificationHelper =
-                new WifiWakeupNotificationHelper(this, resources, new Handler(looper),
-                        notificationManager, wifiManager);
-        mWifiWakeupController = new WifiWakeupController(this, contentResolver, looper,
-                wifiManager, wifiWakeupNetworkSelector, wifiWakeupNotificationHelper);
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        mWifiWakeupController.start();
-        mWifiNotificationController.start();
-        return mProvider.getBinder();
-    }
-
-    @Override
-    public boolean onUnbind(Intent intent) {
-        mWifiWakeupController.stop();
-        mWifiNotificationController.stop();
-        return super.onUnbind(intent);
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        mProvider.dump(fd, writer, args);
-        mWifiNotificationController.dump(fd, writer, args);
-        mWifiWakeupController.dump(fd, writer, args);
-    }
-}
diff --git a/src/com/android/networkrecommendation/Example.java b/src/com/android/networkrecommendation/Example.java
new file mode 100644
index 0000000..a85a112
--- /dev/null
+++ b/src/com/android/networkrecommendation/Example.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.networkrecommendation;
+
+import android.net.RecommendationRequest;
+
+import java.lang.reflect.Method;
+
+public class Example {
+
+    public Example() {}
+
+    public void buildRecommendationRequest() {
+        new RecommendationRequest.Builder().build();
+    }
+
+    @SuppressWarnings("unchecked")
+    public RecommendationRequest reflectRecommendationRequest() {
+        try {
+            Class builder = RecommendationRequest.class.getClasses()[0];
+            Object builderObj = builder.getConstructor().newInstance();
+            Method build = builder.getDeclaredMethod("build");
+            return (RecommendationRequest) build.invoke(builderObj);
+        } catch (Exception e) {
+            throw new RuntimeException("Noooo", e);
+        }
+    }
+}
diff --git a/src/com/android/networkrecommendation/ImageUtils.java b/src/com/android/networkrecommendation/ImageUtils.java
deleted file mode 100644
index 4b3d042..0000000
--- a/src/com/android/networkrecommendation/ImageUtils.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-
-/** Helper for image manipulation */
-public class ImageUtils {
-
-    /**
-     * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
-     */
-    public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
-            int maxHeight) {
-        if (drawable == null) {
-            return null;
-        }
-        int originalWidth = drawable.getIntrinsicWidth();
-        int originalHeight = drawable.getIntrinsicHeight();
-
-        if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight)
-                && (drawable instanceof BitmapDrawable)) {
-            return ((BitmapDrawable) drawable).getBitmap();
-        }
-        if (originalHeight <= 0 || originalWidth <= 0) {
-            return null;
-        }
-
-        // create a new bitmap, scaling down to fit the max dimensions of
-        // a large notification icon if necessary
-        float ratio = Math.min((float) maxWidth / (float) originalWidth,
-                (float) maxHeight / (float) originalHeight);
-        ratio = Math.min(1.0f, ratio);
-        int scaledWidth = (int) (ratio * originalWidth);
-        int scaledHeight = (int) (ratio * originalHeight);
-        Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
-
-        // and paint our app bitmap on it
-        Canvas canvas = new Canvas(result);
-        drawable.setBounds(0, 0, scaledWidth, scaledHeight);
-        drawable.draw(canvas);
-
-        return result;
-    }
-}
diff --git a/src/com/android/networkrecommendation/NetworkRecommendationApp.java b/src/com/android/networkrecommendation/NetworkRecommendationApp.java
new file mode 100644
index 0000000..c63c875
--- /dev/null
+++ b/src/com/android/networkrecommendation/NetworkRecommendationApp.java
@@ -0,0 +1,17 @@
+package com.android.networkrecommendation;
+
+import android.app.Application;
+
+import com.android.networkrecommendation.config.PreferenceFile;
+
+/**
+ * Initialize app-wide state.
+ */
+public class NetworkRecommendationApp extends Application {
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        PreferenceFile.init(this);
+    }
+}
diff --git a/src/com/android/networkrecommendation/NetworkRecommendationService.java b/src/com/android/networkrecommendation/NetworkRecommendationService.java
new file mode 100644
index 0000000..d4923d0
--- /dev/null
+++ b/src/com/android/networkrecommendation/NetworkRecommendationService.java
@@ -0,0 +1,114 @@
+/*
+ * 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 com.android.networkrecommendation;
+
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.UserManager;
+
+import com.android.networkrecommendation.notify.WifiNotificationController;
+import com.android.networkrecommendation.notify.WifiNotificationHelper;
+import com.android.networkrecommendation.util.NotificationChannelUtil;
+import com.android.networkrecommendation.wakeup.WifiWakeupController;
+import com.android.networkrecommendation.wakeup.WifiWakeupHelper;
+import com.android.networkrecommendation.wakeup.WifiWakeupNetworkSelector;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Provides network recommendations for the platform.
+ */
+public class NetworkRecommendationService extends Service {
+
+    private HandlerThread mProviderHandlerThread;
+    private Handler mProviderHandler;
+    private HandlerThread mControllerHandlerThread;
+    private Handler mControllerHandler;
+    private DefaultNetworkRecommendationProvider mProvider;
+    private WifiNotificationController mWifiNotificationController;
+    private WifiWakeupController mWifiWakeupController;
+
+    @Override
+    public void onCreate() {
+        mProviderHandlerThread = new HandlerThread("RecommendationProvider");
+        mProviderHandlerThread.start();
+        mProviderHandler = new Handler(mProviderHandlerThread.getLooper());
+        NetworkScoreManager networkScoreManager = getSystemService(NetworkScoreManager.class);
+        mProvider = new DefaultNetworkRecommendationProvider(this, mProviderHandler::post,
+                networkScoreManager, new DefaultNetworkRecommendationProvider.ScoreStorage());
+
+        mControllerHandlerThread = new HandlerThread("RecommendationProvider");
+        mControllerHandlerThread.start();
+        mControllerHandler = new Handler(mControllerHandlerThread.getLooper());
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        NotificationChannelUtil.configureNotificationChannels(notificationManager, this);
+
+        WifiManager wifiManager = getSystemService(WifiManager.class);
+        PowerManager powerManager = getSystemService(PowerManager.class);
+        UserManager userManager = getSystemService(UserManager.class);
+        Resources resources = getResources();
+        ContentResolver contentResolver = getContentResolver();
+        mWifiNotificationController = new WifiNotificationController(
+                this, contentResolver, mControllerHandler, mProvider,
+                wifiManager, notificationManager, userManager, new WifiNotificationHelper(this));
+        WifiWakeupNetworkSelector wifiWakeupNetworkSelector =
+                new WifiWakeupNetworkSelector(resources, mProvider);
+        WifiWakeupHelper wifiWakeupHelper = new WifiWakeupHelper(this, resources, mControllerHandler,
+                notificationManager, wifiManager);
+        mWifiWakeupController =
+                new WifiWakeupController(this, getContentResolver(), mControllerHandler, wifiManager,
+                        powerManager, userManager, wifiWakeupNetworkSelector, wifiWakeupHelper);
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        mWifiWakeupController.start();
+        mWifiNotificationController.start();
+        return mProvider.getBinder();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mWifiWakeupController.stop();
+        mWifiNotificationController.stop();
+        return super.onUnbind(intent);
+    }
+
+    @Override
+    public void onDestroy() {
+        mProviderHandlerThread.quit();
+        super.onDestroy();
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        mProvider.dump(fd, writer, args);
+        mWifiNotificationController.dump(fd, writer, args);
+        mWifiWakeupController.dump(fd, writer, args);
+    }
+}
diff --git a/src/com/android/networkrecommendation/SynchronousNetworkRecommendationProvider.java b/src/com/android/networkrecommendation/SynchronousNetworkRecommendationProvider.java
index 44a7e0d..b2ff12c 100644
--- a/src/com/android/networkrecommendation/SynchronousNetworkRecommendationProvider.java
+++ b/src/com/android/networkrecommendation/SynchronousNetworkRecommendationProvider.java
@@ -24,7 +24,7 @@
 /**
  * Provider to return {@link ScoredNetwork} from cached scores in NetworkRecommendationProvider.
  */
-interface SynchronousNetworkRecommendationProvider {
+public interface SynchronousNetworkRecommendationProvider {
 
     /** Returns a {@link ScoredNetwork} if present in the cache. Otherwise, return null. */
     ScoredNetwork getCachedScoredNetwork(NetworkKey networkKey);
diff --git a/src/com/android/networkrecommendation/WifiNotificationController.java b/src/com/android/networkrecommendation/WifiNotificationController.java
deleted file mode 100644
index 9dbb665..0000000
--- a/src/com/android/networkrecommendation/WifiNotificationController.java
+++ /dev/null
@@ -1,456 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.graphics.Bitmap;
-import android.net.NetworkInfo;
-import android.net.NetworkScoreManager;
-import android.net.RecommendationRequest;
-import android.net.RecommendationResult;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.provider.Settings;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Takes care of handling the "open wi-fi network available" notification
- * @hide
- */
-public class WifiNotificationController {
-    /**
-     * The icon to show in the 'available networks' notification. This will also
-     * be the ID of the Notification given to the NotificationManager.
-     */
-    private static final int ICON_NETWORKS_AVAILABLE = R.drawable.stat_notify_wifi_in_range;
-    /**
-     * When a notification is shown, we wait this amount before possibly showing it again.
-     */
-    private final long mNotificationRepeatDelayMs;
-    /**
-     * Whether the user has set the setting to show the 'available networks' notification.
-     */
-    private boolean mNotificationEnabled;
-    /**
-     * Observes the user setting to keep {@link #mNotificationEnabled} in sync.
-     */
-    private final NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
-    /**
-     * The {@link System#currentTimeMillis()} must be at least this value for us
-     * to show the notification again.
-     */
-    private long mNotificationRepeatTime;
-    /**
-     * Whether the notification is being shown.
-     */
-    private boolean mNotificationShown;
-    /**
-     * The number of continuous scans that must occur before consider the
-     * supplicant in a scanning state. This allows supplicant to associate with
-     * remembered networks that are in the scan results.
-     */
-    private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
-    /**
-     * The number of scans since the last network state change. When this
-     * exceeds {@link #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the
-     * supplicant to actually be scanning. When the network state changes to
-     * something other than scanning, we reset this to 0.
-     */
-    private int mNumScansSinceNetworkStateChange;
-    /**
-     * Time in milliseconds to display the Connecting notification.
-     */
-    private static final int TIME_TO_SHOW_CONNECTING_MILLIS = 10000;
-    /**
-     * Time in milliseconds to display the Connected notification.
-     */
-    private static final int TIME_TO_SHOW_CONNECTED_MILLIS = 5000;
-    /**
-     * Time in milliseconds to display the Failed To Connect notification.
-     */
-    private static final int TIME_TO_SHOW_FAILED_MILLIS = 5000;
-    /**
-     * Try to connect to provided WifiConfiguration since user wants to
-     * connect to the recommended open access point.
-     */
-    static final String ACTION_CONNECT_TO_RECOMMENDED_NETWORK =
-            "com.android.networkrecommendation.CONNECT_TO_RECOMMENDED_NETWORK";
-    /**
-     * Handles behavior when notification is deleted.
-     */
-    static final String ACTION_NOTIFICATION_DELETED =
-            "com.android.networkrecommendation.NOTIFICATION_DELETED";
-    /**
-     * Network recommended by {@link NetworkScoreManager#requestRecommendation}.
-     */
-    private WifiConfiguration mRecommendedNetwork;
-    private Bitmap mNotificationBadgeBitmap;
-    /**
-     * Whether {@link WifiNotificationController} has been started.
-     */
-    private final AtomicBoolean mStarted;
-    /**
-     * Runnable to dismiss notification.
-     */
-    @VisibleForTesting
-    final Runnable mDismissNotificationRunnable = () -> {
-        removeNotification();
-    };
-    /**
-     * Runnable to show Failed To Connect notification.
-     */
-    @VisibleForTesting
-    final Runnable mShowFailedToConnectNotificationRunnable = () -> {
-        showFailedToConnectNotification();
-    };
-
-    private static final String TAG = "WifiNotification";
-
-    private final Context mContext;
-    private final Handler mHandler;
-    private final ContentResolver mContentResolver;
-    private final SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider;
-    private final WifiManager mWifiManager;
-    private final NotificationManager mNotificationManager;
-    private final WifiNotificationHelper mWifiNotificationHelper;
-    private NetworkInfo mNetworkInfo;
-    private NetworkInfo.DetailedState mDetailedState;
-    private volatile int mWifiState;
-
-    WifiNotificationController(Context context, ContentResolver contentResolver, Handler handler,
-            SynchronousNetworkRecommendationProvider networkRecommendationProvider,
-            WifiManager wifiManager, NotificationManager notificationManager,
-            WifiNotificationHelper helper) {
-        mContext = context;
-        mContentResolver = contentResolver;
-        mNetworkRecommendationProvider = networkRecommendationProvider;
-        mWifiManager = wifiManager;
-        mNotificationManager = notificationManager;
-        mHandler = handler;
-        mWifiNotificationHelper = helper;
-        mStarted = new AtomicBoolean(false);
-
-        // Setting is in seconds
-        mNotificationRepeatDelayMs = Settings.Global.getInt(
-                contentResolver, Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, 900) * 1000L;
-        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(mHandler);
-    }
-
-    /** Starts {@link WifiNotificationController}. */
-    public void start() {
-        if (!mStarted.compareAndSet(false, true)) {
-            return;
-        }
-
-        mWifiState = WifiManager.WIFI_STATE_UNKNOWN;
-        mDetailedState = NetworkInfo.DetailedState.IDLE;
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-        filter.addAction(ACTION_CONNECT_TO_RECOMMENDED_NETWORK);
-        filter.addAction(ACTION_NOTIFICATION_DELETED);
-
-        mContext.registerReceiver(
-                mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
-        mNotificationEnabledSettingObserver.register();
-    }
-
-    /** Stops {@link WifiNotificationController}. */
-    public void stop() {
-        if (!mStarted.compareAndSet(true, false)) {
-            return;
-        }
-        mContext.unregisterReceiver(mBroadcastReceiver);
-        mNotificationEnabledSettingObserver.register();
-    }
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
-                mWifiState = mWifiManager.getWifiState();
-                resetNotification();
-            } else if (intent.getAction().equals(
-                    WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
-                mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
-                        WifiManager.EXTRA_NETWORK_INFO);
-                NetworkInfo.DetailedState detailedState =
-                        mNetworkInfo.getDetailedState();
-                if (detailedState != NetworkInfo.DetailedState.SCANNING
-                        && detailedState != mDetailedState) {
-                    mDetailedState = detailedState;
-                    switch (mDetailedState) {
-                        case CONNECTED:
-                            updateNotificationOnConnect();
-                            break;
-                        case DISCONNECTED:
-                        case CAPTIVE_PORTAL_CHECK:
-                            resetNotification();
-                            break;
-
-                        // TODO: figure out if these are failure cases when connecting
-                        case IDLE:
-                        case SCANNING:
-                        case CONNECTING:
-                        case AUTHENTICATING:
-                        case OBTAINING_IPADDR:
-                        case SUSPENDED:
-                        case FAILED:
-                        case BLOCKED:
-                        case VERIFYING_POOR_LINK:
-                            break;
-                    }
-                }
-            } else if (intent.getAction().equals(
-                    WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
-                checkAndSetNotification(mNetworkInfo, mWifiManager.getScanResults());
-            } else if (intent.getAction().equals(ACTION_CONNECT_TO_RECOMMENDED_NETWORK)) {
-                connectToRecommendedNetwork();
-            } else if (intent.getAction().equals(ACTION_NOTIFICATION_DELETED)) {
-                handleNotificationDeleted();
-            }
-        }
-    };
-
-    private void checkAndSetNotification(NetworkInfo networkInfo,
-            List<ScanResult> scanResults) {
-
-        // TODO: unregister broadcast so we do not have to check here
-        // If we shouldn't place a notification on available networks, then
-        // don't bother doing any of the following
-        if (!mNotificationEnabled) return;
-        if (mWifiState != WifiManager.WIFI_STATE_ENABLED) return;
-        if (scanResults == null || scanResults.isEmpty()) return;
-
-        NetworkInfo.State state = NetworkInfo.State.DISCONNECTED;
-        if (networkInfo != null) {
-            state = networkInfo.getState();
-        }
-
-
-        if (state == NetworkInfo.State.DISCONNECTED
-                || state == NetworkInfo.State.UNKNOWN) {
-            RecommendationResult result = getOpenNetworkRecommendation(scanResults);
-            if (result != null
-                    && result.getWifiConfiguration() != null) {
-                mRecommendedNetwork = result.getWifiConfiguration();
-                mNotificationBadgeBitmap = mWifiNotificationHelper.createNotificationBadgeBitmap(
-                        mRecommendedNetwork, scanResults);
-                if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING
-                        && mNotificationBadgeBitmap != null) {
-                    /*
-                     * We have scanned continuously at least
-                     * NUM_SCANS_BEFORE_NOTIFICATION times. The user
-                     * probably does not have a remembered network in range,
-                     * since otherwise supplicant would have tried to
-                     * associate and thus resetting this counter.
-                     */
-                    displayNotification();
-                }
-                return;
-            }
-        }
-
-        // No open networks in range, remove the notification
-        removeNotification();
-    }
-
-    /**
-     * Uses {@link NetworkScoreManager} to choose a qualified network out of the list of
-     * {@link ScanResult}s.
-     *
-     * @return returns the best qualified open networks, if any.
-     */
-    @Nullable
-    private RecommendationResult getOpenNetworkRecommendation(List<ScanResult> scanResults) {
-        if (scanResults == null || scanResults.isEmpty()) {
-            return null;
-        }
-        ArrayList<ScanResult> openNetworks = new ArrayList<>();
-        for (ScanResult scanResult : scanResults) {
-            //A capability of [ESS] represents an open access point
-            //that is available for an STA to connect
-            if ("[ESS]".equals(scanResult.capabilities)) {
-                openNetworks.add(scanResult);
-            }
-        }
-        RecommendationRequest request = new RecommendationRequest.Builder()
-                .setScanResults(openNetworks.toArray(new ScanResult[openNetworks.size()]))
-                .build();
-
-        return mNetworkRecommendationProvider.requestRecommendation(request);
-    }
-
-    /**
-     * Display's a notification that there are open Wi-Fi networks.
-     */
-    private void displayNotification() {
-
-        // Since we use auto cancel on the notification, when the
-        // mNetworksAvailableNotificationShown is true, the notification may
-        // have actually been canceled.  However, when it is false we know
-        // for sure that it is not being shown (it will not be shown any other
-        // place than here)
-
-        // Not enough time has passed to show the notification again
-
-        if (System.currentTimeMillis() < mNotificationRepeatTime) {
-            return;
-        }
-        Notification notification = mWifiNotificationHelper.createMainNotification(
-                        mRecommendedNetwork, mNotificationBadgeBitmap);
-        mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelayMs;
-
-        postNotification(notification);
-        mNotificationShown = true;
-    }
-
-
-    /**
-     * Attempts to connect to recommended network and updates the notification to
-     * show Connecting state.
-     * TODO(33668991): work with UX to polish notification UI and figure out failure states
-     */
-    private void connectToRecommendedNetwork() {
-        if (mRecommendedNetwork == null) {
-            return;
-        }
-        // Attempts to connect to recommended network.
-        mWifiManager.connect(mRecommendedNetwork, null /* actionListener */);
-
-        // Update notification to connecting status.
-        Notification notification = mWifiNotificationHelper.createConnectingNotification(
-                        mRecommendedNetwork, mNotificationBadgeBitmap);
-        postNotification(notification);
-        mHandler.postDelayed(mShowFailedToConnectNotificationRunnable,
-                TIME_TO_SHOW_CONNECTING_MILLIS);
-    }
-
-    /**
-     * When detailed state changes to CONNECTED, show connected notification or
-     * reset notification.
-     * TODO: determine failure state where main notification shows but connected.
-     */
-    private void updateNotificationOnConnect() {
-        if (!mNotificationShown) {
-            return;
-        }
-
-        Notification notification = mWifiNotificationHelper.createConnectedNotification(
-                mRecommendedNetwork, mNotificationBadgeBitmap);
-        postNotification(notification);
-        // Remove any previous reset notification callbacks.
-        mHandler.removeCallbacks(mShowFailedToConnectNotificationRunnable);
-        mHandler.postDelayed(mDismissNotificationRunnable, TIME_TO_SHOW_CONNECTED_MILLIS);
-    }
-
-    /**
-     * Displays the Failed To Connect notification after the Connecting notification
-     * is shown for {@link #TIME_TO_SHOW_CONNECTING_MILLIS} duration.
-     */
-    private void showFailedToConnectNotification() {
-        Notification notification =
-                mWifiNotificationHelper.createFailedToConnectNotification(mRecommendedNetwork);
-        postNotification(notification);
-        mHandler.postDelayed(mDismissNotificationRunnable, TIME_TO_SHOW_FAILED_MILLIS);
-    }
-
-    /**
-     * Handles behavior when notification is dismissed.
-     */
-    private void handleNotificationDeleted() {
-        mNotificationShown = false;
-        mRecommendedNetwork = null;
-        mNotificationBadgeBitmap = null;
-    }
-
-    private void postNotification(Notification notification) {
-        mNotificationManager.notify(null /* tag */, ICON_NETWORKS_AVAILABLE, notification);
-    }
-
-    /**
-     * Clears variables related to tracking whether a notification has been
-     * shown recently and clears the current notification.
-     */
-    private void resetNotification() {
-        mNotificationRepeatTime = 0;
-        mNumScansSinceNetworkStateChange = 0;
-        if (mNotificationShown) {
-            removeNotification();
-        }
-    }
-
-    private void removeNotification() {
-        mNotificationManager.cancel(null /* tag */, ICON_NETWORKS_AVAILABLE);
-        mNotificationShown = false;
-    }
-
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("mNotificationEnabled " + mNotificationEnabled);
-        pw.println("mNotificationRepeatTime " + mNotificationRepeatTime);
-        pw.println("mNotificationShown " + mNotificationShown);
-        pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange);
-    }
-
-    private class NotificationEnabledSettingObserver extends ContentObserver {
-        NotificationEnabledSettingObserver(Handler handler) {
-            super(handler);
-        }
-
-        public void register() {
-            mContentResolver.registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON), true, this);
-            mNotificationEnabled = getValue();
-        }
-
-        public void unregister() {
-            mContentResolver.unregisterContentObserver(this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            super.onChange(selfChange);
-
-            mNotificationEnabled = getValue();
-            resetNotification();
-        }
-
-        private boolean getValue() {
-            return Settings.Global.getInt(
-                    mContentResolver,
-                    Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0) == 1;
-        }
-    }
-}
diff --git a/src/com/android/networkrecommendation/WifiNotificationHelper.java b/src/com/android/networkrecommendation/WifiNotificationHelper.java
deleted file mode 100644
index e4c8c4e..0000000
--- a/src/com/android/networkrecommendation/WifiNotificationHelper.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
-
-import android.app.Notification;
-import android.app.Notification.Action;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.net.NetworkKey;
-import android.net.ScoredNetwork;
-import android.net.WifiKey;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.support.annotation.NonNull;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.style.ForegroundColorSpan;
-
-import java.util.List;
-
-/**
- * Helper class that creates notifications for {@link WifiNotificationController}.
- */
-public class WifiNotificationHelper {
-    private final Context mContext;
-    private final SynchronousNetworkRecommendationProvider mCachedScoredNetworkProvider;
-
-    public WifiNotificationHelper(
-            Context context, SynchronousNetworkRecommendationProvider cachedScoredNetworkProvider) {
-        mContext = context;
-        mCachedScoredNetworkProvider = cachedScoredNetworkProvider;
-    }
-
-    /**
-     * Creates the main open networks notification with two actions. "Options" link to the
-     * Wi-Fi picker activity, and "Connect" prompts {@link WifiNotificationController}
-     * to connect to the recommended network.
-     */
-    public Notification createMainNotification(WifiConfiguration config, Bitmap badge) {
-        PendingIntent optionsIntent = PendingIntent.getActivity(
-                mContext, 0, new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK), FLAG_UPDATE_CURRENT);
-        Action optionsAction = new Action.Builder(
-                null /* icon */,
-                mContext.getText(R.string.wifi_available_options),
-                optionsIntent)
-                .build();
-        PendingIntent connectIntent = PendingIntent.getBroadcast(
-                mContext,
-                0,
-                new Intent(WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK),
-                FLAG_UPDATE_CURRENT);
-        Action connectAction = new Action.Builder(
-                null /* icon */,
-                mContext.getText(R.string.wifi_available_connect),
-                connectIntent)
-                .build();
-        return createNotificationBuilder(config, badge)
-                .addAction(connectAction)
-                .addAction(optionsAction)
-                .build();
-    }
-
-    /**
-     * Creates the notification that indicates the controller is attempting to connect
-     * to the recommended network.
-     */
-    public Notification createConnectingNotification(WifiConfiguration config, Bitmap badge) {
-        Action connecting = new Action.Builder(
-                null /* icon */,
-                mContext.getText(R.string.wifi_available_connecting),
-                null /* pendingIntent */)
-                .build();
-        return createNotificationBuilder(config, badge)
-                .addAction(connecting)
-                .setProgress(0 /* max */, 0 /* progress */, true /* indeterminate */)
-                .build();
-    }
-
-    /**
-     * Creates the notification that indicates the controller successfully connected
-     * to the recommended network.
-     */
-    public Notification createConnectedNotification(WifiConfiguration config, Bitmap badge) {
-        Action connected = new Action.Builder(
-                null /* icon */,
-                mContext.getText(R.string.wifi_available_connected),
-                null /* pendingIntent */)
-                .build();
-        return createNotificationBuilder(config, badge)
-                .addAction(connected)
-                .build();
-    }
-
-    /**
-     * Creates the notification that indicates the controller failed to connect to
-     * the recommended network.
-     */
-    public Notification createFailedToConnectNotification(WifiConfiguration config) {
-        Spannable failedText =
-                new SpannableString(mContext.getText(R.string.wifi_available_failed));
-        Resources resources = mContext.getResources();
-        Drawable iconDrawable = mContext.getDrawable(R.drawable.ic_signal_wifi_no_network);
-        iconDrawable.setTint(mContext.getColor(R.color.color_tint));
-        Bitmap icon = ImageUtils.buildScaledBitmap(
-                iconDrawable,
-                resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
-                resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height));
-        failedText.setSpan(new ForegroundColorSpan(
-                Color.RED), 0, failedText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
-        return createNotificationBuilder(config, icon)
-                .setContentText(failedText)
-                .build();
-    }
-
-    private Notification.Builder createNotificationBuilder(WifiConfiguration config, Bitmap badge) {
-        CharSequence title = mContext.getText(R.string.wifi_available);
-        PendingIntent deleteIntent = PendingIntent.getBroadcast(
-                mContext,
-                0,
-                new Intent(WifiNotificationController.ACTION_NOTIFICATION_DELETED),
-                FLAG_UPDATE_CURRENT);
-        return new Notification.Builder(mContext)
-                .setDeleteIntent(deleteIntent)
-                .setSmallIcon(R.drawable.stat_notify_wifi_in_range)
-                .setLargeIcon(badge)
-                .setAutoCancel(true)
-                .setTicker(title)
-                .setContentTitle(title)
-                .setContentText(WifiConfigurationUtil.getPrintableSsid(config))
-                .addExtras(getSystemLabelExtras());
-    }
-
-    private Bundle getSystemLabelExtras() {
-        Bundle extras = new Bundle();
-        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                mContext.getString(R.string.android_system_label));
-        return extras;
-    }
-
-    //TODO(34177812): Share this logic between systemUi and Settings.
-    static final int[] WIFI_PIE_FOR_BADGING = {
-            R.drawable.ic_signal_wifi_badged_0_bars,
-            R.drawable.ic_signal_wifi_badged_1_bar,
-            R.drawable.ic_signal_wifi_badged_2_bars,
-            R.drawable.ic_signal_wifi_badged_3_bars,
-            R.drawable.ic_signal_wifi_badged_4_bars
-    };
-
-    private int getWifiBadgeResourceForEnum(int badgeEnum) {
-        switch (badgeEnum) {
-            case ScoredNetwork.BADGING_NONE:
-                return 0;
-            case ScoredNetwork.BADGING_SD:
-                return R.drawable.ic_signal_wifi_badged_sd;
-            case ScoredNetwork.BADGING_HD:
-                return R.drawable.ic_signal_wifi_badged_hd;
-            case ScoredNetwork.BADGING_4K:
-                return R.drawable.ic_signal_wifi_badged_4k;
-            default:
-                throw new IllegalArgumentException("No badge resource for enum :" + badgeEnum);
-        }
-    }
-
-    /**
-     * Creates a Wi-Fi badge for the notification using matching {@link ScanResult}'s RSSI
-     * and badging from {@link CachedScoredNetworkProvider}.
-     */
-    public Bitmap createNotificationBadgeBitmap(
-            @NonNull WifiConfiguration config,
-            @NonNull List<ScanResult> scanResults) {
-        ScanResult matchingScanResult = findMatchingScanResult(scanResults, config);
-        if (matchingScanResult == null) {
-            return null;
-        }
-        int rssi = matchingScanResult.level;
-        WifiKey wifiKey = new WifiKey(config.SSID, config.BSSID);
-        ScoredNetwork scoredNetwork =
-                mCachedScoredNetworkProvider.getCachedScoredNetwork(new NetworkKey(wifiKey));
-        if (scoredNetwork != null) {
-            return getBadgedWifiBitmap(scoredNetwork.calculateBadge(rssi), rssi);
-        }
-        return null;
-    }
-
-    private Bitmap getBadgedWifiBitmap(int badgeEnum, int rssi) {
-        if (badgeEnum == ScoredNetwork.BADGING_NONE) {
-            return null;
-        }
-        int signalLevel = WifiManager.calculateSignalLevel(rssi, 5);
-        LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{
-                mContext.getDrawable(WIFI_PIE_FOR_BADGING[signalLevel]),
-                mContext.getDrawable(getWifiBadgeResourceForEnum(badgeEnum))});
-        layerDrawable.setTint(mContext.getColor(R.color.color_tint));
-        Resources resources = mContext.getResources();
-        return ImageUtils.buildScaledBitmap(layerDrawable,
-                resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
-                resources.getDimensionPixelSize(android.R.dimen.notification_large_icon_height));
-    }
-
-    private ScanResult findMatchingScanResult(List<ScanResult> scanResults,
-            WifiConfiguration wifiConfiguration) {
-        String ssid = WifiConfigurationUtil.removeDoubleQuotes(wifiConfiguration);
-        String bssid = wifiConfiguration.BSSID;
-        for (ScanResult scanResult : scanResults) {
-            if (ssid.equals(scanResult.SSID) && bssid.equals(scanResult.BSSID)) {
-                return scanResult;
-            }
-        }
-        return null;
-    }
-}
diff --git a/src/com/android/networkrecommendation/WifiWakeupController.java b/src/com/android/networkrecommendation/WifiWakeupController.java
deleted file mode 100644
index 5fe65ef..0000000
--- a/src/com/android/networkrecommendation/WifiWakeupController.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Handles enabling Wi-Fi for the Wi-Fi Wakeup feature.
- *
- * <p>
- * This class enables Wi-Fi when the user is near a network that would would autojoined if Wi-Fi
- * were enabled. When a user disables Wi-Fi, Wi-Fi Wakeup will not enable Wi-Fi until the
- * user's context has changed. For saved networks, this context change is defined by the user
- * leaving the range of the saved SSIDs that were in range when the user disabled Wi-Fi.
- *
- * @hide
- */
-public class WifiWakeupController {
-    private static final String TAG = "WifiWakeupController";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
-    /** Number of scans to ensure that a previously in range AP is now out of range. */
-    private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
-
-    private final Context mContext;
-    private final ContentResolver mContentResolver;
-    private final WifiManager mWifiManager;
-    private final WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
-    private final Handler mHandler;
-    private final WifiWakeupNotificationHelper mWifiWakeupNotificationHelper;
-    private final AtomicBoolean mStarted;
-    @VisibleForTesting final ContentObserver mContentObserver;
-
-    private final Map<String, WifiConfiguration> mSavedNetworks = new ArrayMap<>();
-    private final Set<String> mSavedSsidsInLastScan = new ArraySet<>();
-    private final Set<String> mSavedSsids = new ArraySet<>();
-    private final Map<String, Integer> mSavedSsidsOnDisable = new ArrayMap<>();
-    private int mWifiState;
-    private int mWifiApState;
-    private boolean mWifiWakeupEnabled;
-    private boolean mAirplaneModeEnabled;
-
-    WifiWakeupController(Context context, ContentResolver contentResolver, Looper looper,
-            WifiManager wifiManager, WifiWakeupNetworkSelector wifiWakeupNetworkSelector,
-            WifiWakeupNotificationHelper wifiWakeupNotificationHelper) {
-        mContext = context;
-        mContentResolver = contentResolver;
-        mHandler = new Handler(looper);
-        mWifiWakeupNotificationHelper = wifiWakeupNotificationHelper;
-        mStarted = new AtomicBoolean(false);
-        mWifiManager = wifiManager;
-        mWifiWakeupNetworkSelector = wifiWakeupNetworkSelector;
-        mContentObserver = new ContentObserver(mHandler) {
-            @Override
-            public void onChange(boolean selfChange) {
-                mWifiWakeupEnabled = Settings.Global.getInt(mContentResolver,
-                        Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
-                mAirplaneModeEnabled = Settings.Global.getInt(mContentResolver,
-                        Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
-            }
-        };
-    }
-
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!mWifiWakeupEnabled) {
-                return;
-            }
-
-            if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(intent.getAction())) {
-                handleWifiApStateChanged();
-            } else if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
-                handleWifiStateChanged();
-            } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())) {
-                handleScanResultsAvailable();
-            } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(intent.getAction())) {
-                handleConfiguredNetworksChanged();
-            }
-        }
-    };
-
-    /** Starts {@link WifiWakeupController}. */
-    public void start() {
-        if (!mStarted.compareAndSet(false, true)) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "Starting WifiWakeupController.");
-        }
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-        filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
-        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
-        // TODO(b/33695273): conditionally register this receiver based on wifi enabled setting
-        mContext.registerReceiver(mBroadcastReceiver, filter, null, mHandler);
-        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
-                Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
-        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
-                Settings.Global.AIRPLANE_MODE_ON), true, mContentObserver);
-        mContentObserver.onChange(true);
-        handleWifiStateChanged();
-        handleWifiApStateChanged();
-        handleConfiguredNetworksChanged();
-        handleScanResultsAvailable();
-    }
-
-    /** Stops {@link WifiWakeupController}. */
-    public void stop() {
-        if (!mStarted.compareAndSet(true, false)) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "Stopping WifiWakeupController.");
-        }
-        mContext.unregisterReceiver(mBroadcastReceiver);
-        mContentResolver.unregisterContentObserver(mContentObserver);
-    }
-
-    private void handleWifiApStateChanged() {
-        mWifiApState = mWifiManager.getWifiApState();
-        if (VERBOSE) {
-            Log.v(TAG, "handleWifiApStateChanged: " + mWifiApState);
-        }
-    }
-
-    private void handleConfiguredNetworksChanged() {
-        List<WifiConfiguration> wifiConfigurations = mWifiManager.getConfiguredNetworks();
-        if (wifiConfigurations == null) {
-            return;
-        }
-        if (VERBOSE) {
-            Log.v(TAG, "handleConfiguredNetworksChanged: " + wifiConfigurations.size());
-        }
-
-        mSavedNetworks.clear();
-        mSavedSsids.clear();
-        for (int i = 0; i < wifiConfigurations.size(); i++) {
-            WifiConfiguration wifiConfiguration = wifiConfigurations.get(i);
-            if (wifiConfiguration.status != WifiConfiguration.Status.ENABLED
-                    && wifiConfiguration.status != WifiConfiguration.Status.CURRENT) {
-                continue; // Ignore networks that are not connected or enabled.
-            }
-            if (wifiConfiguration.useExternalScores) {
-                continue; // Ignore externally scored networks.
-            }
-            if (wifiConfiguration.hasNoInternetAccess()
-                    || wifiConfiguration.isNoInternetAccessExpected()) {
-                continue; // Ignore networks that will likely not have internet access.
-            }
-            String ssid = WifiConfigurationUtil.removeDoubleQuotes(wifiConfiguration);
-            if (TextUtils.isEmpty(ssid)) {
-                continue;
-            }
-            mSavedNetworks.put(ssid, wifiConfiguration);
-            mSavedSsids.add(ssid);
-        }
-        mSavedSsidsInLastScan.retainAll(mSavedSsids);
-    }
-
-    private void handleWifiStateChanged() {
-        mWifiState = mWifiManager.getWifiState();
-        if (VERBOSE) {
-            Log.v(TAG, "handleWifiStateChanged: " + mWifiState);
-        }
-        switch (mWifiState) {
-            case WifiManager.WIFI_STATE_ENABLED:
-                mSavedSsidsOnDisable.clear();
-                break;
-            case WifiManager.WIFI_STATE_DISABLED:
-                for (String ssid : mSavedSsidsInLastScan) {
-                    mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS);
-                }
-                break;
-        }
-    }
-
-    private void handleScanResultsAvailable() {
-        List<ScanResult> scanResults = mWifiManager.getScanResults();
-        if (scanResults == null) {
-            return;
-        }
-        if (VERBOSE) {
-            Log.v(TAG, "handleScanResultsAvailable: " + scanResults.size());
-        }
-
-        mSavedSsidsInLastScan.clear();
-        for (int i = 0; i < scanResults.size(); i++) {
-            String ssid = scanResults.get(i).SSID;
-            if (mSavedSsids.contains(ssid)) {
-                mSavedSsidsInLastScan.add(ssid);
-            }
-        }
-
-        if (mAirplaneModeEnabled
-                || mWifiState != WifiManager.WIFI_STATE_DISABLED
-                || mWifiApState != WifiManager.WIFI_AP_STATE_DISABLED) {
-            return;
-        }
-
-        // Update mSavedSsidsOnDisable to remove ssids that the user has moved away from.
-        for (Map.Entry<String, Integer> entry : mSavedSsidsOnDisable.entrySet()) {
-            if (mSavedSsidsInLastScan.contains(entry.getKey())) {
-                mSavedSsidsOnDisable.put(entry.getKey(), NUM_SCANS_TO_CONFIRM_AP_LOSS);
-            } else {
-                if (entry.getValue() > 1) {
-                    mSavedSsidsOnDisable.put(entry.getKey(), entry.getValue() - 1);
-                } else {
-                    mSavedSsidsOnDisable.remove(entry.getKey());
-                }
-            }
-        }
-
-        if (!mSavedSsidsOnDisable.isEmpty()) {
-            if (DEBUG) {
-                Log.d(TAG, "Latest scan result contains ssids from the disabled set: "
-                        + mSavedSsidsOnDisable);
-            }
-            return;
-        }
-
-        WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector.selectNetwork(mSavedNetworks,
-                scanResults);
-        if (selectedNetwork != null) {
-            if (DEBUG) {
-                Log.d(TAG, "Enabling wifi for ssid: " + selectedNetwork.SSID);
-            }
-            mWifiManager.setWifiEnabled(true /* enabled */);
-            mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(selectedNetwork);
-        }
-    }
-
-    void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("mStarted " + mStarted.get());
-        pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
-        pw.println("mSavedSsids: " + mSavedSsids);
-        pw.println("mSavedSsidsInLastScan: " + mSavedSsidsInLastScan);
-        pw.println("mSavedSsidsOnDisable: " + mSavedSsidsOnDisable);
-    }
-}
diff --git a/src/com/android/networkrecommendation/WifiWakeupNetworkSelector.java b/src/com/android/networkrecommendation/WifiWakeupNetworkSelector.java
deleted file mode 100644
index 94fce56..0000000
--- a/src/com/android/networkrecommendation/WifiWakeupNetworkSelector.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import android.content.res.Resources;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * This class determines which network the framework would connect to if Wi-Fi was enabled.
- */
-public class WifiWakeupNetworkSelector {
-    private final int mThresholdQualifiedRssi24;
-    private final int mThresholdQualifiedRssi5;
-    private final int mRssiScoreSlope;
-    private final int mRssiScoreOffset;
-    private final int mPasspointSecurityAward;
-    private final int mSecurityAward;
-    private final int mBand5GHzAward;
-    private final int mThresholdSaturatedRssi24;
-
-    public WifiWakeupNetworkSelector(Resources resources) {
-        mThresholdQualifiedRssi24 = resources.getInteger(
-                R.integer.config_netrec_wifi_score_low_rssi_threshold_24GHz);
-        mThresholdQualifiedRssi5 = resources.getInteger(
-                R.integer.config_netrec_wifi_score_low_rssi_threshold_5GHz);
-        mRssiScoreSlope = resources.getInteger(
-                R.integer.config_netrec_RSSI_SCORE_SLOPE);
-        mRssiScoreOffset = resources.getInteger(
-                R.integer.config_netrec_RSSI_SCORE_OFFSET);
-        mPasspointSecurityAward = resources.getInteger(
-                R.integer.config_netrec_PASSPOINT_SECURITY_AWARD);
-        mSecurityAward = resources.getInteger(
-                R.integer.config_netrec_SECURITY_AWARD);
-        mBand5GHzAward = resources.getInteger(
-                R.integer.config_netrec_5GHz_preference_boost_factor);
-        mThresholdSaturatedRssi24 = resources.getInteger(
-                R.integer.config_netrec_wifi_score_good_rssi_threshold_24GHz);
-    }
-
-    /**
-     * Returns the network that the framework would most likely connect to if Wi-Fi was enabled.
-     */
-    public WifiConfiguration selectNetwork(Map<String, WifiConfiguration> savedNetworks,
-            List<ScanResult> scanResults) {
-        WifiConfiguration candidateWifiConfiguration = null;
-        ScanResult candidateScanResult = null;
-        int candidateScore = -1;
-        for (int i = 0; i < scanResults.size(); i++) {
-            ScanResult scanResult = scanResults.get(i);
-            WifiConfiguration wifiConfiguration = savedNetworks.get(scanResult.SSID);
-            if (wifiConfiguration == null) {
-                continue;
-            }
-            if (ScanResultUtil.is5GHz(scanResult) && scanResult.level < mThresholdQualifiedRssi5
-                    || ScanResultUtil.is24GHz(scanResult)
-                    && scanResult.level < mThresholdQualifiedRssi24) {
-                continue;
-            }
-            if (!ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, wifiConfiguration)) {
-                continue;
-            }
-            int score = calculateScore(scanResult, wifiConfiguration);
-            if (candidateScanResult == null
-                    || calculateScore(scanResult, wifiConfiguration) > candidateScore) {
-                candidateScanResult = scanResult;
-                candidateWifiConfiguration = wifiConfiguration;
-                candidateScore = score;
-            }
-        }
-        return candidateWifiConfiguration;
-    }
-
-    private int calculateScore(ScanResult scanResult, WifiConfiguration wifiConfiguration) {
-        int score = 0;
-        // Calculate the RSSI score.
-        int rssi = scanResult.level <= mThresholdSaturatedRssi24
-                ? scanResult.level : mThresholdSaturatedRssi24;
-        score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
-
-        // 5GHz band bonus.
-        if (ScanResultUtil.is5GHz(scanResult)) {
-            score += mBand5GHzAward;
-        }
-
-        // Security award.
-        if (wifiConfiguration.isPasspoint()) {
-            score += mPasspointSecurityAward;
-        } else if (!WifiConfigurationUtil.isConfigForOpenNetwork(wifiConfiguration)) {
-            score += mSecurityAward;
-        }
-
-        return score;
-    }
-}
diff --git a/src/com/android/networkrecommendation/WifiWakeupNotificationHelper.java b/src/com/android/networkrecommendation/WifiWakeupNotificationHelper.java
deleted file mode 100644
index e044555..0000000
--- a/src/com/android/networkrecommendation/WifiWakeupNotificationHelper.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.res.Resources;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Settings;
-import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class for building and showing notifications for {@link WifiWakeupController}.
- */
-public class WifiWakeupNotificationHelper {
-    private static final String TAG = "WifiWakeupNotifHelper";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    /** Unique ID used for the Wi-Fi Enabled notification. */
-    private static final int NOTIFICATION_ID = R.string.wifi_wakeup_enabled_notification_title;
-    @VisibleForTesting
-    static final String KEY_SHOWN_SSIDS = "key_shown_ssids";
-    private static final String ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION =
-            "com.android.networkrecommendation.ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION";
-    private static final IntentFilter INTENT_FILTER = new IntentFilter();
-    private static final long NETWORK_CONNECTED_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30);
-
-    static {
-        INTENT_FILTER.addAction(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION);
-        INTENT_FILTER.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
-    }
-
-    private final Context mContext;
-    private final Resources mResources;
-    private final NotificationManager mNotificationManager;
-    private final Handler mHandler;
-    private final WifiManager mWifiManager;
-    private final SharedPreferences mSharedPreferences;
-
-    @VisibleForTesting
-    final Runnable mCancelNotification = new Runnable() {
-        @Override
-        public void run() {
-            cancelNotificationAndUnregisterReceiver();
-        }
-    };
-
-    @VisibleForTesting
-    final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION.equals(intent.getAction())) {
-                cancelNotificationAndUnregisterReceiver();
-            } else if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
-                networkStateChanged();
-            }
-        }
-    };
-    private boolean mNotificationShown;
-    private String mConnectedSsid;
-
-    public WifiWakeupNotificationHelper(Context context, Resources resources, Handler handler,
-            NotificationManager notificationManager, WifiManager wifiManager) {
-        this(context, resources, handler, notificationManager, wifiManager,
-                context.getSharedPreferences("wifi_wakeup", Context.MODE_PRIVATE));
-    }
-
-    @VisibleForTesting
-    WifiWakeupNotificationHelper(Context context, Resources resources, Handler handler,
-            NotificationManager notificationManager, WifiManager wifiManager,
-            SharedPreferences sharedPreferences) {
-        mContext = context;
-        mResources = resources;
-        mNotificationManager = notificationManager;
-        mHandler = handler;
-        mWifiManager = wifiManager;
-        mSharedPreferences = sharedPreferences;
-        mNotificationShown = false;
-        mConnectedSsid = null;
-    }
-
-    /**
-     * Show a notification that Wi-Fi has been enabled by Wi-Fi Wakeup.
-     *
-     * @param wifiConfiguration the {@link WifiConfiguration} that triggered Wi-Fi to wakeup
-     */
-    public void maybeShowWifiEnabledNotification(@NonNull WifiConfiguration wifiConfiguration) {
-        Set<String> ssidSet = mSharedPreferences.getStringSet(KEY_SHOWN_SSIDS, null);
-        if (ssidSet == null) {
-            ssidSet = new ArraySet<>();
-        } else if (ssidSet.contains(wifiConfiguration.SSID)) {
-            if (DEBUG) {
-                Log.d(TAG, "Already showed Wi-Fi Enabled notification for ssid: "
-                        + wifiConfiguration.SSID);
-            }
-            return;
-        }
-        ssidSet.add(wifiConfiguration.SSID);
-        mSharedPreferences.edit().putStringSet(KEY_SHOWN_SSIDS, ssidSet).apply();
-
-        String title = mResources.getString(
-                R.string.wifi_wakeup_enabled_notification_title);
-        String summary = mResources.getString(
-                R.string.wifi_wakeup_enabled_notification_context, wifiConfiguration.SSID);
-        PendingIntent savedNetworkSettingsPendingIntent = PendingIntent.getActivity(mContext, 0,
-                // TODO(b/34135303): update action
-                new Intent(Settings.ACTION_WIFI_SETTINGS),
-                PendingIntent.FLAG_UPDATE_CURRENT);
-        PendingIntent deletePendingIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION),
-                PendingIntent.FLAG_UPDATE_CURRENT);
-        Bundle extras = new Bundle();
-        extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME,
-                mResources.getString(R.string.android_system_label));
-        Notification notification = new Notification.Builder(mContext)
-                .setContentTitle(title)
-                .setSmallIcon(R.drawable.ic_wifi_signal_4)
-                .setStyle(new Notification.BigTextStyle().bigText(summary))
-                .setAutoCancel(true)
-                .setDeleteIntent(deletePendingIntent)
-                .setPriority(Notification.PRIORITY_LOW)
-                .setVisibility(Notification.VISIBILITY_PUBLIC)
-                .setCategory(Notification.CATEGORY_STATUS)
-                .setContentIntent(savedNetworkSettingsPendingIntent)
-                .addExtras(extras)
-                .build();
-        mNotificationManager.notify(TAG, NOTIFICATION_ID, notification);
-        mNotificationShown = true;
-        mContext.registerReceiver(mBroadcastReceiver, INTENT_FILTER, null /* broadcastPermission*/,
-                mHandler);
-        mHandler.postDelayed(mCancelNotification, NETWORK_CONNECTED_TIMEOUT_MILLIS);
-    }
-
-    private void cancelNotificationAndUnregisterReceiver() {
-        if (mNotificationShown) {
-            mNotificationShown = false;
-            mConnectedSsid = null;
-            mNotificationManager.cancel(TAG, NOTIFICATION_ID);
-            mContext.unregisterReceiver(mBroadcastReceiver);
-        }
-    }
-
-    private void networkStateChanged() {
-        if (!mWifiManager.isWifiEnabled()) {
-            cancelNotificationAndUnregisterReceiver();
-            return;
-        }
-
-        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
-        String ssid = wifiInfo == null ? null : wifiInfo.getSSID();
-        if (mConnectedSsid == null) {
-            mConnectedSsid = ssid;
-            mHandler.removeCallbacks(mCancelNotification);
-        } else {
-            if (!TextUtils.equals(ssid, mConnectedSsid)) {
-                cancelNotificationAndUnregisterReceiver();
-            }
-        }
-    }
-}
diff --git a/src/com/android/networkrecommendation/config/Csv.java b/src/com/android/networkrecommendation/config/Csv.java
new file mode 100644
index 0000000..35b6182
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/Csv.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2009 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.networkrecommendation.config;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Utilities for parsing and serializing Comma-Separated Value data.
+ * See http://en.wikipedia.org/wiki/Comma-separated_values and
+ * http://tools.ietf.org/html/rfc4180 for details of the format.
+ */
+public class Csv {
+    /** Field delimiter.  The C in CSV. */
+    public static final String COMMA = ",";
+
+    /** Record delimiter.  "Proper" CSV uses CR-LF, but that would be annoying. */
+    public static final String NEWLINE = "\n";
+
+    /**
+     * Appends a single value to an output string.  If the value
+     * contains quotes, commas, newlines, or leading or trailing whitespace,
+     * the value will be written in quotes (with internal quotes doubled).
+     * Newlines are converted to standard CR-LF form.  This function does not
+     * write field delimiters -- append {@link COMMA} yourself between values.
+     *
+     * @param value to write, quoted as necessary; must be non-null.
+     * @param output to append (possibly quoted) value to
+     * @throws java.io.IOException if writing to 'output' fails
+     */
+    public static void writeValue(String value, Appendable output) throws IOException {
+        int len = value.length();
+        if (len == 0) return;
+
+        char first = value.charAt(0);
+        char last = value.charAt(len - 1);
+        if (first != ' ' && first != '\t' && last != ' ' && last != '\t' &&
+                value.indexOf('"') < 0 && value.indexOf(',') < 0 &&
+                value.indexOf('\r') < 0 && value.indexOf('\n') < 0) {
+            // No quoting needed.
+            output.append(value);
+            return;
+        }
+
+        output.append('"').append(value.replace("\"", "\"\"")).append('"');
+    }
+
+    /**
+     * Parse a record of comma separated values from an input file.
+     * May read multiple physical lines if values contain embedded newlines.
+     *
+     * @param reader to read one or more physical lines from
+     * @param out array to append unquoted CSV values to
+     * @return true if values were read, false on EOF
+     * @throws java.io.IOException if reading from 'reader' fails
+     */
+    public static boolean parseLine(BufferedReader reader, List<String> out) throws IOException {
+        String text = reader.readLine();
+        if (text == null) return false;
+
+        int pos = 0;
+        do {
+            StringBuilder buf = new StringBuilder();
+            int comma;
+            for (;;) {
+                comma = text.indexOf(',', pos);
+                int quote = text.indexOf('"', pos);
+                if (quote == -1 || (comma != -1 && comma < quote)) break;
+
+                if (pos > 0 && text.charAt(pos - 1) == '"') buf.append('"');
+                buf.append(text, pos, quote);
+                while ((quote = text.indexOf('"', (pos = quote + 1))) == -1) {
+                    buf.append(text, pos, text.length()).append('\n');
+                    text = reader.readLine();
+                    if (text == null) {
+                        out.add(buf.toString());
+                        return true;
+                    }
+                    quote = -1;
+                }
+
+                buf.append(text, pos, quote);
+                pos = quote + 1;
+            }
+
+            buf.append(text, pos, comma == -1 ? text.length() : comma);
+            out.add(buf.toString());
+            pos = comma + 1;
+        } while (pos > 0);
+        return true;
+    }
+
+    private Csv() {}  // Do not instantiate
+}
diff --git a/src/com/android/networkrecommendation/config/Flag.java b/src/com/android/networkrecommendation/config/Flag.java
new file mode 100644
index 0000000..953fb1e
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/Flag.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.android.networkrecommendation.config;
+
+/**
+ * Simple configuration parameters for application behavior.
+ * @param <T> A type for a flag value.
+ */
+public class Flag<T> {
+
+    private final T mDefaultValue;
+    private T mOverride;
+
+    public Flag(T defaultValue) {
+        mDefaultValue = defaultValue;
+        mOverride = null;
+    }
+
+    /** Get the currently set flag value. */
+    public T get() {
+        return mOverride != null ? mOverride : mDefaultValue;
+    }
+
+    /** Force a value for testing. */
+    public void override(T value) {
+        mOverride = value;
+    }
+
+    /** Ensure flag state is initialized for tests. */
+    public static void initForTest() {
+    }
+}
diff --git a/src/com/android/networkrecommendation/config/G.java b/src/com/android/networkrecommendation/config/G.java
new file mode 100644
index 0000000..b256f25
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/G.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.networkrecommendation.config;
+
+/** Application behavior parameters. */
+public final class G {
+
+    /**
+     * Configurations related to general behaviors of the netrec module, not tied to a specific
+     * feature.
+     */
+    public interface Netrec {
+        Flag<Boolean> enableSensitiveLogging = new Flag(false);
+        Flag<String> wideAreaNetworks = new Flag("xfinitywifi,XFINITY");
+    }
+
+    private G() {}
+}
diff --git a/src/com/android/networkrecommendation/config/PreferenceFile.java b/src/com/android/networkrecommendation/config/PreferenceFile.java
new file mode 100644
index 0000000..9066a6a
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/PreferenceFile.java
@@ -0,0 +1,299 @@
+/*
+ * 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 com.android.networkrecommendation.config;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.os.Build;
+
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility class for retrieving and storing key/value pairs in a SharedPreferences file.
+ *
+ * Typical usage:
+ *
+ * In your application's onCreate();
+ * PreferenceFile.init(this);
+ *
+ * In common preferences declaration area:
+ * private static final PreferenceFile sFile = new PreferenceFile("my_prefs");
+ * public static final SharedPreference<String> pageUrl = sFile.value("page_url", "http://blah");
+ *
+ * At usage time:
+ * String pageUrl = Preferences.pageUrl.get();
+ * Preferences.pageUrl.put("http://www.newurl.com/");
+ */
+public class PreferenceFile {
+    private static final String TAG = "PreferenceFile";
+
+    private static Context sContext;
+
+    public static void init(Context context) {
+        sContext = context;
+    }
+
+    private final String mName;
+    private final int mMode;
+
+    @SuppressWarnings("deprecation")
+    public PreferenceFile(String name) {
+        this(name, Context.MODE_PRIVATE);
+    }
+
+    /**
+     * @deprecated any mode other than MODE_PRIVATE is a bad idea. If you need multi-process
+     * support, see if {@link MultiProcessPreferenceFile} is suitable.
+     */
+    @Deprecated
+    public PreferenceFile(String name, int mode) {
+        mName = name;
+        mMode = mode;
+    }
+
+    /**
+     * Returns a text dump of all preferences in this file; for debugging.
+     */
+    public String dump() {
+        SharedPreferences sp = open();
+        Map<String, ?> allPrefs = sp.getAll();
+        String format = "%" + longestString(allPrefs.keySet()) + "s = %s\n";
+        StringBuilder s = new StringBuilder();
+        for (String key : allPrefs.keySet()) {
+            s.append(String.format(Locale.US, format, key, allPrefs.get(key)));
+        }
+        return s.toString();
+    }
+
+    /** Return a SharedPreferences for this file. */
+    public SharedPreferences open() {
+        return sContext.getSharedPreferences(mName, mMode);
+    }
+
+    public void remove(SharedPreference<?>... preferences) {
+        SharedPreferences.Editor editor = open().edit();
+        for (SharedPreference<?> preference : preferences) {
+            editor.remove(preference.getKey());
+        }
+        commit(editor);
+    }
+
+    /** Synchronously clear all SharedPreferences in this file. */
+    public void clear() {
+        open().edit().clear().commit();
+    }
+
+
+    /**
+     * If on API >= 9, use the asynchronous
+     * {@link Editor#apply()} method. Otherwise, use the
+     * synchronous {@link Editor#commit()} method. <br />
+     * <br />
+     * If commit() is used, the result will be returned. If apply() is used, it
+     * will always return true.
+     *
+     * @param editor The editor to save
+     * @return the result of commit() on API &lt; 9 and true on API >= 9.
+     */
+    @SuppressLint("NewApi")
+    public static boolean commit(Editor editor) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
+            return editor.commit();
+        }
+        editor.apply();
+        return true;
+    }
+
+    /** Define a new Long value shared pref key. */
+    public SharedPreference<Long> longValue(final String key, final Long defaultValue) {
+        return new SharedPreference<Long>(this, key) {
+            @Override
+            protected Long read(SharedPreferences sp) {
+                if (sp.contains(key)) {
+                    return sp.getLong(key, 0L);
+                }
+                return defaultValue;
+            }
+
+            @Override
+            protected void write(Editor editor, Long value) {
+                if (value == null) {
+                    throw new IllegalArgumentException("null cannot be written for Long");
+                }
+                editor.putLong(key, value);
+            }
+        };
+    }
+
+    /** Define a new String value shared pref key. */
+    public SharedPreference<String> stringValue(final String key, final String defaultValue) {
+        return new SharedPreference<String>(this, key) {
+            @Override
+            protected String read(SharedPreferences sp) {
+                if (sp.contains(key)) {
+                    return sp.getString(key, null);
+                }
+                return defaultValue;
+            }
+
+            @Override
+            protected void write(Editor editor, String value) {
+                if (value == null) {
+                    throw new IllegalArgumentException("null cannot be written for String");
+                }
+                editor.putString(key, value);
+            }
+        };
+    }
+
+    /** Define a new Boolean value shared pref key. */
+    public SharedPreference<Boolean> booleanValue(final String key, final Boolean defaultValue) {
+        return new SharedPreference<Boolean>(this, key) {
+            @Override
+            protected Boolean read(SharedPreferences sp) {
+                if (sp.contains(key)) {
+                    return sp.getBoolean(key, false);
+                }
+                return defaultValue;
+            }
+
+            @Override
+            protected void write(Editor editor, Boolean value) {
+                if (value == null) {
+                    throw new IllegalArgumentException("null cannot be written for Boolean");
+                }
+                editor.putBoolean(key, value);
+            }
+        };
+    }
+
+    /** Define a new Integer value shared pref key. */
+    public SharedPreference<Integer> intValue(final String key, final Integer defaultValue) {
+        return new SharedPreference<Integer>(this, key) {
+            @Override
+            protected Integer read(SharedPreferences sp) {
+                if (sp.contains(key)) {
+                    return sp.getInt(key, 0);
+                }
+                return defaultValue;
+            }
+
+            @Override
+            protected void write(Editor editor, Integer value) {
+                if (value == null) {
+                    throw new IllegalArgumentException("null cannot be written for Integer");
+                }
+                editor.putInt(key, value);
+            }
+        };
+    }
+
+    /** Define a new Set<String> value shared pref key. */
+    public SharedPreference<Set<String>> stringSetValue(final String key,
+            final Set<String> defaultValue) {
+        return new SharedPreference<Set<String>>(this, key) {
+            @Override
+            protected Set<String> read(SharedPreferences sp) {
+                if (sp.contains(key)) {
+                    return sp.getStringSet(key, null);
+                }
+                return defaultValue;
+            }
+
+            @Override
+            protected void write(Editor editor, Set<String> value) {
+                if (value == null) {
+                    throw new IllegalArgumentException("null cannot be written for Set<String>");
+                }
+                editor.putStringSet(key, value);
+            }
+        };
+    }
+
+    /**
+     * A class representing a key/value pair in a given {@link PreferenceFile}.
+     */
+    public static abstract class SharedPreference<T> {
+        PreferenceFile mFile;
+        final String mKey;
+
+        protected SharedPreference(PreferenceFile file, String key) {
+            mFile = file;
+            mKey = key;
+        }
+
+        /** Read the value stored for this pref, or the default value if none is stored. */
+        public final T get() {
+            return read(mFile.open());
+        }
+
+        /** Get the representation in string of the value for this pref. */
+        public String getValueString() {
+            T value = get();
+            if (value == null) {
+                return null;
+            }
+            return value.toString();
+        }
+
+        /** Return this pref's key. */
+        public final String getKey() {
+            return mKey;
+        }
+
+        /** Return true if this key is defined in its file. */
+        public final boolean exists() {
+            return mFile.open().contains(mKey);
+        }
+
+        /** Write a new value for this pref to its file. */
+        public final void put(T value) {
+            SharedPreferences sp = mFile.open();
+            Editor editor = sp.edit();
+            write(editor, value);
+            commit(editor);
+        }
+
+        /** Removes this pref from its file. */
+        public final void remove() {
+            commit(mFile.open().edit().remove(mKey));
+        }
+
+        /** Override the PreferenceFile used by this preference (for testing). */
+        public final void override(PreferenceFile file) {
+            mFile = file;
+        }
+
+        protected abstract T read(SharedPreferences sp);
+        protected abstract void write(Editor editor, T value);
+    }
+
+    /**
+     * Returns the length of the longest string in the provided Collection.
+     */
+    private static int longestString(Collection<String> strings) {
+        int longest = 0;
+        for (String s : strings) {
+            longest = Math.max(longest, s.length());
+        }
+        return longest;
+    }
+}
diff --git a/src/com/android/networkrecommendation/config/Preferences.java b/src/com/android/networkrecommendation/config/Preferences.java
new file mode 100644
index 0000000..a9e6fb0
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/Preferences.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.android.networkrecommendation.config;
+
+import com.android.networkrecommendation.config.PreferenceFile.SharedPreference;
+import java.util.Collections;
+import java.util.Set;
+
+/** The NetRec preferences file. */
+public final class Preferences {
+    private Preferences() {}
+
+    private static final PreferenceFile sPrefs =
+            new PreferenceFile("com.android.networkrecommendation");
+
+    /**
+     * {@link ScoreNetworksChimeraBroadcastReceiver} sets this to true when the scorer is enabled.
+     * {@link com.android.networkrecommendation.scoring.service.FutureRefreshRequestor} checks for
+     * this value and triggers a quick score refresh if this is set.
+     */
+    public static final SharedPreference<Boolean> justEnabled =
+            sPrefs.booleanValue("justEnabled", false);
+
+    /**
+     * The next time, in ms since system boot, that a rapid (i.e. outside the usual refresh window)
+     * will be allowed to make a network request.
+     */
+    public static final SharedPreference<Long> nextRapidRefreshAllowed =
+            sPrefs.longValue("nextRapidRefreshAllowedMillis", 0L);
+
+    /**
+     * The set of saved ssid hashes in previous scan result list when the user disabled Wi-Fi. Saved
+     * to preferences when {@link com.android.networkrecommendation.wakeup.WifiWakeupController}
+     * stops.
+     */
+    public static final SharedPreference<Set<String>> savedSsidsOnDisable =
+            sPrefs.stringSetValue("savedSsidsOnDisable", Collections.emptySet());
+
+    /**
+     * The set of saved ssid hashes that were previously shown as Wi-Fi Enabled notifications
+     * through {@link com.android.networkrecommendation.wakeup.WifiWakeupController}.
+     */
+    public static final SharedPreference<Set<String>> ssidsForWakeupShown =
+            sPrefs.stringSetValue("ssidsForWakeupShown", Collections.emptySet());
+
+    /** Key for {@link com.android.networkrecommendation.storage.Encrypter} on pre-MNC devices. */
+    public static final SharedPreference<String> encrypterKey =
+            sPrefs.stringValue("encrypterKey", null);
+
+    /**
+     * How long we should wait before requesting another network. Used by {@link
+     * PersistentNetworkRequest} to support GCS/WFA.
+     */
+    public static final SharedPreference<Integer> nextNetworkRequestDelayMs =
+            sPrefs.intValue("nextNetworkRequestDelayMs", 0);
+
+    /**
+     * Hash of the SSID that last satisfied the network request in {@link PersistentNetworkRequest}.
+     */
+    public static final SharedPreference<String> lastSsidHash =
+            sPrefs.stringValue("lastSsidHash", "");
+}
diff --git a/src/com/android/networkrecommendation/config/WideAreaNetworks.java b/src/com/android/networkrecommendation/config/WideAreaNetworks.java
new file mode 100644
index 0000000..1a33559
--- /dev/null
+++ b/src/com/android/networkrecommendation/config/WideAreaNetworks.java
@@ -0,0 +1,63 @@
+/*
+ * 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 com.android.networkrecommendation.config;
+
+import static com.android.networkrecommendation.Constants.TAG;
+
+import android.support.annotation.VisibleForTesting;
+import com.android.networkrecommendation.config.G.Netrec;
+import com.android.networkrecommendation.util.Blog;
+import com.google.common.collect.ImmutableSet;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/** Provides a list of known wide area networks. */
+public class WideAreaNetworks {
+    private WideAreaNetworks() {}
+
+    private static ImmutableSet<String> sWideAreaNetworks;
+
+    /** Initialize the list of wide area networks from the phenotype flag. */
+    public static void init() {
+        sWideAreaNetworks = parseFlag();
+    }
+
+    /**
+     * @param ssid canonical SSID for a network (with quotes removed)
+     * @return {@code true} if {@code ssid} is in the set of wide area networks.
+     */
+    public static boolean contains(String ssid) {
+        if (sWideAreaNetworks == null) {
+            init();
+        }
+        return sWideAreaNetworks.contains(ssid);
+    }
+
+    @VisibleForTesting
+    static ImmutableSet<String> parseFlag() {
+        List<String> parts = new ArrayList<>();
+        BufferedReader reader = new BufferedReader(new StringReader(Netrec.wideAreaNetworks.get()));
+        try {
+            Csv.parseLine(reader, parts);
+        } catch (IOException ex) {
+            Blog.e(TAG, ex, "Error parsing flag");
+        }
+        return ImmutableSet.copyOf(parts);
+    }
+}
diff --git a/src/com/android/networkrecommendation/notify/WifiNotificationController.java b/src/com/android/networkrecommendation/notify/WifiNotificationController.java
new file mode 100644
index 0000000..c134b47
--- /dev/null
+++ b/src/com/android/networkrecommendation/notify/WifiNotificationController.java
@@ -0,0 +1,590 @@
+/*
+ * 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 com.android.networkrecommendation.notify;
+
+import static com.android.networkrecommendation.Constants.TAG;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.NetworkInfo;
+import android.net.NetworkScoreManager;
+import android.net.RecommendationRequest;
+import android.net.RecommendationResult;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
+import com.android.networkrecommendation.R;
+import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider;
+import com.android.networkrecommendation.util.Blog;
+import com.android.networkrecommendation.util.RoboCompatUtil;
+import com.android.networkrecommendation.util.ScanResultUtil;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Takes care of handling the "open wi-fi network available" notification */
+public class WifiNotificationController {
+    /** The unique ID of the Notification given to the NotificationManager. */
+    private static final int NOTIFICATION_ID = R.string.wifi_available_title;
+
+    /** When a notification is shown, we wait this amount before possibly showing it again. */
+    private final long mNotificationRepeatDelayMs;
+
+    /** Whether the user has set the setting to show the 'available networks' notification. */
+    private boolean mNotificationEnabled;
+
+    /** Whether the user has {@link UserManager#DISALLOW_CONFIG_WIFI} restriction. */
+    private boolean mWifiConfigRestricted;
+
+    /** Observes the user setting to keep {@link #mNotificationEnabled} in sync. */
+    private final NotificationEnabledSettingObserver mNotificationEnabledSettingObserver;
+
+    /**
+     * The {@link System#currentTimeMillis()} must be at least this value for us to show the
+     * notification again.
+     */
+    private long mNotificationRepeatTime;
+
+    /** These are all of the possible states for the open networks available notification. */
+    @IntDef({
+        State.NO_RECOMMENDATION,
+        State.SHOWING_RECOMMENDATION_NOTIFICATION,
+        State.CONNECTING_IN_NOTIFICATION,
+        State.CONNECTING_IN_WIFI_PICKER,
+        State.CONNECTED,
+        State.CONNECT_FAILED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface State {
+        int NO_RECOMMENDATION = 0;
+        int SHOWING_RECOMMENDATION_NOTIFICATION = 1;
+        int CONNECTING_IN_NOTIFICATION = 2;
+        int CONNECTING_IN_WIFI_PICKER = 3;
+        int CONNECTED = 4;
+        int CONNECT_FAILED = 5;
+    }
+
+    /**
+     * The {@link System#currentTimeMillis()} must be at least this value to log that open networks
+     * are available.
+     */
+    private long mOpenNetworksLoggingRepeatTime;
+
+    /** Current state of the notification. */
+    @State private int mState = State.NO_RECOMMENDATION;
+
+    /**
+     * The number of continuous scans that must occur before consider the supplicant in a scanning
+     * state. This allows supplicant to associate with remembered networks that are in the scan
+     * results.
+     */
+    private static final int NUM_SCANS_BEFORE_ACTUALLY_SCANNING = 3;
+
+    /**
+     * The number of scans since the last network state change. When this exceeds {@link
+     * #NUM_SCANS_BEFORE_ACTUALLY_SCANNING}, we consider the supplicant to actually be scanning.
+     * When the network state changes to something other than scanning, we reset this to 0.
+     */
+    private int mNumScansSinceNetworkStateChange;
+
+    /** Time in milliseconds to display the Connecting notification. */
+    private static final int TIME_TO_SHOW_CONNECTING_MILLIS = 10000;
+
+    /** Time in milliseconds to display the Connected notification. */
+    private static final int TIME_TO_SHOW_CONNECTED_MILLIS = 5000;
+
+    /** Time in milliseconds to display the Failed To Connect notification. */
+    private static final int TIME_TO_SHOW_FAILED_MILLIS = 5000;
+
+    /** Try to connect to the recommended WifiConfiguration and also open the wifi picker. */
+    static final String ACTION_CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS =
+            "com.android.networkrecommendation.notify.CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS";
+
+    /** Try to connect to the recommended WifiConfiguration. */
+    static final String ACTION_CONNECT_TO_RECOMMENDED_NETWORK =
+            "com.android.networkrecommendation.notify.CONNECT_TO_RECOMMENDED_NETWORK";
+
+    /** Open wifi picker to see all available networks. */
+    static final String ACTION_PICK_WIFI_NETWORK =
+            "com.android.networkrecommendation.notify.ACTION_PICK_WIFI_NETWORK";
+
+    /** Open wifi picker to see all networks after connect to the recommended network failed. */
+    static final String ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE =
+            "com.android.networkrecommendation.notify.ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE";
+
+    /** Handles behavior when notification is deleted. */
+    static final String ACTION_NOTIFICATION_DELETED =
+            "com.android.networkrecommendation.notify.NOTIFICATION_DELETED";
+
+    /** Network recommended by {@link NetworkScoreManager#requestRecommendation}. */
+    private WifiConfiguration mRecommendedNetwork;
+
+    /** Whether {@link WifiNotificationController} has been started. */
+    private final AtomicBoolean mStarted;
+
+    private static final String NOTIFICATION_TAG = "WifiNotification";
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final ContentResolver mContentResolver;
+    private final SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider;
+    private final WifiManager mWifiManager;
+    private final NotificationManager mNotificationManager;
+    private final UserManager mUserManager;
+    private final WifiNotificationHelper mWifiNotificationHelper;
+    private NetworkInfo mNetworkInfo;
+    private NetworkInfo.DetailedState mDetailedState;
+    private volatile int mWifiState;
+
+    public WifiNotificationController(
+            Context context,
+            ContentResolver contentResolver,
+            Handler handler,
+            SynchronousNetworkRecommendationProvider networkRecommendationProvider,
+            WifiManager wifiManager,
+            NotificationManager notificationManager,
+            UserManager userManager,
+            WifiNotificationHelper helper) {
+        mContext = context;
+        mContentResolver = contentResolver;
+        mNetworkRecommendationProvider = networkRecommendationProvider;
+        mWifiManager = wifiManager;
+        mNotificationManager = notificationManager;
+        mUserManager = userManager;
+        mHandler = handler;
+        mWifiNotificationHelper = helper;
+        mStarted = new AtomicBoolean(false);
+
+        // Setting is in seconds
+        mNotificationRepeatDelayMs =
+                TimeUnit.SECONDS.toMillis(
+                        Settings.Global.getInt(
+                                contentResolver,
+                                Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
+                                900));
+        mNotificationEnabledSettingObserver = new NotificationEnabledSettingObserver(mHandler);
+    }
+
+    /** Starts {@link WifiNotificationController}. */
+    public void start() {
+        if (!mStarted.compareAndSet(false, true)) {
+            return;
+        }
+
+        mWifiState = mWifiManager.getWifiState();
+        mDetailedState = NetworkInfo.DetailedState.IDLE;
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+        filter.addAction(RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED);
+        filter.addAction(ACTION_CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS);
+        filter.addAction(ACTION_CONNECT_TO_RECOMMENDED_NETWORK);
+        filter.addAction(ACTION_NOTIFICATION_DELETED);
+        filter.addAction(ACTION_PICK_WIFI_NETWORK);
+        filter.addAction(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
+
+        mContext.registerReceiver(
+                mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
+        mNotificationEnabledSettingObserver.register();
+
+        handleUserRestrictionsChanged();
+    }
+
+    /** Stops {@link WifiNotificationController}. */
+    public void stop() {
+        if (!mStarted.compareAndSet(true, false)) {
+            return;
+        }
+        mContext.unregisterReceiver(mBroadcastReceiver);
+        mNotificationEnabledSettingObserver.unregister();
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    try {
+                        switch (intent.getAction()) {
+                            case WifiManager.WIFI_STATE_CHANGED_ACTION:
+                                mWifiState = mWifiManager.getWifiState();
+                                resetNotification();
+                                break;
+                            case WifiManager.NETWORK_STATE_CHANGED_ACTION:
+                                handleNetworkStateChange(intent);
+                                break;
+                            case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
+                                checkAndSetNotification(mNetworkInfo);
+                                break;
+                            case RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED:
+                                handleUserRestrictionsChanged();
+                                break;
+                            case ACTION_CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS:
+                                connectToRecommendedNetwork();
+                                openWifiPicker();
+                                updateOnConnecting(false /* showNotification*/);
+                                break;
+                            case ACTION_CONNECT_TO_RECOMMENDED_NETWORK:
+                                connectToRecommendedNetwork();
+                                updateOnConnecting(true /* showNotification*/);
+                                break;
+                            case ACTION_NOTIFICATION_DELETED:
+                                handleNotificationDeleted();
+                                break;
+                            case ACTION_PICK_WIFI_NETWORK:
+                                openWifiPicker();
+                                break;
+                            case ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE:
+                                openWifiPicker();
+                                break;
+                            default:
+                                Blog.e(
+                                        TAG,
+                                        "Unexpected broadcast. [action=%s]",
+                                        intent.getAction());
+                        }
+
+                    } catch (RuntimeException re) {
+                        // TODO(b/35044022) Remove try/catch after a couple of releases when we are confident
+                        // this is not going to throw.
+                        Blog.e(TAG, re, "RuntimeException in broadcast receiver.");
+                    }
+                }
+            };
+
+    private void handleNetworkStateChange(Intent intent) {
+        mNetworkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
+        NetworkInfo.DetailedState detailedState = mNetworkInfo.getDetailedState();
+        if (detailedState != NetworkInfo.DetailedState.SCANNING
+                && detailedState != mDetailedState) {
+            mDetailedState = detailedState;
+            switch (mDetailedState) {
+                case CONNECTED:
+                    updateOnConnect();
+                    break;
+                case DISCONNECTED:
+                case CAPTIVE_PORTAL_CHECK:
+                    resetNotification();
+                    break;
+
+                    // TODO: figure out if these are failure cases when connecting
+                case IDLE:
+                case SCANNING:
+                case CONNECTING:
+                case DISCONNECTING:
+                case AUTHENTICATING:
+                case OBTAINING_IPADDR:
+                case SUSPENDED:
+                case FAILED:
+                case BLOCKED:
+                case VERIFYING_POOR_LINK:
+                    break;
+            }
+        }
+    }
+
+    private void handleUserRestrictionsChanged() {
+        mWifiConfigRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI);
+        Blog.v(TAG, "handleUserRestrictionsChanged: %b", mWifiConfigRestricted);
+    }
+
+    private void checkAndSetNotification(NetworkInfo networkInfo) {
+        // TODO: unregister broadcast so we do not have to check here
+        // If we shouldn't place a notification on available networks, then
+        // don't bother doing any of the following
+        if (!mNotificationEnabled
+                || mWifiConfigRestricted
+                || mWifiState != WifiManager.WIFI_STATE_ENABLED
+                || mState > State.SHOWING_RECOMMENDATION_NOTIFICATION) {
+            return;
+        }
+
+        NetworkInfo.State state = NetworkInfo.State.DISCONNECTED;
+        if (networkInfo != null) {
+            state = networkInfo.getState();
+        }
+
+        if (state == NetworkInfo.State.DISCONNECTED || state == NetworkInfo.State.UNKNOWN) {
+            maybeLogOpenNetworksAvailable();
+            RecommendationResult result = getOpenNetworkRecommendation();
+            if (result != null && result.getWifiConfiguration() != null) {
+                mRecommendedNetwork = result.getWifiConfiguration();
+
+                if (++mNumScansSinceNetworkStateChange >= NUM_SCANS_BEFORE_ACTUALLY_SCANNING) {
+                    /*
+                     * We have scanned continuously at least
+                     * NUM_SCANS_BEFORE_NOTIFICATION times. The user
+                     * probably does not have a remembered network in range,
+                     * since otherwise supplicant would have tried to
+                     * associate and thus resetting this counter.
+                     */
+                    displayNotification();
+                }
+                return;
+            }
+        }
+
+        // No open networks in range, remove the notification
+        removeNotification();
+    }
+
+    private void maybeLogOpenNetworksAvailable() {
+        long now = System.currentTimeMillis();
+        if (now < mOpenNetworksLoggingRepeatTime) {
+            return;
+        }
+        mOpenNetworksLoggingRepeatTime = now + mNotificationRepeatDelayMs;
+    }
+
+    /**
+     * Uses {@link NetworkScoreManager} to choose a qualified network out of the list of {@link
+     * ScanResult}s.
+     *
+     * @return returns the best qualified open networks, if any.
+     */
+    @Nullable
+    private RecommendationResult getOpenNetworkRecommendation() {
+        List<ScanResult> scanResults = mWifiManager.getScanResults();
+        if (scanResults == null || scanResults.isEmpty()) {
+            return null;
+        }
+
+        ArrayList<ScanResult> openNetworks = new ArrayList<>();
+        List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
+        for (ScanResult scanResult : scanResults) {
+            //A capability of [ESS] represents an open access point
+            //that is available for an STA to connect
+            //TODO: potentially handle this within NetworkRecommendationProvider instead.
+            if ("[ESS]".equals(scanResult.capabilities)) {
+                if (isSavedNetwork(scanResult, configuredNetworks)) {
+                    continue;
+                }
+                openNetworks.add(scanResult);
+            }
+        }
+
+        Blog.d(TAG, "Sending RecommendationRequest. [num_open_networks=%d]", openNetworks.size());
+        RecommendationRequest request =
+                new RecommendationRequest.Builder()
+                        .setScanResults(openNetworks.toArray(new ScanResult[openNetworks.size()]))
+                        .build();
+        return mNetworkRecommendationProvider.requestRecommendation(request);
+    }
+
+    /** Returns true if scanResult matches the list of saved networks */
+    private boolean isSavedNetwork(ScanResult scanResult, List<WifiConfiguration> savedNetworks) {
+        if (savedNetworks == null) {
+            return false;
+        }
+        for (int i = 0; i < savedNetworks.size(); i++) {
+            if (ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, savedNetworks.get(i))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Display's a notification that there are open Wi-Fi networks. */
+    private void displayNotification() {
+        // Since we use auto cancel on the notification, when the
+        // mNetworksAvailableNotificationShown is true, the notification may
+        // have actually been canceled.  However, when it is false we know
+        // for sure that it is not being shown (it will not be shown any other
+        // place than here)
+
+        // Not enough time has passed to show the notification again
+        if (mState == State.NO_RECOMMENDATION
+                && System.currentTimeMillis() < mNotificationRepeatTime) {
+            return;
+        }
+        Notification notification =
+                mWifiNotificationHelper.createMainNotification(mRecommendedNetwork);
+        mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelayMs;
+        postNotification(notification);
+        if (mState != State.SHOWING_RECOMMENDATION_NOTIFICATION) {
+            mState = State.SHOWING_RECOMMENDATION_NOTIFICATION;
+        }
+    }
+
+    /** Opens activity to allow the user to select a wifi network. */
+    private void openWifiPicker() {
+        // Close notification drawer before opening the picker.
+        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+        mContext.startActivity(
+                new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        removeNotification();
+    }
+
+    /** Attempts to connect to recommended network the recommended network. */
+    private void connectToRecommendedNetwork() {
+        if (mRecommendedNetwork == null) {
+            return;
+        }
+        mRecommendedNetwork.BSSID = null;
+
+        // Attempts to connect to recommended network.
+        RoboCompatUtil.getInstance().connectToWifi(mWifiManager, mRecommendedNetwork);
+    }
+
+    private void updateOnConnecting(boolean showNotification) {
+        if (showNotification) {
+            // Update notification to connecting status.
+            Notification notification =
+                    mWifiNotificationHelper.createConnectingNotification(mRecommendedNetwork);
+            postNotification(notification);
+            mState = State.CONNECTING_IN_NOTIFICATION;
+        } else {
+            mState = State.CONNECTING_IN_WIFI_PICKER;
+        }
+        mHandler.postDelayed(
+                () -> {
+                    updateOnFailedToConnect();
+                },
+                TIME_TO_SHOW_CONNECTING_MILLIS);
+    }
+
+    /**
+     * When detailed state changes to CONNECTED, show connected notification or reset notification.
+     */
+    private void updateOnConnect() {
+        if (mState == State.CONNECTING_IN_NOTIFICATION) {
+            Notification notification =
+                    mWifiNotificationHelper.createConnectedNotification(mRecommendedNetwork);
+            postNotification(notification);
+            mState = State.CONNECTED;
+            mHandler.postDelayed(
+                    () -> {
+                        if (mState == State.CONNECTED) {
+                            removeNotification();
+                        }
+                    },
+                    TIME_TO_SHOW_CONNECTED_MILLIS);
+        } else if (mState == State.CONNECTING_IN_WIFI_PICKER) {
+            removeNotification();
+        }
+    }
+
+    /**
+     * Displays the Failed To Connect notification after the Connecting notification is shown for
+     * {@link #TIME_TO_SHOW_CONNECTING_MILLIS} duration.
+     */
+    private void updateOnFailedToConnect() {
+        if (mState == State.CONNECTING_IN_NOTIFICATION) {
+            Notification notification = mWifiNotificationHelper.createFailedToConnectNotification();
+            postNotification(notification);
+            mState = State.CONNECT_FAILED;
+            mHandler.postDelayed(
+                    () -> {
+                        if (mState == State.CONNECT_FAILED) {
+                            removeNotification();
+                        }
+                    },
+                    TIME_TO_SHOW_FAILED_MILLIS);
+        } else if (mState == State.CONNECTING_IN_WIFI_PICKER) {
+            removeNotification();
+        }
+    }
+
+    /** Handles behavior when notification is dismissed. */
+    private void handleNotificationDeleted() {
+        mState = State.NO_RECOMMENDATION;
+        mRecommendedNetwork = null;
+    }
+
+    private void postNotification(Notification notification) {
+        mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
+    }
+
+    /**
+     * Clears variables related to tracking whether a notification has been shown recently and
+     * clears the current notification.
+     */
+    private void resetNotification() {
+        if (mState != State.NO_RECOMMENDATION) {
+            removeNotification();
+        }
+        mRecommendedNetwork = null;
+        mNotificationRepeatTime = 0;
+        mNumScansSinceNetworkStateChange = 0;
+        mOpenNetworksLoggingRepeatTime = 0;
+    }
+
+    private void removeNotification() {
+        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
+        mState = State.NO_RECOMMENDATION;
+        mRecommendedNetwork = null;
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("mNotificationEnabled " + mNotificationEnabled);
+        pw.println("mNotificationRepeatTime " + mNotificationRepeatTime);
+        pw.println("mState " + mState);
+        pw.println("mNumScansSinceNetworkStateChange " + mNumScansSinceNetworkStateChange);
+    }
+
+    private class NotificationEnabledSettingObserver extends ContentObserver {
+        NotificationEnabledSettingObserver(Handler handler) {
+            super(handler);
+        }
+
+        public void register() {
+            mContentResolver.registerContentObserver(
+                    Settings.Global.getUriFor(
+                            Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON),
+                    true,
+                    this);
+            mNotificationEnabled = getValue();
+        }
+
+        public void unregister() {
+            mContentResolver.unregisterContentObserver(this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+
+            mNotificationEnabled = getValue();
+            resetNotification();
+        }
+
+        private boolean getValue() {
+            return Settings.Global.getInt(
+                            mContentResolver,
+                            Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+                            0)
+                    == 1;
+        }
+    }
+}
diff --git a/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java b/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java
new file mode 100644
index 0000000..b19f25c
--- /dev/null
+++ b/src/com/android/networkrecommendation/notify/WifiNotificationHelper.java
@@ -0,0 +1,157 @@
+/*
+ * 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 com.android.networkrecommendation.notify;
+
+import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
+import static com.android.networkrecommendation.util.NotificationChannelUtil.CHANNEL_ID_NETWORK_AVAILABLE;
+
+import android.app.Notification;
+import android.app.Notification.Action;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.WifiConfiguration;
+import android.os.Bundle;
+import com.android.networkrecommendation.R;
+import com.android.networkrecommendation.util.NotificationChannelUtil;
+
+/** Helper class that creates notifications for {@link WifiNotificationController}. */
+public class WifiNotificationHelper {
+    private final Context mContext;
+
+    public WifiNotificationHelper(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Creates the main open networks notification with two actions. "Options" link to the Wi-Fi
+     * picker activity, and "Connect" prompts {@link WifiNotificationController} to connect to the
+     * recommended network. Tapping on the content body connects to the recommended network and
+     * opens the wifi picker
+     */
+    public Notification createMainNotification(WifiConfiguration config) {
+        PendingIntent allNetworksIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        new Intent(WifiNotificationController.ACTION_PICK_WIFI_NETWORK),
+                        FLAG_UPDATE_CURRENT);
+        Action allNetworksAction =
+                new Action.Builder(
+                                null /* icon */,
+                                mContext.getText(R.string.wifi_available_action_all_networks),
+                                allNetworksIntent)
+                        .build();
+        PendingIntent connectIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        new Intent(
+                                WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK),
+                        FLAG_UPDATE_CURRENT);
+        Action connectAction =
+                new Action.Builder(
+                                null /* icon */,
+                                mContext.getText(R.string.wifi_available_action_connect),
+                                connectIntent)
+                        .build();
+        PendingIntent connectAndOpenPickerIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        new Intent(
+                                WifiNotificationController
+                                        .ACTION_CONNECT_TO_RECOMMENDED_NETWORK_AND_OPEN_SETTINGS),
+                        FLAG_UPDATE_CURRENT);
+        return createNotificationBuilder(R.string.wifi_available_title, config.SSID)
+                .setContentIntent(connectAndOpenPickerIntent)
+                .addAction(connectAction)
+                .addAction(allNetworksAction)
+                .build();
+    }
+
+    /**
+     * Creates the notification that indicates the controller is attempting to connect to the
+     * recommended network.
+     */
+    public Notification createConnectingNotification(WifiConfiguration config) {
+        return createNotificationBuilder(R.string.wifi_available_title_connecting, config.SSID)
+                .setProgress(0 /* max */, 0 /* progress */, true /* indeterminate */)
+                .build();
+    }
+
+    /**
+     * Creates the notification that indicates the controller successfully connected to the
+     * recommended network.
+     */
+    public Notification createConnectedNotification(WifiConfiguration config) {
+        return createNotificationBuilder(R.string.wifi_available_title_connected, config.SSID)
+                .setAutoCancel(true)
+                .build();
+    }
+
+    /**
+     * Creates the notification that indicates the controller failed to connect to the recommended
+     * network. Tapping this notification opens the wifi picker.
+     */
+    public Notification createFailedToConnectNotification() {
+        PendingIntent openWifiPickerAfterFailure =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        new Intent(
+                                WifiNotificationController
+                                        .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE),
+                        FLAG_UPDATE_CURRENT);
+        return createNotificationBuilder(
+                        R.string.wifi_available_title_failed,
+                        mContext.getString(R.string.wifi_available_content_failed))
+                .setContentIntent(openWifiPickerAfterFailure)
+                .setAutoCancel(true)
+                .build();
+    }
+
+    private Notification.Builder createNotificationBuilder(int titleRid, String content) {
+        CharSequence title = mContext.getText(titleRid);
+        PendingIntent deleteIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        new Intent(WifiNotificationController.ACTION_NOTIFICATION_DELETED),
+                        FLAG_UPDATE_CURRENT);
+        int smallIcon = R.drawable.ic_signal_wifi_statusbar_not_connected;
+        Notification.Builder builder =
+                new Notification.Builder(mContext)
+                        .setDeleteIntent(deleteIntent)
+                        .setSmallIcon(smallIcon)
+                        .setTicker(title)
+                        .setContentTitle(title)
+                        .setColor(mContext.getColor(R.color.color_tint))
+                        .setContentText(content)
+                        .setShowWhen(false)
+                        .setLocalOnly(true)
+                        .addExtras(getOverrideLabelExtras());
+        return NotificationChannelUtil.setChannel(builder, CHANNEL_ID_NETWORK_AVAILABLE);
+    }
+
+    private Bundle getOverrideLabelExtras() {
+        Bundle extras = new Bundle();
+        extras.putString(
+                Notification.EXTRA_SUBSTITUTE_APP_NAME,
+                mContext.getString(R.string.notification_channel_group_name));
+        return extras;
+    }
+}
diff --git a/src/com/android/networkrecommendation/scoring/util/HashUtil.java b/src/com/android/networkrecommendation/scoring/util/HashUtil.java
new file mode 100644
index 0000000..19fbd6a
--- /dev/null
+++ b/src/com/android/networkrecommendation/scoring/util/HashUtil.java
@@ -0,0 +1,93 @@
+/*
+ * 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 com.android.networkrecommendation.scoring.util;
+
+import android.util.Base64;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/** Hashcode and encoding utils. */
+public final class HashUtil {
+    private HashUtil() {}
+
+    /**
+     * Returns a base64-encoded secure hash (using the SHA-256 algorithm) of the provided input.
+     *
+     * @param input the bytes for which the secure hash should be computed.
+     * @return the hash
+     */
+    public static String secureHash(String input) {
+        return encodeBase64(getHash(input, "SHA-256"));
+    }
+
+    public static String encodeBase64(byte[] input) {
+        return Base64.encodeToString(input, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+    }
+
+    /** Retrieves the message digest instance for a given hash algorithm. */
+    public static MessageDigest getMessageDigest(String hashAlgorithm) {
+        try {
+            return MessageDigest.getInstance(hashAlgorithm);
+        } catch (NoSuchAlgorithmException e) {
+            return null;
+        }
+    }
+
+    /**
+     * @return hash of input using hashAlgorithm, or 0-length byte array if input is null, or null
+     *     if hashAlgorithm can't be loaded
+     */
+    public static byte[] getHash(String input, String hashAlgorithm) {
+        if (input != null) {
+            MessageDigest digest = getMessageDigest(hashAlgorithm);
+            if (digest == null) {
+                return null;
+            }
+            return digest.digest(input.getBytes());
+        }
+        return new byte[0];
+    }
+
+    /**
+     * Gets an SSID-specific hash which also ensures the SSID is in cannonical form (stripped of
+     * quotes).
+     */
+    public static String getSsidHash(String ssid) {
+        return secureHash(NetworkUtil.canonicalizeSsid(ssid));
+    }
+
+    /** Gets a single hash of over the combined SSID and BSSID. */
+    public static String getSsidBssidHash(String ssid, String bssid) {
+        String canonicalSsid = NetworkUtil.canonicalizeSsid(ssid);
+        return secureHash(canonicalSsid + bssid);
+    }
+
+    /** Return the first 8 bytes of the SHA-256 hash of the given ssid as a long value. */
+    public static long hashAsLong(String ssid) {
+        byte[] h = getHash(ssid, "SHA-256");
+        if (h == null || h.length < 8) {
+            return 0;
+        }
+        return (h[0] & 0xFFL) << 56
+                | (h[1] & 0xFFL) << 48
+                | (h[2] & 0xFFL) << 40
+                | (h[3] & 0xFFL) << 32
+                | (h[4] & 0xFFL) << 24
+                | (h[5] & 0xFFL) << 16
+                | (h[6] & 0xFFL) << 8
+                | (h[7] & 0xFFL);
+    }
+}
diff --git a/src/com/android/networkrecommendation/scoring/util/NetworkUtil.java b/src/com/android/networkrecommendation/scoring/util/NetworkUtil.java
new file mode 100644
index 0000000..b27bbe6
--- /dev/null
+++ b/src/com/android/networkrecommendation/scoring/util/NetworkUtil.java
@@ -0,0 +1,142 @@
+/*
+ * 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 com.android.networkrecommendation.scoring.util;
+
+import static android.net.wifi.WifiConfiguration.KeyMgmt.IEEE8021X;
+import static android.net.wifi.WifiConfiguration.KeyMgmt.WPA_EAP;
+import static android.net.wifi.WifiConfiguration.KeyMgmt.WPA_PSK;
+import static com.android.networkrecommendation.util.ScanResultUtil.isScanResultForOpenNetwork;
+
+import android.content.Context;
+import android.net.NetworkKey;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import com.android.networkrecommendation.Constants;
+import com.android.networkrecommendation.util.Blog;
+import com.android.networkrecommendation.util.ScanResultUtil;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Network Utils. */
+public final class NetworkUtil {
+
+    private NetworkUtil() {
+        // do not instantiate
+    }
+
+    /**
+     * Canonicalize the given SSID returned by WifiInfo#getSSID().
+     *
+     * <p>This method should only be called once on a given SSID! If an SSID contains outer quotes,
+     * we will strip them twice and change the SSID to a different one.
+     *
+     * <p>The SSID should be returned surrounded by double quotation marks if it is valid UTF-8.
+     * This behavior was only implemented correctly after
+     * https://googleplex-android-review.googlesource.com/#/c/224602/ which went into JB-MR1.
+     *
+     * <p>This method does not account for non-UTF-8 SSIDs, which are returned as a string of hex
+     * digits from getSSID().
+     *
+     * <p>For more details, see: http://stackoverflow.com/questions/13563032
+     */
+    public static String canonicalizeSsid(String ssid) {
+        if (ssid == null) {
+            return null;
+        }
+        return removeQuotesIfNeeded(ssid);
+    }
+
+    /** Remove the leading quote and trailing quote. */
+    private static String removeQuotesIfNeeded(String text) {
+        if (text.length() > 1 && text.startsWith("\"") && text.endsWith("\"")) {
+            return text.substring(1, text.length() - 1);
+        }
+        return text;
+    }
+
+    /**
+     * @return a map from NetworkKey to true if that network is open, and false otherwise, for all
+     *     visible networks in the last set of Wi-Fi scan results.
+     */
+    public static Map<NetworkKey, Boolean> getOpenWifiNetworkKeys(Context context) {
+        WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        List<ScanResult> scanResults = null;
+        if (Util.isScorerActive(context)) {
+            try {
+                scanResults = wifiMgr.getScanResults();
+            } catch (SecurityException e) {
+                Blog.w(Constants.TAG, e, "No permission to get scan results");
+                scanResults = null;
+            }
+        }
+        if (scanResults == null) {
+            return Collections.emptyMap();
+        }
+        Map<NetworkKey, Boolean> openKeys = new HashMap<>();
+        for (int i = 0; i < scanResults.size(); i++) {
+            ScanResult scanResult = scanResults.get(i);
+            NetworkKey networkKey = ScanResultUtil.createNetworkKey(scanResult);
+            if (networkKey != null) {
+                openKeys.put(networkKey, isScanResultForOpenNetwork(scanResult));
+            }
+        }
+        return openKeys;
+    }
+
+    /** Returns true if the given config is for an "open" network. */
+    public static boolean isOpenNetwork(@NonNull WifiConfiguration config) {
+        if (config.allowedKeyManagement.get(WPA_PSK) // covers WPA_PSK and WPA2_PSK
+                || config.allowedKeyManagement.get(WPA_EAP)
+                || config.allowedKeyManagement.get(IEEE8021X)
+                || (config.wepKeys != null && config.wepKeys[0] != null)) {
+            return false;
+        }
+        return true;
+    }
+
+    @NonNull
+    public static String getCurrentWifiSsid(Context context) {
+        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+        return wifiInfo == null ? "" : wifiInfo.getSSID();
+    }
+
+    /** Returns the config for the given SSID, or null if one cannot be found. */
+    @Nullable
+    public static WifiConfiguration getConfig(Context context, String ssid) {
+        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
+        if (configs == null) {
+            return null;
+        }
+
+        WifiConfiguration config;
+        for (int i = 0; i < configs.size(); i++) {
+            config = configs.get(i);
+            if (TextUtils.equals(ssid, config.SSID)) {
+                return config;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/networkrecommendation/scoring/util/Util.java b/src/com/android/networkrecommendation/scoring/util/Util.java
new file mode 100644
index 0000000..4a8c80b
--- /dev/null
+++ b/src/com/android/networkrecommendation/scoring/util/Util.java
@@ -0,0 +1,71 @@
+/*
+ * 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 com.android.networkrecommendation.scoring.util;
+
+import static com.android.networkrecommendation.Constants.TAG;
+
+import android.content.Context;
+import android.net.NetworkScoreManager;
+import android.net.ScoredNetwork;
+import com.android.networkrecommendation.util.Blog;
+
+/** Utility methods for the scorer. */
+public final class Util {
+
+    /** @return true if GmsCore is the active network scorer. */
+    public static boolean isScorerActive(Context context) {
+        NetworkScoreManager scoreManager =
+                (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE);
+        final String activeScorer = scoreManager.getActiveScorerPackage();
+        final String packageName = context.getPackageName();
+
+        return packageName.equals(activeScorer);
+    }
+
+    /** Clear scores in the platform if we're the active scorer. */
+    public static boolean safelyClearScores(Context context) {
+        try {
+            NetworkScoreManager scoreManager =
+                    (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE);
+            scoreManager.clearScores();
+            return true;
+        } catch (SecurityException e) {
+            Blog.v(
+                    TAG,
+                    e,
+                    "SecurityException trying to clear scores, probably just an unavoidable race condition. "
+                            + "Ignoring.");
+            return false;
+        }
+    }
+
+    /** Update scores in the platform if we're the active scorer. */
+    public static boolean safelyUpdateScores(Context context, ScoredNetwork[] networkScores) {
+        try {
+            NetworkScoreManager scoreManager =
+                    (NetworkScoreManager) context.getSystemService(Context.NETWORK_SCORE_SERVICE);
+            scoreManager.updateScores(networkScores);
+            return true;
+        } catch (SecurityException e) {
+            Blog.v(
+                    TAG,
+                    e,
+                    "SecurityException trying to update scores, probably just an unavoidable race condition. "
+                            + "Ignoring.");
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/networkrecommendation/util/Blog.java b/src/com/android/networkrecommendation/util/Blog.java
new file mode 100644
index 0000000..47eff57
--- /dev/null
+++ b/src/com/android/networkrecommendation/util/Blog.java
@@ -0,0 +1,176 @@
+/*
+ * 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 com.android.networkrecommendation.util;
+
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.IllegalFormatException;
+import java.util.Locale;
+
+/**
+ * Wrapper for {@link Log} which adds the calling class/method to logged items.
+ *
+ * This works by traversing up the call stack and finding the first calling class whose name does
+ * not end with "Log" (allowing clients to add another layer such as AppLog when a constant tag is
+ * desired across the application).
+ */
+public final class Blog {
+    private static final String TAG = "Blog";
+
+    private Blog() {}
+
+    public static void i(String tag, String format, Object... args) {
+        Log.i(tag, buildMessage(format, args));
+    }
+
+    public static void i(String tag, Throwable tr, String format, Object... args) {
+        Log.i(tag, buildMessage(format, args), tr);
+    }
+
+    public static void v(String tag, String format, Object... args) {
+        // Not guarded with Log.isLoggable - these calls should be stripped out by proguard for
+        // release builds.
+        Log.v(tag, buildMessage(format, args));
+    }
+
+    public static void v(String tag, Throwable tr, String format, Object... args) {
+        // Not guarded with Log.isLoggable - these calls should be stripped out by proguard for
+        // release builds.
+        Log.v(tag, buildMessage(format, args), tr);
+    }
+
+    public static void d(String tag, String format, Object... args) {
+        if (Log.isLoggable(tag, Log.DEBUG)) {
+            Log.d(tag, buildMessage(format, args));
+        }
+    }
+
+    public static void d(String tag, Throwable tr, String format, Object... args) {
+        if (Log.isLoggable(tag, Log.DEBUG)) {
+            Log.d(tag, buildMessage(format, args), tr);
+        }
+    }
+
+    public static void w(String tag, Throwable tr, String format, Object... args) {
+        Log.w(tag, buildMessage(format, args), tr);
+    }
+
+    public static void w(String tag, String format, Object... args) {
+        Log.w(tag, buildMessage(format, args));
+    }
+
+    public static void e(String tag, String format, Object... args) {
+        Log.e(tag, buildMessage(format, args));
+    }
+
+    public static void e(String tag, Throwable tr, String format, Object... args) {
+        Log.e(tag, buildMessage(format, args), tr);
+    }
+
+    public static void wtf(String tag, String format, Object... args) {
+        // Ensure we always log a stack trace when calling Log.wtf.
+        Log.wtf(tag, buildMessage(format, args), new WhatATerribleException());
+    }
+
+    public static void wtf(String tag, Throwable tr, String format, Object...args) {
+        Log.wtf(tag, buildMessage(format, args), new WhatATerribleException(tr));
+    }
+
+    /**
+     * Redact personally identifiable information for production users.
+     *
+     * If:
+     *     1) String is null
+     *     2) String is empty
+     *     3) enableSensitiveLogging param passed in is true
+     *   Return the String value of the input object.
+     * Else:
+     *   Return a SHA-1 hash of the String value of the input object.
+     */
+    public static String pii(Object pii, boolean enableSensitiveLogging) {
+        String val = String.valueOf(pii);
+        if (pii == null || TextUtils.isEmpty(val) || enableSensitiveLogging) {
+            return val;
+        }
+        return "[" + secureHash(val.getBytes()) + "]";
+    }
+
+    /**
+     * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+     *
+     * @return the hash
+     * @param input the bytes for which the secure hash should be computed.
+     */
+    public static String secureHash(byte[] input) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("SHA-1");
+        } catch (NoSuchAlgorithmException e) {
+            return null;
+        }
+        messageDigest.update(input);
+        byte[] result = messageDigest.digest();
+        return Base64.encodeToString(
+                result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+    }
+
+    /**
+     * Formats the caller's provided message and prepends useful info like
+     * calling thread ID and method name.
+     */
+    private static String buildMessage(String format, Object... args) {
+        String msg;
+        try {
+            msg = (args == null || args.length == 0) ? format
+                    : String.format(Locale.US, format, args);
+        } catch (IllegalFormatException ife) {
+            String formattedArgs = Arrays.toString(args);
+            wtf(TAG, ife, "msg: \"%s\" args: %s", format, formattedArgs);
+            msg = format + " " + formattedArgs;
+        }
+        StackTraceElement[] trace = new Throwable().fillInStackTrace().getStackTrace();
+        String caller = "<unknown>";
+        // Walk up the stack looking for the first caller outside of Blog whose name doesn't end
+        // with "Log". It will be at least two frames up, so start there.
+        for (int i = 2; i < trace.length; i++) {
+            String callingClass = trace[i].getClassName();
+            if (!callingClass.equals(Blog.class.getName())
+                        && !callingClass.endsWith("Log")) {
+                callingClass = callingClass.substring(callingClass.lastIndexOf('.') + 1);
+                callingClass = callingClass.substring(callingClass.lastIndexOf('$') + 1);
+                caller = callingClass + "." + trace[i].getMethodName();
+                break;
+            }
+        }
+        return String.format(Locale.US, "[%d] %s: %s",
+                Thread.currentThread().getId(), caller, msg);
+    }
+
+    /** Named exception thrown when calling wtf(). */
+    public static class WhatATerribleException extends RuntimeException {
+        public WhatATerribleException() {
+            super();
+        }
+        public WhatATerribleException(Throwable t) {
+            super(t);
+        }
+    }
+}
diff --git a/src/com/android/networkrecommendation/util/NotificationChannelUtil.java b/src/com/android/networkrecommendation/util/NotificationChannelUtil.java
new file mode 100644
index 0000000..ebe5ff3
--- /dev/null
+++ b/src/com/android/networkrecommendation/util/NotificationChannelUtil.java
@@ -0,0 +1,69 @@
+/*
+ * 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 com.android.networkrecommendation.util;
+
+import android.app.Notification.Builder;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.NotificationManager;
+import android.content.Context;
+import com.android.networkrecommendation.R;
+
+/**
+ * Util class for managing {@link android.app.NotificationChannel}s for our notifications.
+ * TODO(netrec): b/36486429 Add tests for this class once relevant class constructors are supported
+ * for Robolectric.
+ */
+public class NotificationChannelUtil {
+
+    private static final String CHANNEL_GROUP_ID =
+            "com.android.networkrecommendation.Notifications.WifiMessageGroup";
+    public static final String CHANNEL_ID_NETWORK_AVAILABLE =
+            "com.android.networkrecommendation.Notifications.WifiMessageGroup.NetworkAvailableChannel";
+    public static final String CHANNEL_ID_WAKEUP =
+            "com.android.networkrecommendation.Notifications.WifiMessageGroup.WakeupChannel";
+
+    /** Configures the {@link android.app.NotificationChannel}s for our module. */
+    public static void configureNotificationChannels(
+            NotificationManager notificationManager, Context context) {
+        NotificationChannelGroup notificationChannelGroup =
+                new NotificationChannelGroup(
+                        CHANNEL_GROUP_ID,
+                        context.getString(R.string.notification_channel_group_name));
+        notificationManager.createNotificationChannelGroup(notificationChannelGroup);
+
+        NotificationChannel networkAvailableChannel =
+                new NotificationChannel(
+                        CHANNEL_ID_NETWORK_AVAILABLE,
+                        context.getString(R.string.notification_channel_network_available),
+                        NotificationManager.IMPORTANCE_LOW);
+        networkAvailableChannel.setGroup(CHANNEL_GROUP_ID);
+        notificationManager.createNotificationChannel(networkAvailableChannel);
+
+        NotificationChannel wakeupChannel =
+                new NotificationChannel(
+                        CHANNEL_ID_WAKEUP,
+                        context.getString(R.string.notification_channel_wakeup_name),
+                        NotificationManager.IMPORTANCE_LOW);
+        wakeupChannel.setGroup(CHANNEL_GROUP_ID);
+        notificationManager.createNotificationChannel(wakeupChannel);
+    }
+
+    /** Wraps Notification.Builder.setChannel. */
+    public static Builder setChannel(Builder notificationBuilder, String channelId) {
+        return notificationBuilder.setChannelId(channelId);
+    }
+}
diff --git a/src/com/android/networkrecommendation/util/OCompatFactory.java b/src/com/android/networkrecommendation/util/OCompatFactory.java
new file mode 100644
index 0000000..ef949cf
--- /dev/null
+++ b/src/com/android/networkrecommendation/util/OCompatFactory.java
@@ -0,0 +1,43 @@
+/*
+ * 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 com.android.networkrecommendation.util;
+
+import android.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.os.Bundle;
+
+/**
+ * Encapsulates the constructors for O-only objects to make unit testing possible, since those APIs
+ * are not supported by Robolectric yet. Once Robolectric supports O, we can remove this class.
+ */
+public class OCompatFactory {
+
+    /** Create a ScoredNetwork with the provided parameters. */
+    public static ScoredNetwork createScoredNetworkOCompat(
+            NetworkKey networkToScore,
+            RssiCurve rssiCurve,
+            boolean meteredHint,
+            Bundle attributesBundle) {
+        return new ScoredNetwork(networkToScore, rssiCurve, meteredHint, attributesBundle);
+    }
+
+    /** Create an RssiCurve with the provided parameters. */
+    public static RssiCurve createRssiCurveOCompat(
+            int start, int bucketWidth, byte[] rssiBuckets, int activeNetworkRssiBoost) {
+        return new RssiCurve(start, bucketWidth, rssiBuckets, activeNetworkRssiBoost);
+    }
+}
diff --git a/src/com/android/networkrecommendation/util/RoboCompatUtil.java b/src/com/android/networkrecommendation/util/RoboCompatUtil.java
new file mode 100644
index 0000000..7aa0d7b
--- /dev/null
+++ b/src/com/android/networkrecommendation/util/RoboCompatUtil.java
@@ -0,0 +1,99 @@
+/*
+ * 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 com.android.networkrecommendation.util;
+
+import android.content.res.Resources.Theme;
+import android.graphics.drawable.Drawable;
+import android.net.NetworkBadging;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
+
+/**
+ * This class provides access to @SystemApi methods that were added in Android O and not yet
+ * available in using the --config robo_experimental configuration when testing.
+ */
+public class RoboCompatUtil {
+
+    /**
+     * {@link UserManager#ACTION_USER_RESTRICTIONS_CHANGED}. TODO: remove when string is available
+     * in experimental.
+     */
+    public static final String ACTION_USER_RESTRICTIONS_CHANGED =
+            "android.os.action.USER_RESTRICTIONS_CHANGED";
+
+    private static RoboCompatUtil mRoboCompatUtil;
+
+    private RoboCompatUtil() {}
+
+    /** Get a shared instance of this utility. */
+    public static synchronized RoboCompatUtil getInstance() {
+        if (mRoboCompatUtil == null) {
+            mRoboCompatUtil = new RoboCompatUtil();
+        }
+        return mRoboCompatUtil;
+    }
+
+    @VisibleForTesting
+    public static void setInstanceForTesting(RoboCompatUtil roboCompatUtil) {
+        mRoboCompatUtil = roboCompatUtil;
+    }
+
+    /** Wraps WifiManager.connect. */
+    public void connectToWifi(WifiManager wifiManager, WifiConfiguration wifiConfiguration) {
+        wifiManager.connect(wifiConfiguration, null /* actionListener */);
+    }
+
+    /** Wraps WifiConfiguration.hasNoInternetAccess. */
+    @SuppressWarnings("unchecked")
+    public boolean hasNoInternetAccess(WifiConfiguration wifiConfiguration) {
+        return wifiConfiguration.hasNoInternetAccess();
+    }
+
+    /** Wraps WifiConfiguration.isNoInternetAccessExpected. */
+    public boolean isNoInternetAccessExpected(WifiConfiguration wifiConfiguration) {
+        return wifiConfiguration.isNoInternetAccessExpected();
+    }
+
+    /** Wraps WifiConfiguration.useExternalScores. */
+    public boolean useExternalScores(WifiConfiguration wifiConfiguration) {
+        return wifiConfiguration.useExternalScores;
+    }
+
+    /** Wraps WifiConfiguration.isPasspoint. */
+    public boolean isPasspoint(WifiConfiguration wifiConfiguration) {
+        return wifiConfiguration.isPasspoint();
+    }
+
+    /** Wraps NetworkBadging.getWifiIcon. */
+    public Drawable getWifiIcon(int signalLevel, int badging, Theme theme) {
+        return NetworkBadging.getWifiIcon(signalLevel, badging, theme);
+    }
+
+    /** Wraps RssiCurve.activeNetworkRssiBoost. */
+    public int activeNetworkRssiBoost(RssiCurve curve) {
+        return curve.activeNetworkRssiBoost;
+    }
+
+    /** Wraps ScoredNetwork.attributes. */
+    public Bundle attributes(ScoredNetwork scoredNetwork) {
+        return scoredNetwork.attributes;
+    }
+}
diff --git a/src/com/android/networkrecommendation/ScanResultUtil.java b/src/com/android/networkrecommendation/util/ScanResultUtil.java
similarity index 64%
rename from src/com/android/networkrecommendation/ScanResultUtil.java
rename to src/com/android/networkrecommendation/util/ScanResultUtil.java
index 08b71d0..4587e91 100644
--- a/src/com/android/networkrecommendation/ScanResultUtil.java
+++ b/src/com/android/networkrecommendation/util/ScanResultUtil.java
@@ -13,39 +13,44 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.networkrecommendation.util;
 
-package com.android.networkrecommendation;
+import static com.android.networkrecommendation.Constants.TAG;
+import static com.android.networkrecommendation.util.SsidUtil.quoteSsid;
 
+import android.net.NetworkKey;
+import android.net.WifiKey;
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
-import android.support.annotation.VisibleForTesting;
+import android.support.annotation.Nullable;
 import android.text.TextUtils;
+import com.android.networkrecommendation.config.G;
 
 /**
- * Scan result utility for any {@link ScanResult} related operations.
- * TODO(b/34125341): Delete this class once exposed as a SystemApi
+ * Scan result utility for any {@link ScanResult} related operations. TODO(b/34125341): Delete this
+ * class once exposed as a SystemApi
  */
 public class ScanResultUtil {
 
     /**
-     * Helper method to check if the provided |scanResult| corresponds to a PSK network or not.
-     * This checks if the provided capabilities string contains PSK encryption type or not.
+     * Helper method to check if the provided |scanResult| corresponds to a PSK network or not. This
+     * checks if the provided capabilities string contains PSK encryption type or not.
      */
     public static boolean isScanResultForPskNetwork(ScanResult scanResult) {
         return scanResult.capabilities.contains("PSK");
     }
 
     /**
-     * Helper method to check if the provided |scanResult| corresponds to a EAP network or not.
-     * This checks if the provided capabilities string contains EAP encryption type or not.
+     * Helper method to check if the provided |scanResult| corresponds to a EAP network or not. This
+     * checks if the provided capabilities string contains EAP encryption type or not.
      */
     public static boolean isScanResultForEapNetwork(ScanResult scanResult) {
         return scanResult.capabilities.contains("EAP");
     }
 
     /**
-     * Helper method to check if the provided |scanResult| corresponds to a WEP network or not.
-     * This checks if the provided capabilities string contains WEP encryption type or not.
+     * Helper method to check if the provided |scanResult| corresponds to a WEP network or not. This
+     * checks if the provided capabilities string contains WEP encryption type or not.
      */
     public static boolean isScanResultForWepNetwork(ScanResult scanResult) {
         return scanResult.capabilities.contains("WEP");
@@ -57,17 +62,43 @@
      * encryption types or not.
      */
     public static boolean isScanResultForOpenNetwork(ScanResult scanResult) {
-        return !(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult)
+        return !(isScanResultForWepNetwork(scanResult)
+                || isScanResultForPskNetwork(scanResult)
                 || isScanResultForEapNetwork(scanResult));
     }
 
+    /** Create a {@link NetworkKey} from a ScanResult, properly quoting the SSID. */
+    @Nullable
+    public static final NetworkKey createNetworkKey(ScanResult scanResult) {
+        WifiKey wifiKey = createWifiKey(scanResult);
+        if (wifiKey == null) {
+            return null;
+        }
+        return new NetworkKey(wifiKey);
+    }
+
     /**
      * Helper method to quote the SSID in Scan result to use for comparing/filling SSID stored in
      * WifiConfiguration object.
      */
-    @VisibleForTesting
-    public static String createQuotedSSID(String ssid) {
-        return "\"" + ssid + "\"";
+    @Nullable
+    public static WifiKey createWifiKey(ScanResult result) {
+        if (result == null) {
+            Blog.e(TAG, "Couldn't create WifiKey, provided scan result is null.");
+            return null;
+        }
+        try {
+            return new WifiKey(quoteSsid(result.SSID), result.BSSID);
+        } catch (IllegalArgumentException | NullPointerException e) {
+            // Expect IllegalArgumentException only in Android O.
+            Blog.e(
+                    TAG,
+                    e,
+                    "Couldn't make a wifi key from %s/%s",
+                    Blog.pii(result.SSID, G.Netrec.enableSensitiveLogging.get()),
+                    Blog.pii(result.BSSID, G.Netrec.enableSensitiveLogging.get()));
+            return null;
+        }
     }
 
     /** @return {@code true} if the result is for a 2.4GHz network. */
@@ -91,13 +122,13 @@
     }
 
     /**
-     * Checks if the provided |scanResult| match with the provided |config|. Essentially checks
-     * if the network config and scan result have the same SSID and encryption type.
+     * Checks if the provided |scanResult| match with the provided |config|. Essentially checks if
+     * the network config and scan result have the same SSID and encryption type.
      */
     public static boolean doesScanResultMatchWithNetwork(
             ScanResult scanResult, WifiConfiguration config) {
         // Add the double quotes to the scan result SSID for comparison with the network configs.
-        String configSSID = createQuotedSSID(scanResult.SSID);
+        String configSSID = quoteSsid(scanResult.SSID);
         if (TextUtils.equals(config.SSID, configSSID)) {
             if (ScanResultUtil.isScanResultForPskNetwork(scanResult)
                     && WifiConfigurationUtil.isConfigForPskNetwork(config)) {
diff --git a/src/com/android/networkrecommendation/util/SsidUtil.java b/src/com/android/networkrecommendation/util/SsidUtil.java
new file mode 100644
index 0000000..b15ac5f
--- /dev/null
+++ b/src/com/android/networkrecommendation/util/SsidUtil.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 com.android.networkrecommendation.util;
+
+import static com.android.networkrecommendation.Constants.TAG;
+
+import android.net.WifiKey;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import com.android.networkrecommendation.config.G;
+
+/** Utility methods for Wifi Network SSID and BSSID manipulation. */
+public final class SsidUtil {
+
+    // A special BSSID used to indicate a wildcard/ignore.
+    // The MAC address this refers to is reserved by IANA
+    // http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml
+    public static final String BSSID_IGNORE = "00:00:5E:00:00:00";
+
+    /** Quote an SSID if it hasn't already been quoted. */
+    @Nullable
+    public static String quoteSsid(@Nullable String ssid) {
+        if (ssid == null) {
+            return null;
+        }
+        if (isValidQuotedSsid(ssid)) {
+            return ssid;
+        }
+        return "\"" + ssid + "\"";
+    }
+
+    /** Strip initial and final quotations marks from an SSID. */
+    public static String unquoteSsid(@Nullable String ssid) {
+        if (ssid == null) {
+            return null;
+        }
+        return ssid.replaceAll("^\"", "").replaceAll("\"$", "");
+    }
+
+    /**
+     * Create a WifiKey for the given SSID/BSSID. Returns null if the key could not be created
+     * (ssid/bssid are not valid patterns).
+     */
+    @Nullable
+    public static WifiKey createWifiKey(String ssid, String bssid) {
+        try {
+            return new WifiKey(quoteSsid(ssid), bssid);
+        } catch (IllegalArgumentException | NullPointerException e) {
+            // Expect IllegalArgumentException only in Android O.
+            Blog.e(
+                    TAG,
+                    e,
+                    "Couldn't make a wifi key from %s/%s",
+                    Blog.pii(ssid, G.Netrec.enableSensitiveLogging.get()),
+                    Blog.pii(bssid, G.Netrec.enableSensitiveLogging.get()));
+            return null;
+        }
+    }
+
+    /**
+     * Returns true if the given string will be accepted as an SSID by WifiKey, especially meaning
+     * it is quoted.
+     */
+    public static boolean isValidQuotedSsid(@Nullable String ssid) {
+        return ssid != null && ssid.startsWith("\"") && ssid.endsWith("\"");
+    }
+
+    /** Thows IllegalArgumentException if the given string cannot be used for an SSID in WifiKey. */
+    public static void checkIsValidQuotedSsid(String ssid) {
+        if (!isValidQuotedSsid(ssid)) {
+            throw new IllegalArgumentException("SSID " + ssid + " expected to be quoted");
+        }
+    }
+
+    /**
+     * Returns true if the canonical version of two SSIDs (ignoring wrapping quotations) is equal.
+     */
+    public static boolean areEqual(@Nullable String ssid1, @Nullable String ssid2) {
+        String quotedSsid1 = quoteSsid(ssid1);
+        String quotedSsid2 = quoteSsid(ssid2);
+        return TextUtils.equals(quotedSsid1, quotedSsid2);
+    }
+
+    /**
+     * Returns a string version of the SSID for logging which is typically redacted.
+     *
+     * <p>The ID will only be returned verbatim if the enableSensitiveLogging flag is set.
+     */
+    public static String getRedactedId(String ssid) {
+        return Blog.pii(String.format("%s", ssid), G.Netrec.enableSensitiveLogging.get());
+    }
+
+    /**
+     * Returns a string version of the SSID/BSSID pair for logging which is typically redacted.
+     *
+     * <p>The IDs will only be returned verbatim if the enableSensitiveLogging flag is set.
+     */
+    public static String getRedactedId(String ssid, String bssid) {
+        return Blog.pii(String.format("%s/%s", ssid, bssid), G.Netrec.enableSensitiveLogging.get());
+    }
+
+    // Can't instantiate.
+    private SsidUtil() {}
+}
diff --git a/src/com/android/networkrecommendation/WifiConfigurationUtil.java b/src/com/android/networkrecommendation/util/WifiConfigurationUtil.java
similarity index 98%
rename from src/com/android/networkrecommendation/WifiConfigurationUtil.java
rename to src/com/android/networkrecommendation/util/WifiConfigurationUtil.java
index d653a4f..3616cd7 100644
--- a/src/com/android/networkrecommendation/WifiConfigurationUtil.java
+++ b/src/com/android/networkrecommendation/util/WifiConfigurationUtil.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.networkrecommendation;
+package com.android.networkrecommendation.util;
 
 import android.net.wifi.WifiConfiguration;
 
diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java
new file mode 100644
index 0000000..4c9b4c1
--- /dev/null
+++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupController.java
@@ -0,0 +1,404 @@
+/*
+ * 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 com.android.networkrecommendation.wakeup;
+
+import static com.android.networkrecommendation.Constants.TAG;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import com.android.networkrecommendation.config.G;
+import com.android.networkrecommendation.config.Preferences;
+import com.android.networkrecommendation.config.WideAreaNetworks;
+import com.android.networkrecommendation.scoring.util.HashUtil;
+import com.android.networkrecommendation.util.Blog;
+import com.android.networkrecommendation.util.RoboCompatUtil;
+import com.android.networkrecommendation.util.WifiConfigurationUtil;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Handles enabling Wi-Fi for the Wi-Fi Wakeup feature.
+ *
+ * <p>This class enables Wi-Fi when the user is near a network that would would autojoined if Wi-Fi
+ * were enabled. When a user disables Wi-Fi, Wi-Fi Wakeup will not enable Wi-Fi until the user's
+ * context has changed. For saved networks, this context change is defined by the user leaving the
+ * range of the saved SSIDs that were in range when the user disabled Wi-Fi.
+ *
+ * @hide
+ */
+public class WifiWakeupController {
+    /** Number of scans to ensure that a previously in range AP is now out of range. */
+    private static final int NUM_SCANS_TO_CONFIRM_AP_LOSS = 3;
+
+    private final Context mContext;
+    private final ContentResolver mContentResolver;
+    private final WifiManager mWifiManager;
+    private final PowerManager mPowerManager;
+    private final UserManager mUserManager;
+    private final WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
+    private final Handler mHandler;
+    private final WifiWakeupHelper mWifiWakeupHelper;
+    private final AtomicBoolean mStarted;
+    @VisibleForTesting final ContentObserver mContentObserver;
+
+    private final Map<String, WifiConfiguration> mSavedNetworks = new ArrayMap<>();
+    private final Set<String> mSavedSsidsInLastScan = new ArraySet<>();
+    private final Set<String> mSavedSsids = new ArraySet<>();
+    private final Map<String, Integer> mSavedSsidsOnDisable = new ArrayMap<>();
+    private final SavedNetworkCounts mSavedNetworkCounts = new SavedNetworkCounts();
+    private int mWifiState;
+    private int mWifiApState;
+    private boolean mWifiWakeupEnabled;
+    private boolean mAirplaneModeEnabled;
+    private boolean mAutopilotEnabledWifi;
+    private boolean mPowerSaverModeOn;
+    private boolean mWifiConfigRestricted;
+
+    public WifiWakeupController(
+            Context context,
+            ContentResolver contentResolver,
+            Handler handler,
+            WifiManager wifiManager,
+            PowerManager powerManager,
+            UserManager userManager,
+            WifiWakeupNetworkSelector wifiWakeupNetworkSelector,
+            WifiWakeupHelper wifiWakeupHelper) {
+        mContext = context;
+        mContentResolver = contentResolver;
+        mHandler = handler;
+        mWifiWakeupHelper = wifiWakeupHelper;
+        mStarted = new AtomicBoolean(false);
+        mWifiManager = wifiManager;
+        mPowerManager = powerManager;
+        mUserManager = userManager;
+        mWifiWakeupNetworkSelector = wifiWakeupNetworkSelector;
+        mContentObserver =
+                new ContentObserver(mHandler) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        mWifiWakeupEnabled =
+                                Settings.Global.getInt(
+                                                mContentResolver,
+                                                Settings.Global.WIFI_WAKEUP_ENABLED,
+                                                0)
+                                        == 1;
+                        mAirplaneModeEnabled =
+                                Settings.Global.getInt(
+                                                mContentResolver,
+                                                Settings.Global.AIRPLANE_MODE_ON,
+                                                0)
+                                        == 1;
+                        Blog.d(
+                                TAG,
+                                "onChange: [mWifiWakeupEnabled=%b,mAirplaneModeEnabled=%b]",
+                                mWifiWakeupEnabled,
+                                mAirplaneModeEnabled);
+                    }
+                };
+    }
+
+    private final BroadcastReceiver mBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    try {
+                        if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(intent.getAction())) {
+                            handleWifiApStateChanged();
+                        } else if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(
+                                intent.getAction())) {
+                            handleWifiStateChanged(false);
+                        } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(
+                                intent.getAction())) {
+                            handleScanResultsAvailable();
+                        } else if (WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(
+                                intent.getAction())) {
+                            handleConfiguredNetworksChanged();
+                        } else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(
+                                intent.getAction())) {
+                            handlePowerSaverModeChanged();
+                        } else if (RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED.equals(
+                                intent.getAction())) {
+                            handleUserRestrictionsChanged();
+                        }
+                    } catch (RuntimeException re) {
+                        // TODO(b/35044022) Remove try/catch after a couple of releases when we are confident
+                        // this is not going to throw.
+                        Blog.e(TAG, re, "RuntimeException in broadcast receiver.");
+                    }
+                }
+            };
+
+    /** Starts {@link WifiWakeupController}. */
+    public void start() {
+        if (!mStarted.compareAndSet(false, true)) {
+            return;
+        }
+        Blog.d(TAG, "Starting WifiWakeupController.");
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
+        filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
+        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
+        filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+        filter.addAction(RoboCompatUtil.ACTION_USER_RESTRICTIONS_CHANGED);
+        // TODO(b/33695273): conditionally register this receiver based on wifi enabled setting
+        mContext.registerReceiver(mBroadcastReceiver, filter, null, mHandler);
+        mContentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.WIFI_WAKEUP_ENABLED),
+                true,
+                mContentObserver);
+        mContentResolver.registerContentObserver(
+                Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
+                true,
+                mContentObserver);
+        mContentObserver.onChange(true);
+        handlePowerSaverModeChanged();
+        handleUserRestrictionsChanged();
+        handleWifiApStateChanged();
+        handleConfiguredNetworksChanged();
+        handleWifiStateChanged(true);
+        handleScanResultsAvailable();
+    }
+
+    /** Stops {@link WifiWakeupController}. */
+    public void stop() {
+        if (!mStarted.compareAndSet(true, false)) {
+            return;
+        }
+        Blog.d(TAG, "Stopping WifiWakeupController.");
+        mContext.unregisterReceiver(mBroadcastReceiver);
+        mContentResolver.unregisterContentObserver(mContentObserver);
+    }
+
+    private void handlePowerSaverModeChanged() {
+        mPowerSaverModeOn = mPowerManager.isPowerSaveMode();
+        Blog.v(TAG, "handlePowerSaverModeChanged: %b", mPowerSaverModeOn);
+    }
+
+    private void handleWifiApStateChanged() {
+        mWifiApState = mWifiManager.getWifiApState();
+        Blog.v(TAG, "handleWifiApStateChanged: %d", mWifiApState);
+    }
+
+    private void handleUserRestrictionsChanged() {
+        mWifiConfigRestricted = mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI);
+        Blog.v(TAG, "handleUserRestrictionsChanged: %b", mWifiConfigRestricted);
+    }
+
+    private void handleConfiguredNetworksChanged() {
+        List<WifiConfiguration> wifiConfigurations = mWifiManager.getConfiguredNetworks();
+        if (wifiConfigurations == null) {
+            return;
+        }
+        Blog.v(TAG, "handleConfiguredNetworksChanged: %d", wifiConfigurations.size());
+
+        mSavedNetworkCounts.clear();
+        mSavedNetworkCounts.total = wifiConfigurations.size();
+        mSavedNetworks.clear();
+        mSavedSsids.clear();
+        for (int i = 0; i < wifiConfigurations.size(); i++) {
+            WifiConfiguration wifiConfiguration = wifiConfigurations.get(i);
+            if (wifiConfiguration.status != WifiConfiguration.Status.ENABLED
+                    && wifiConfiguration.status != WifiConfiguration.Status.CURRENT) {
+                continue; // Ignore networks that are not connected or enabled.
+            }
+            mSavedNetworkCounts.enabled++;
+            if (RoboCompatUtil.getInstance().hasNoInternetAccess(wifiConfiguration)) {
+                mSavedNetworkCounts.noInternetAccess++;
+                continue; // Ignore networks that do not have verified internet access.
+            }
+            if (RoboCompatUtil.getInstance().isNoInternetAccessExpected(wifiConfiguration)) {
+                mSavedNetworkCounts.noInternetAccessExpected++;
+                continue; // Ignore networks that are expected not to have internet access.
+            }
+            String ssid = WifiConfigurationUtil.removeDoubleQuotes(wifiConfiguration);
+            if (TextUtils.isEmpty(ssid)) {
+                continue;
+            }
+            if (WideAreaNetworks.contains(ssid)) {
+                mSavedNetworkCounts.blacklisted++;
+                continue; // Ignore wide area networks.
+            }
+            mSavedNetworks.put(ssid, wifiConfiguration);
+            mSavedSsids.add(ssid);
+
+            if (WifiConfigurationUtil.isConfigForOpenNetwork(wifiConfiguration)) {
+                mSavedNetworkCounts.open++;
+            }
+            if (RoboCompatUtil.getInstance().useExternalScores(wifiConfiguration)) {
+                mSavedNetworkCounts.useExternalScores++;
+            }
+        }
+        mSavedSsidsInLastScan.retainAll(mSavedSsids);
+    }
+
+    private void handleWifiStateChanged(boolean calledOnStart) {
+        mWifiState = mWifiManager.getWifiState();
+        Blog.v(TAG, "handleWifiStateChanged: %d", mWifiState);
+
+        switch (mWifiState) {
+            case WifiManager.WIFI_STATE_ENABLED:
+                mSavedSsidsOnDisable.clear();
+                if (!mAutopilotEnabledWifi) {}
+                break;
+            case WifiManager.WIFI_STATE_DISABLED:
+                if (calledOnStart) {
+                    readDisabledSsidsFromSharedPreferences();
+                } else {
+                    for (String ssid : mSavedSsidsInLastScan) {
+                        mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS);
+                    }
+                    writeDisabledSsidsToSharedPreferences();
+                }
+                Blog.d(TAG, "Disabled ssid set: %s", mSavedSsidsOnDisable);
+
+                mAutopilotEnabledWifi = false;
+                break;
+            default: // Only handle ENABLED and DISABLED states
+        }
+    }
+
+    private void readDisabledSsidsFromSharedPreferences() {
+        Set<String> ssidsOnDisable = Preferences.savedSsidsOnDisable.get();
+        for (String ssid : mSavedSsids) {
+            if (ssidsOnDisable.contains(HashUtil.getSsidHash(ssid))) {
+                mSavedSsidsOnDisable.put(ssid, NUM_SCANS_TO_CONFIRM_AP_LOSS);
+            }
+        }
+    }
+
+    private void writeDisabledSsidsToSharedPreferences() {
+        Set<String> ssids = new ArraySet<>();
+        for (String ssid : mSavedSsidsOnDisable.keySet()) {
+            ssids.add(HashUtil.getSsidHash(ssid));
+        }
+        Preferences.savedSsidsOnDisable.put(ssids);
+    }
+
+    private void handleScanResultsAvailable() {
+        if (!mWifiWakeupEnabled || mWifiConfigRestricted) {
+            return;
+        }
+        List<ScanResult> scanResults = mWifiManager.getScanResults();
+        if (scanResults == null) {
+            return;
+        }
+        Blog.v(TAG, "handleScanResultsAvailable: %d", scanResults.size());
+
+        mSavedSsidsInLastScan.clear();
+        for (int i = 0; i < scanResults.size(); i++) {
+            String ssid = scanResults.get(i).SSID;
+            if (mSavedSsids.contains(ssid)) {
+                mSavedSsidsInLastScan.add(ssid);
+            }
+        }
+
+        if (mAirplaneModeEnabled
+                || mWifiState != WifiManager.WIFI_STATE_DISABLED
+                || mWifiApState != WifiManager.WIFI_AP_STATE_DISABLED
+                || mPowerSaverModeOn) {
+            return;
+        }
+
+        // Update mSavedSsidsOnDisable to remove ssids that the user has moved away from.
+        for (Map.Entry<String, Integer> entry : mSavedSsidsOnDisable.entrySet()) {
+            if (mSavedSsidsInLastScan.contains(entry.getKey())) {
+                mSavedSsidsOnDisable.put(entry.getKey(), NUM_SCANS_TO_CONFIRM_AP_LOSS);
+            } else {
+                if (entry.getValue() > 1) {
+                    mSavedSsidsOnDisable.put(entry.getKey(), entry.getValue() - 1);
+                } else {
+                    mSavedSsidsOnDisable.remove(entry.getKey());
+                }
+            }
+        }
+
+        if (!mSavedSsidsOnDisable.isEmpty()) {
+            Blog.d(
+                    TAG,
+                    "Scan results contain ssids from the disabled set: %s",
+                    mSavedSsidsOnDisable);
+            return;
+        }
+
+        if (mSavedSsidsInLastScan.isEmpty()) {
+            Blog.v(TAG, "Scan results do not contain any saved ssids.");
+            return;
+        }
+
+        WifiConfiguration selectedNetwork =
+                mWifiWakeupNetworkSelector.selectNetwork(mSavedNetworks, scanResults);
+        if (selectedNetwork != null) {
+            Blog.d(
+                    TAG,
+                    "Enabling wifi for ssid: %s",
+                    Blog.pii(selectedNetwork.SSID, G.Netrec.enableSensitiveLogging.get()));
+
+            mAutopilotEnabledWifi = true;
+            mWifiManager.setWifiEnabled(true /* enabled */);
+            mWifiWakeupHelper.startWifiSession(selectedNetwork);
+        }
+    }
+
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("mStarted " + mStarted.get());
+        pw.println("mWifiWakeupEnabled: " + mWifiWakeupEnabled);
+        pw.println("mSavedSsids: " + mSavedSsids);
+        pw.println("mSavedSsidsInLastScan: " + mSavedSsidsInLastScan);
+        pw.println("mSavedSsidsOnDisable: " + mSavedSsidsOnDisable);
+    }
+
+    /** Class to track counts for saved networks for logging. */
+    private static class SavedNetworkCounts {
+        int total;
+        int open;
+        int enabled;
+        int noInternetAccess;
+        int noInternetAccessExpected;
+        int useExternalScores;
+        int blacklisted;
+
+        void clear() {
+            total = 0;
+            open = 0;
+            enabled = 0;
+            noInternetAccess = 0;
+            noInternetAccessExpected = 0;
+            useExternalScores = 0;
+            blacklisted = 0;
+        }
+    }
+}
diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java
new file mode 100644
index 0000000..8b4da55
--- /dev/null
+++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupHelper.java
@@ -0,0 +1,231 @@
+/*
+ * 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 com.android.networkrecommendation.wakeup;
+
+import static com.android.networkrecommendation.Constants.TAG;
+import static com.android.networkrecommendation.util.NotificationChannelUtil.CHANNEL_ID_WAKEUP;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Settings;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import com.android.networkrecommendation.R;
+import com.android.networkrecommendation.config.G;
+import com.android.networkrecommendation.config.Preferences;
+import com.android.networkrecommendation.scoring.util.HashUtil;
+import com.android.networkrecommendation.util.Blog;
+import com.android.networkrecommendation.util.NotificationChannelUtil;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class for logging Wi-Fi Wakeup sessions and showing showing notifications for {@link
+ * WifiWakeupController}.
+ */
+public class WifiWakeupHelper {
+    /** Unique ID used for the Wi-Fi Enabled notification. */
+    private static final int NOTIFICATION_ID = R.string.wifi_wakeup_enabled_notification_title;
+
+    @VisibleForTesting
+    static final String ACTION_WIFI_SETTINGS =
+            "com.android.networkrecommendation.wakeup.ACTION_WIFI_SETTINGS";
+
+    @VisibleForTesting
+    static final String ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION =
+            "com.android.networkrecommendation.wakeup.ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION";
+
+    private static final long NETWORK_CONNECTED_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(30);
+    private static final IntentFilter INTENT_FILTER = new IntentFilter();
+
+    static {
+        INTENT_FILTER.addAction(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION);
+        INTENT_FILTER.addAction(ACTION_WIFI_SETTINGS);
+        INTENT_FILTER.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+    }
+
+    private final Context mContext;
+    private final Resources mResources;
+    private final NotificationManager mNotificationManager;
+    private final Handler mHandler;
+    private final WifiManager mWifiManager;
+
+    /** Whether the wakeup notification is currently displayed. */
+    private boolean mNotificationShown;
+    /** True when the device is still connected to the first connected ssid since wakeup. */
+    private boolean mWifiSessionStarted;
+    /** The first connected ssid after wakeup enabled wifi. */
+    private String mConnectedSsid;
+
+    private final BroadcastReceiver mBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    try {
+                        if (ACTION_WIFI_SETTINGS.equals(intent.getAction())) {
+                            mContext.startActivity(
+                                    new Intent(Settings.ACTION_WIFI_SETTINGS)
+                                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+                        } else if (ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION.equals(
+                                intent.getAction())) {
+                            cancelNotificationIfNeeded();
+                        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(
+                                intent.getAction())) {
+                            networkStateChanged();
+                        }
+                    } catch (RuntimeException re) {
+                        // TODO(b/35044022) Remove try/catch after a couple of releases when we are confident
+                        // this is not going to throw.
+                        Blog.e(TAG, re, "RuntimeException in broadcast receiver.");
+                    }
+                }
+            };
+
+    public WifiWakeupHelper(
+            Context context,
+            Resources resources,
+            Handler handler,
+            NotificationManager notificationManager,
+            WifiManager wifiManager) {
+        mContext = context;
+        mResources = resources;
+        mNotificationManager = notificationManager;
+        mHandler = handler;
+        mWifiManager = wifiManager;
+        mWifiSessionStarted = false;
+        mNotificationShown = false;
+        mConnectedSsid = null;
+    }
+
+    /**
+     * Start tracking a wifi wakeup session. Optionally show a notification that Wi-Fi has been
+     * enabled by Wi-Fi Wakeup if one has not been displayed for this {@link WifiConfiguration}.
+     *
+     * @param wifiConfiguration the {@link WifiConfiguration} that triggered Wi-Fi to wakeup
+     */
+    public void startWifiSession(@NonNull WifiConfiguration wifiConfiguration) {
+        mContext.registerReceiver(
+                mBroadcastReceiver, INTENT_FILTER, null /* broadcastPermission*/, mHandler);
+        mWifiSessionStarted = true;
+        mHandler.postDelayed(
+                () -> {
+                    if (mWifiSessionStarted && mConnectedSsid == null) {
+                        endWifiSession();
+                    }
+                },
+                NETWORK_CONNECTED_TIMEOUT_MILLIS);
+
+        Set<String> hashedSsidSet = Preferences.ssidsForWakeupShown.get();
+        String hashedSsid = HashUtil.getSsidHash(wifiConfiguration.SSID);
+        if (hashedSsidSet.isEmpty()) {
+            hashedSsidSet = new ArraySet<>();
+        } else if (hashedSsidSet.contains(hashedSsid)) {
+            Blog.i(
+                    TAG,
+                    "Already showed Wi-Fi Enabled notification for ssid: %s",
+                    Blog.pii(wifiConfiguration.SSID, G.Netrec.enableSensitiveLogging.get()));
+            return;
+        }
+        hashedSsidSet.add(hashedSsid);
+        Preferences.ssidsForWakeupShown.put(hashedSsidSet);
+
+        String title = mResources.getString(R.string.wifi_wakeup_enabled_notification_title);
+        String summary =
+                mResources.getString(
+                        R.string.wifi_wakeup_enabled_notification_context, wifiConfiguration.SSID);
+        PendingIntent savedNetworkSettingsPendingIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        new Intent(ACTION_WIFI_SETTINGS),
+                        PendingIntent.FLAG_UPDATE_CURRENT);
+        PendingIntent deletePendingIntent =
+                PendingIntent.getBroadcast(
+                        mContext,
+                        0,
+                        new Intent(ACTION_DISMISS_WIFI_ENABLED_NOTIFICATION),
+                        PendingIntent.FLAG_UPDATE_CURRENT);
+        Bundle extras = new Bundle();
+        extras.putString(
+                Notification.EXTRA_SUBSTITUTE_APP_NAME,
+                mResources.getString(R.string.notification_channel_group_name));
+        int smallIcon = R.drawable.ic_signal_wifi_statusbar_not_connected;
+        Notification.Builder notificationBuilder =
+                new Notification.Builder(mContext)
+                        .setContentTitle(title)
+                        .setSmallIcon(smallIcon)
+                        .setColor(mContext.getColor(R.color.color_tint))
+                        .setStyle(new Notification.BigTextStyle().bigText(summary))
+                        .setAutoCancel(true)
+                        .setShowWhen(false)
+                        .setDeleteIntent(deletePendingIntent)
+                        .setPriority(Notification.PRIORITY_LOW)
+                        .setVisibility(Notification.VISIBILITY_PUBLIC)
+                        .setCategory(Notification.CATEGORY_STATUS)
+                        .setContentIntent(savedNetworkSettingsPendingIntent)
+                        .setLocalOnly(true)
+                        .addExtras(extras);
+        NotificationChannelUtil.setChannel(notificationBuilder, CHANNEL_ID_WAKEUP);
+        mNotificationManager.notify(TAG, NOTIFICATION_ID, notificationBuilder.build());
+        mNotificationShown = true;
+    }
+
+    private void networkStateChanged() {
+        if (!mWifiManager.isWifiEnabled()) {
+            endWifiSession();
+            return;
+        }
+
+        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+        String ssid = wifiInfo == null ? null : wifiInfo.getSSID();
+        if (mConnectedSsid == null) {
+            if (!TextUtils.isEmpty(ssid)) {
+                mConnectedSsid = ssid;
+            }
+        } else if (!TextUtils.equals(ssid, mConnectedSsid)) {
+            endWifiSession();
+        }
+    }
+
+    private void endWifiSession() {
+        if (mWifiSessionStarted) {
+            mWifiSessionStarted = false;
+            cancelNotificationIfNeeded();
+            mConnectedSsid = null;
+            mContext.unregisterReceiver(mBroadcastReceiver);
+        }
+    }
+
+    private void cancelNotificationIfNeeded() {
+        if (mNotificationShown) {
+            mNotificationShown = false;
+            mNotificationManager.cancel(TAG, NOTIFICATION_ID);
+        }
+    }
+}
diff --git a/src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java b/src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java
new file mode 100644
index 0000000..61ed2cb
--- /dev/null
+++ b/src/com/android/networkrecommendation/wakeup/WifiWakeupNetworkSelector.java
@@ -0,0 +1,139 @@
+/*
+ * 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 com.android.networkrecommendation.wakeup;
+
+import android.content.res.Resources;
+import android.net.RecommendationRequest;
+import android.net.RecommendationResult;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.support.annotation.Nullable;
+import android.util.ArraySet;
+import com.android.networkrecommendation.R;
+import com.android.networkrecommendation.SynchronousNetworkRecommendationProvider;
+import com.android.networkrecommendation.util.RoboCompatUtil;
+import com.android.networkrecommendation.util.ScanResultUtil;
+import com.android.networkrecommendation.util.WifiConfigurationUtil;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/** This class determines which network the framework would connect to if Wi-Fi was enabled. */
+public class WifiWakeupNetworkSelector {
+    private final int mThresholdQualifiedRssi24;
+    private final int mThresholdQualifiedRssi5;
+    private final int mRssiScoreSlope;
+    private final int mRssiScoreOffset;
+    private final int mPasspointSecurityAward;
+    private final int mSecurityAward;
+    private final int mBand5GHzAward;
+    private final int mThresholdSaturatedRssi24;
+    private final SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider;
+
+    public WifiWakeupNetworkSelector(
+            Resources resources,
+            SynchronousNetworkRecommendationProvider networkRecommendationProvider) {
+        mThresholdQualifiedRssi24 =
+                resources.getInteger(R.integer.config_netrec_wifi_score_low_rssi_threshold_24GHz);
+        mThresholdQualifiedRssi5 =
+                resources.getInteger(R.integer.config_netrec_wifi_score_low_rssi_threshold_5GHz);
+        mRssiScoreSlope = resources.getInteger(R.integer.config_netrec_RSSI_SCORE_SLOPE);
+        mRssiScoreOffset = resources.getInteger(R.integer.config_netrec_RSSI_SCORE_OFFSET);
+        mPasspointSecurityAward =
+                resources.getInteger(R.integer.config_netrec_PASSPOINT_SECURITY_AWARD);
+        mSecurityAward = resources.getInteger(R.integer.config_netrec_SECURITY_AWARD);
+        mBand5GHzAward = resources.getInteger(R.integer.config_netrec_5GHz_preference_boost_factor);
+        mThresholdSaturatedRssi24 =
+                resources.getInteger(R.integer.config_netrec_wifi_score_good_rssi_threshold_24GHz);
+        mNetworkRecommendationProvider = networkRecommendationProvider;
+    }
+
+    /** Returns the network that the framework would most likely connect to if Wi-Fi was enabled. */
+    @Nullable
+    public WifiConfiguration selectNetwork(
+            Map<String, WifiConfiguration> savedNetworks, List<ScanResult> scanResults) {
+        Set<WifiConfiguration> openOrExternalConfigs = new ArraySet<>();
+        List<ScanResult> openOrExternalScanResults = new ArrayList<>();
+        WifiConfiguration candidateWifiConfiguration = null;
+        ScanResult candidateScanResult = null;
+        int candidateScore = -1;
+        for (int i = 0; i < scanResults.size(); i++) {
+            ScanResult scanResult = scanResults.get(i);
+            WifiConfiguration wifiConfiguration = savedNetworks.get(scanResult.SSID);
+            if (wifiConfiguration == null) {
+                continue;
+            }
+            if ((ScanResultUtil.is5GHz(scanResult) && scanResult.level < mThresholdQualifiedRssi5)
+                    || (ScanResultUtil.is24GHz(scanResult)
+                            && scanResult.level < mThresholdQualifiedRssi24)) {
+                continue;
+            }
+            if (!ScanResultUtil.doesScanResultMatchWithNetwork(scanResult, wifiConfiguration)) {
+                continue;
+            }
+            if (WifiConfigurationUtil.isConfigForOpenNetwork(wifiConfiguration)
+                    || RoboCompatUtil.getInstance().useExternalScores(wifiConfiguration)) {
+                // All open and externally scored networks should defer to network recommendations.
+                openOrExternalConfigs.add(wifiConfiguration);
+                openOrExternalScanResults.add(scanResult);
+                continue;
+            }
+            int score = calculateScore(scanResult, wifiConfiguration);
+            if (candidateScanResult == null
+                    || calculateScore(scanResult, wifiConfiguration) > candidateScore) {
+                candidateScanResult = scanResult;
+                candidateWifiConfiguration = wifiConfiguration;
+                candidateScore = score;
+            }
+        }
+        if (candidateWifiConfiguration == null && !openOrExternalConfigs.isEmpty()) {
+            // TODO(netrec): Add connectableConfigs after next SystemApi drop
+            RecommendationRequest request =
+                    new RecommendationRequest.Builder()
+                            .setScanResults(openOrExternalScanResults.toArray(new ScanResult[0]))
+                            .build();
+            RecommendationResult result =
+                    mNetworkRecommendationProvider.requestRecommendation(request);
+            return result.getWifiConfiguration();
+        }
+        return candidateWifiConfiguration;
+    }
+
+    private int calculateScore(ScanResult scanResult, WifiConfiguration wifiConfiguration) {
+        int score = 0;
+        // Calculate the RSSI score.
+        int rssi =
+                scanResult.level <= mThresholdSaturatedRssi24
+                        ? scanResult.level
+                        : mThresholdSaturatedRssi24;
+        score += (rssi + mRssiScoreOffset) * mRssiScoreSlope;
+
+        // 5GHz band bonus.
+        if (ScanResultUtil.is5GHz(scanResult)) {
+            score += mBand5GHzAward;
+        }
+
+        // Security award.
+        if (RoboCompatUtil.getInstance().isPasspoint(wifiConfiguration)) {
+            score += mPasspointSecurityAward;
+        } else if (!WifiConfigurationUtil.isConfigForOpenNetwork(wifiConfiguration)) {
+            score += mSecurityAward;
+        }
+
+        return score;
+    }
+}
diff --git a/tests/Android.mk b/tests/Android.mk
deleted file mode 100644
index b5759f3..0000000
--- a/tests/Android.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    guava \
-    jsr305 \
-    mockito-target
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := NetworkRecommendationTests
-LOCAL_CERTIFICATE := platform
-
-LOCAL_INSTRUMENTATION_FOR := NetworkRecommendation
-
-include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
deleted file mode 100644
index 3ebaaf3..0000000
--- a/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.networkrecommendation.tests">
-
-    <uses-permission android:name="android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE"/>
-    <uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES"/>
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.networkrecommendation"
-        android:label="Tests for NetworkRecommendation">
-    </instrumentation>
-</manifest>
diff --git a/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationProviderTest.java b/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationProviderTest.java
deleted file mode 100644
index 5b78310..0000000
--- a/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationProviderTest.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.net.NetworkKey;
-import android.net.NetworkRecommendationProvider;
-import android.net.NetworkScoreManager;
-import android.net.RecommendationRequest;
-import android.net.RecommendationResult;
-import android.net.RssiCurve;
-import android.net.ScoredNetwork;
-import android.net.WifiKey;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiSsid;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-/**
- * Tests the recommendation provider directly, to test internals of the provider rather than the
- * service's API.
- */
-@RunWith(AndroidJUnit4.class)
-public class DefaultNetworkRecommendationProviderTest {
-
-    private static final String GOOD_METERED_NETWORK_STRING_UNQUOTED = "Metered";
-    private static final String GOOD_METERED_NETWORK_STRING = "\"Metered\",aa:bb:cc:dd:ee:ff" +
-            "|10,-128,-128,-128,-128,-128,-128,-128,-128,20,20,20,20,-128|1|0|4K";
-    private static final RssiCurve GOOD_METERED_NETWORK_CURVE = new RssiCurve(
-            DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START, 10 /* bucketWidth */,
-            new byte[]{-128, -128, -128, -128, -128, -128, -128, -128, 20, 20, 20, 20, -128},
-            0 /* defaultActiveNetworkBoost */);
-    private static final ScoredNetwork GOOD_METERED_NETWORK = new ScoredNetwork(
-            new NetworkKey(new WifiKey("\"Metered\"", "aa:bb:cc:dd:ee:ff")),
-            GOOD_METERED_NETWORK_CURVE, true /* meteredHint */, new Bundle());
-
-    private static final String GOOD_CAPTIVE_NETWORK_STRING_UNQUOTED = "Captive";
-    private static final String GOOD_CAPTIVE_NETWORK_STRING =
-            "\"Captive\",ff:ee:dd:cc:bb:aa"
-                    + "|18,-128,-128,-128,-128,-128,-128,21,21,21,-128|0|1|HD";
-    private static final RssiCurve GOOD_CAPTIVE_NETWORK_CURVE = new RssiCurve(
-            DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START, 18 /* bucketWidth */,
-            new byte[]{-128, -128, -128, -128, -128, -128, 21, 21, 21, -128},
-            0 /* defaultActiveNetworkBoost */);
-    private static final ScoredNetwork GOOD_CAPTIVE_NETWORK;
-    static {
-        Bundle attributes = new Bundle();
-        attributes.putBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL, true);
-        GOOD_CAPTIVE_NETWORK = new ScoredNetwork(
-                new NetworkKey(new WifiKey("\"Captive\"", "ff:ee:dd:cc:bb:aa")),
-                GOOD_CAPTIVE_NETWORK_CURVE, false /* meteredHint */, attributes);
-    }
-
-    private static final String ANY_NETWORK_STRING_UNQUOTED = "AnySsid";
-    private static final String ANY_NETWORK_STRING =
-            "\"AnySsid\",00:00:00:00:00:00"
-                    + "|18,-128,-128,-128,-128,-128,-128,22,22,22,-128|0|0|NONE";
-    private static final RssiCurve ANY_NETWORK_CURVE = new RssiCurve(
-            DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START, 18 /* bucketWidth */,
-            new byte[]{-128, -128, -128, -128, -128, -128, 22, 22, 22, -128},
-            0 /* defaultActiveNetworkBoost */);
-    private static final ScoredNetwork ANY_NETWORK = new ScoredNetwork(
-            new NetworkKey(new WifiKey("\"AnySsid\"", "ee:ee:ee:ee:ee:ee")),
-            ANY_NETWORK_CURVE, false /* meteredHint */, new Bundle());
-
-    private static final String ANY_NETWORK_SPECIFIC_STRING_UNQUOTED = "AnySsid";
-    private static final String ANY_NETWORK_SPECIFIC_STRING =
-            "\"AnySsid\",ee:ee:ee:ee:ee:ee"
-                    + "|18,-128,-128,-128,-128,-128,-128,23,23,23,-128|0|0|NONE";
-    private static final RssiCurve ANY_NETWORK_SPECIFIC_CURVE = new RssiCurve(
-            DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START, 18 /* bucketWidth */,
-            new byte[]{-128, -128, -128, -128, -128, -128, 23, 23, 23, -128},
-            0 /* defaultActiveNetworkBoost */);
-    private static final ScoredNetwork ANY_NETWORK_SPECIFIC = new ScoredNetwork(
-            new NetworkKey(new WifiKey("\"AnySsid\"", "ee:ee:ee:ee:ee:ee")),
-            ANY_NETWORK_SPECIFIC_CURVE, false /* meteredHint */, new Bundle());
-
-    private static final String BAD_NETWORK_STRING_UNQUOTED = "Bad";
-    private static final String BAD_NETWORK_STRING =
-            "\"Bad\",aa:bb:cc:dd:ee:ff"
-                    + "|10,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128,-128"
-                    + "|1|0|SD";
-    private static final RssiCurve BAD_NETWORK_CURVE =
-            new RssiCurve(
-                    DefaultNetworkRecommendationProvider.CONSTANT_CURVE_START,
-                    10 /* bucketWidth */,
-                    new byte[] {-128, -128, -128, -128, -128, -128,
-                            -128, -128, -128, -128, -128, -128, -128},
-                    0 /* defaultActiveNetworkBoost */);
-    private static final ScoredNetwork BAD_NETWORK =
-            new ScoredNetwork(
-                    new NetworkKey(new WifiKey("\"Bad\"", "aa:bb:cc:dd:ee:ff")),
-                    BAD_NETWORK_CURVE,
-                    true /* meteredHint */,
-                    new Bundle());
-
-    @Mock
-    private NetworkRecommendationProvider.ResultCallback mCallback;
-
-    @Mock
-    private NetworkScoreManager mNetworkScoreManager;
-
-    private DefaultNetworkRecommendationProvider.ScoreStorage mStorage;
-    private DefaultNetworkRecommendationProvider mProvider;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mStorage = new DefaultNetworkRecommendationProvider.ScoreStorage();
-        mProvider = new DefaultNetworkRecommendationProvider(
-                new Handler(Looper.getMainLooper()), mNetworkScoreManager, mStorage);
-    }
-
-    @Test
-    public void basicRecommendation() throws Exception {
-
-        ScanResult[] scanResults = new ScanResult[6];
-        for (int i = 0; i < 3; i++) {
-            scanResults[i] = TestUtil.createMockScanResult(i);
-        }
-
-        // For now we add directly to storage, but when we start calling
-        // networkScoreManager.updateScores, we'll have to adjust this test.
-        mProvider.addScoreForTest(GOOD_METERED_NETWORK);
-        {
-            ScanResult scanResult = new ScanResult();
-            scanResult.level = 115;
-            scanResult.SSID = GOOD_METERED_NETWORK_STRING_UNQUOTED;
-            scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded(
-                    GOOD_METERED_NETWORK_STRING_UNQUOTED);
-            scanResult.BSSID = GOOD_METERED_NETWORK.networkKey.wifiKey.bssid;
-            scanResult.capabilities = "[ESS]";
-            scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
-            scanResults[3] = scanResult;
-        }
-
-        for (int i = 4; i < 6; i++) {
-            scanResults[i] = TestUtil.createMockScanResult(i);
-        }
-
-        RecommendationRequest request = new RecommendationRequest.Builder()
-                .setScanResults(scanResults)
-                .build();
-
-        RecommendationResult result = verifyAndCaptureResult(request);
-        assertEquals(GOOD_METERED_NETWORK.networkKey.wifiKey.ssid,
-                result.getWifiConfiguration().SSID);
-    }
-
-    @Test
-    public void recommendation_noScans_returnsCurrentConfig() throws Exception {
-        ScanResult[] scanResults = new ScanResult[0];
-
-        WifiConfiguration expectedConfig = new WifiConfiguration();
-        expectedConfig.SSID = "ssid";
-        expectedConfig.BSSID = "bssid";
-        RecommendationRequest request = new RecommendationRequest.Builder()
-                .setScanResults(scanResults)
-                .setDefaultWifiConfig(expectedConfig)
-                .build();
-
-        RecommendationResult result = verifyAndCaptureResult(request);
-        assertEquals(expectedConfig, result.getWifiConfiguration());
-    }
-
-    @Test
-    public void recommendation_noScans_noCurrentConfig_returnsEmpty() throws Exception {
-        ScanResult[] scanResults = new ScanResult[0];
-
-        RecommendationRequest request = new RecommendationRequest.Builder()
-                .setScanResults(scanResults)
-                .build();
-
-        RecommendationResult result = verifyAndCaptureResult(request);
-        assertNull(result.getWifiConfiguration());
-    }
-
-    @Test
-    public void scoreNetworks() throws Exception {
-        NetworkKey[] keys =
-                new NetworkKey[]{GOOD_METERED_NETWORK.networkKey, GOOD_CAPTIVE_NETWORK.networkKey};
-        mProvider.onRequestScores(keys);
-
-        verify(mNetworkScoreManager).updateScores(Mockito.any());
-    }
-
-    @Test
-    public void scoreNetworks_empty() throws Exception {
-        NetworkKey[] keys = new NetworkKey[]{};
-        mProvider.onRequestScores(keys);
-
-        verify(mNetworkScoreManager, times(0)).updateScores(Mockito.any());
-    }
-
-    @Test
-    public void dumpAddScores_goodMetered() {
-        String[] args = {"netrec", "addScore", GOOD_METERED_NETWORK_STRING};
-        mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()), args);
-
-        ScoredNetwork[] scoredNetworks = verifyAndCaptureScoredNetworks();
-        assertEquals(1, scoredNetworks.length);
-        ScoredNetwork score = scoredNetworks[0];
-
-        assertEquals(GOOD_METERED_NETWORK.networkKey.wifiKey.ssid, score.networkKey.wifiKey.ssid);
-        assertEquals(GOOD_METERED_NETWORK.networkKey.wifiKey.bssid, score.networkKey.wifiKey.bssid);
-
-        assertEquals(GOOD_METERED_NETWORK.meteredHint, score.meteredHint);
-        assertEquals(
-                GOOD_METERED_NETWORK.attributes.getBoolean(
-                        ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
-                score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
-
-        assertEquals("Network curve does not match", GOOD_METERED_NETWORK_CURVE, score.rssiCurve);
-        assertEquals(
-                "Badge curve does not match",
-                DefaultNetworkRecommendationProvider.BADGE_CURVE_4K,
-                (RssiCurve) score.attributes.getParcelable(
-                        ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
-    }
-
-    @Test
-    public void dumpAddScores_bad() {
-        String[] args = {"netrec", "addScore", BAD_NETWORK_STRING};
-        mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()), args);
-
-        ScoredNetwork[] scoredNetworks = verifyAndCaptureScoredNetworks();
-        assertEquals(1, scoredNetworks.length);
-        ScoredNetwork score = scoredNetworks[0];
-
-        assertEquals(BAD_NETWORK.networkKey.wifiKey.ssid, score.networkKey.wifiKey.ssid);
-        assertEquals(BAD_NETWORK.networkKey.wifiKey.bssid, score.networkKey.wifiKey.bssid);
-
-        assertEquals(BAD_NETWORK.meteredHint, score.meteredHint);
-        assertEquals(
-                BAD_NETWORK.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
-                score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
-
-        assertEquals("Network curve does not match", BAD_NETWORK_CURVE, score.rssiCurve);
-        assertEquals(
-                "Badge curve does not match",
-                DefaultNetworkRecommendationProvider.BADGE_CURVE_SD,
-                (RssiCurve) score.attributes.getParcelable(
-                        ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
-    }
-
-    @Test
-    public void dumpAddScores_goodCaptivePortal() {
-        String[] args = {"addScore", GOOD_CAPTIVE_NETWORK_STRING};
-        mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()), args);
-
-        ScoredNetwork[] scoredNetworks = verifyAndCaptureScoredNetworks();
-        assertEquals(1, scoredNetworks.length);
-        ScoredNetwork score = scoredNetworks[0];
-
-        assertEquals(GOOD_CAPTIVE_NETWORK.networkKey.wifiKey.ssid, score.networkKey.wifiKey.ssid);
-        assertEquals(GOOD_CAPTIVE_NETWORK.networkKey.wifiKey.bssid, score.networkKey.wifiKey.bssid);
-
-        assertEquals(GOOD_CAPTIVE_NETWORK.meteredHint, score.meteredHint);
-
-        assertEquals(
-                GOOD_CAPTIVE_NETWORK.attributes.getBoolean(
-                        ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
-                score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
-        assertEquals("Network curve does not match.", GOOD_CAPTIVE_NETWORK_CURVE, score.rssiCurve);
-        assertEquals(
-                "Badge curve does not match",
-                DefaultNetworkRecommendationProvider.BADGE_CURVE_HD,
-                (RssiCurve) score.attributes.getParcelable(
-                        ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
-    }
-
-    @Test
-    public void dumpAddScores_anySsid() {
-        String[] args = {"addScore", ANY_NETWORK_STRING};
-        mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()), args);
-
-        // We don't update the platform with the any bssid score, but we do store it.
-        verify(mNetworkScoreManager, times(0)).updateScores(Mockito.any());
-
-        // We do store and serve the score, though:
-        ScoredNetwork score = mStorage.get(ANY_NETWORK.networkKey);
-        assertNotNull(score);
-
-        assertEquals(ANY_NETWORK.networkKey, score.networkKey);
-        assertEquals(ANY_NETWORK.meteredHint, score.meteredHint);
-        assertEquals(
-                ANY_NETWORK.attributes.getBoolean(
-                    ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
-                score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
-        assertEquals("Network curve does not match", ANY_NETWORK_CURVE, score.rssiCurve);
-        assertNull(
-                "Badge curve should not be set.",
-                (RssiCurve) score.attributes.getParcelable(
-                        ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
-    }
-
-    @Test
-    public void dumpAddScores_anySsid_useMoreSpecific() {
-        mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()),
-                new String[] {"addScore", ANY_NETWORK_STRING});
-        verify(mNetworkScoreManager, times(0)).updateScores(Mockito.any());
-
-        mProvider.dump(null /* fd */, new PrintWriter(new StringWriter()),
-                new String[] {"addScore", ANY_NETWORK_SPECIFIC_STRING});
-        verify(mNetworkScoreManager).updateScores(Mockito.any());
-
-        // We don't update the platform with the any bssid score, but we do store it.
-        ScoredNetwork score = mStorage.get(ANY_NETWORK.networkKey);
-        assertNotNull(score);
-
-        assertEquals(ANY_NETWORK_SPECIFIC.networkKey, score.networkKey);
-        assertEquals(ANY_NETWORK_SPECIFIC.meteredHint, score.meteredHint);
-        assertEquals(
-                ANY_NETWORK_SPECIFIC.attributes.getBoolean(
-                    ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL),
-                score.attributes.getBoolean(ScoredNetwork.ATTRIBUTES_KEY_HAS_CAPTIVE_PORTAL));
-        assertEquals("Network curve does not match", ANY_NETWORK_SPECIFIC_CURVE, score.rssiCurve);
-        assertNull(
-                "Badge curve should not be set.",
-                (RssiCurve) score.attributes.getParcelable(
-                        ScoredNetwork.ATTRIBUTES_KEY_BADGING_CURVE));
-
-    }
-
-    private RecommendationResult verifyAndCaptureResult(
-            RecommendationRequest request) {
-        mProvider.onRequestRecommendation(request, mCallback);
-
-        ArgumentCaptor<RecommendationResult> resultCaptor =
-                ArgumentCaptor.forClass(RecommendationResult.class);
-        verify(mCallback).onResult(resultCaptor.capture());
-
-        return resultCaptor.getValue();
-    }
-
-    private ScoredNetwork[] verifyAndCaptureScoredNetworks() {
-        ArgumentCaptor<ScoredNetwork[]> resultCaptor = ArgumentCaptor.forClass(
-                ScoredNetwork[].class);
-        verify(mNetworkScoreManager).updateScores(resultCaptor.capture());
-        return resultCaptor.getValue();
-    }
-}
diff --git a/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationServiceTest.java b/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationServiceTest.java
deleted file mode 100644
index 75aff2b..0000000
--- a/tests/src/com/android/networkrecommendation/DefaultNetworkRecommendationServiceTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import android.content.Intent;
-import android.net.INetworkRecommendationProvider;
-import android.net.NetworkKey;
-import android.net.NetworkRecommendationProvider;
-import android.net.NetworkScoreManager;
-import android.net.RecommendationRequest;
-import android.net.RecommendationResult;
-import android.net.WifiKey;
-import android.net.wifi.ScanResult;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ServiceTestRule;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-@RunWith(AndroidJUnit4.class)
-public class DefaultNetworkRecommendationServiceTest {
-
-    private static final int RESULT_LATCH_TIMEOUT_MILLIS = 2000;
-    private static final int WAIT_FOR_BIND_TIMEOUT_MILLIS = 2000;
-    private static final int SEQUENCE_ID = 11;
-
-    @Rule
-    public final ServiceTestRule mServiceRule = new ServiceTestRule();
-
-    private INetworkRecommendationProvider bind() throws TimeoutException {
-        Intent bindIntent = new Intent(InstrumentationRegistry.getTargetContext(),
-                DefaultNetworkRecommendationService.class);
-        bindIntent.setAction(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
-
-        // https://code.google.com/p/android/issues/detail?id=200071
-        // bindService can occasionally returns null.
-        IBinder binder = null;
-        boolean interrupted = false;
-        try {
-            long startTime = SystemClock.elapsedRealtime();
-            long currentTime = startTime;
-            while (currentTime < startTime + WAIT_FOR_BIND_TIMEOUT_MILLIS) {
-                binder = mServiceRule.bindService(bindIntent);
-                if (binder != null) {
-                    return INetworkRecommendationProvider.Stub.asInterface(binder);
-                }
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                    interrupted = true;
-                    currentTime = SystemClock.elapsedRealtime();
-                }
-            }
-            throw new TimeoutException("Unable to bind to service.");
-        } finally {
-            if (interrupted) {
-                Thread.currentThread().interrupt();
-            }
-        }
-    }
-
-    /**
-     * Assert that when we make a request we get a response with the proper sequence.
-     * <p />
-     * We do not assert the config given to us, because we're simply verifying the behavior of
-     * this service interacting with the provider. For recommendation tests, see
-     * {@link DefaultNetworkRecommendationProviderTest}.
-     */
-    @Test
-    public void requestRecommendation() throws Exception {
-        INetworkRecommendationProvider service = bind();
-
-        ScanResult[] scanResults = new ScanResult[5];
-        for (int i = 0; i < 5; i++) {
-            scanResults[i] = TestUtil.createMockScanResult(i);
-        }
-
-        RecommendationRequest request = new RecommendationRequest.Builder()
-                .setScanResults(scanResults)
-                .build();
-
-        Result result = requestRecommendation(service, request, SEQUENCE_ID);
-        synchronized (result) {
-            assertEquals(result.sequence, SEQUENCE_ID);
-        }
-    }
-
-    @Test
-    public void scoreNetworks() throws Exception {
-        INetworkRecommendationProvider service = bind();
-        service.requestScores(new NetworkKey[]{new NetworkKey(new WifiKey("\"ProperlyQuoted\"",
-                "aa:bb:cc:dd:ee:ff"))});
-    }
-
-    @Test
-    public void scoreNetworks_empty() throws Exception {
-        INetworkRecommendationProvider service = bind();
-        service.requestScores(new NetworkKey[]{});
-    }
-
-    @Test
-    public void scoreNetworks_invalid() throws Exception {
-        INetworkRecommendationProvider service = bind();
-        try {
-            service.requestScores(new NetworkKey[]{
-                    new NetworkKey(new WifiKey("ImproperlyQuoted", "aa:bb:cc:dd:ee:ff"))});
-            fail("An invalid SSID should throw an exception.");
-        } catch (IllegalArgumentException e) {
-            // Expected.
-        }
-    }
-
-    /**
-     * Make a network recommendation request. Be sure to synchronize on the result to access its
-     * values properly.
-     */
-    private Result requestRecommendation(INetworkRecommendationProvider service,
-            RecommendationRequest request, int seqId) throws RemoteException, InterruptedException {
-        final Result result = new Result();
-        final CountDownLatch latch = new CountDownLatch(1);
-        IRemoteCallback callback = new IRemoteCallback.Stub() {
-            @Override
-            public void sendResult(Bundle data) throws RemoteException {
-                synchronized (result) {
-                    result.sequence = data.getInt(NetworkRecommendationProvider.EXTRA_SEQUENCE);
-                    result.recommendation = data.getParcelable(
-                            NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT);
-                    latch.countDown();
-                }
-            }
-        };
-
-        service.requestRecommendation(request, callback, seqId);
-        latch.await(RESULT_LATCH_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        return result;
-    }
-
-    private static class Result {
-        int sequence;
-        RecommendationResult recommendation;
-    }
-}
diff --git a/tests/src/com/android/networkrecommendation/TestUtil.java b/tests/src/com/android/networkrecommendation/TestUtil.java
deleted file mode 100644
index 77d7aa7..0000000
--- a/tests/src/com/android/networkrecommendation/TestUtil.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.net.NetworkInfo;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
-import android.os.SystemClock;
-
-/**
- * Utils for wifi tests.
- */
-public class TestUtil {
-
-    /** Create a scan result with some basic properties. */
-    public static ScanResult createMockScanResult(int i) {
-        ScanResult scanResult = new ScanResult();
-        scanResult.level = i;
-        scanResult.SSID = "ssid-" + i;
-        scanResult.wifiSsid = WifiSsid.createFromAsciiEncoded("ssid-" + i);
-        scanResult.BSSID = "aa:bb:cc:dd:ee:0" + i;
-        scanResult.capabilities = "[ESS]";
-        scanResult.timestamp = SystemClock.elapsedRealtime() * 1000;
-        return scanResult;
-    }
-
-    /** Send {@link WifiManager#NETWORK_STATE_CHANGED_ACTION} broadcast. */
-    public static void sendNetworkStateChanged(BroadcastReceiver broadcastReceiver,
-            Context context, NetworkInfo.DetailedState detailedState) {
-        Intent intent = new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION);
-        NetworkInfo networkInfo = new NetworkInfo(0, 0, "", "");
-        networkInfo.setDetailedState(detailedState, "", "");
-        intent.putExtra(WifiManager.EXTRA_NETWORK_INFO, networkInfo);
-        broadcastReceiver.onReceive(context, intent);
-    }
-
-    /** Send {@link WifiManager#WIFI_AP_STATE_CHANGED_ACTION} broadcast. */
-    public static void sendWifiApStateChanged(BroadcastReceiver broadcastReceiver,
-            Context context) {
-        Intent intent = new Intent(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
-        broadcastReceiver.onReceive(context, intent);
-    }
-
-    /** Send {@link WifiManager#SCAN_RESULTS_AVAILABLE_ACTION} broadcast. */
-    public static void sendScanResultsAvailable(BroadcastReceiver broadcastReceiver,
-            Context context) {
-        Intent intent = new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
-        broadcastReceiver.onReceive(context, intent);
-    }
-
-    /** Send {@link WifiManager#CONFIGURED_NETWORKS_CHANGED_ACTION} broadcast. */
-    public static void sendConfiguredNetworksChanged(BroadcastReceiver broadcastReceiver,
-            Context context) {
-        Intent intent = new Intent(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
-        broadcastReceiver.onReceive(context, intent);
-    }
-
-    /** Send {@link WifiManager#WIFI_STATE_CHANGED_ACTION} broadcast. */
-    public static void sendWifiStateChanged(BroadcastReceiver broadcastReceiver, Context context) {
-        Intent intent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        broadcastReceiver.onReceive(context, intent);
-    }
-}
diff --git a/tests/src/com/android/networkrecommendation/WifiNotificationControllerTest.java b/tests/src/com/android/networkrecommendation/WifiNotificationControllerTest.java
deleted file mode 100644
index d902e68..0000000
--- a/tests/src/com/android/networkrecommendation/WifiNotificationControllerTest.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.graphics.Bitmap;
-import android.net.NetworkInfo;
-import android.net.RecommendationRequest;
-import android.net.RecommendationResult;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.ActionListener;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Unit tests for {@link com.android.networkrecommendation.WifiNotificationController}.
- */
-@RunWith(AndroidJUnit4.class)
-public class WifiNotificationControllerTest {
-    @Mock private Context mContext;
-    @Mock private WifiManager mWifiManager;
-    @Mock private NotificationManager mNotificationManager;
-    @Mock private WifiNotificationHelper mWifiNotificationHelper;
-    @Mock private SynchronousNetworkRecommendationProvider mNetworkRecommendationProvider;
-    private ContentResolver mContentResolver;
-    private Handler mHandler;
-    private WifiNotificationController mWifiNotificationController;
-    private int mNotificationsEnabledOriginalValue;
-
-    /**
-     * Internal BroadcastReceiver that WifiNotificationController uses to listen for broadcasts
-     * this is initialized by calling startServiceAndLoadDriver
-     */
-    private BroadcastReceiver mBroadcastReceiver;
-
-    /** Initialize objects before each test run. */
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        // Needed for the NotificationEnabledSettingObserver.
-        mContentResolver = InstrumentationRegistry.getTargetContext().getContentResolver();
-        mNotificationsEnabledOriginalValue =
-                Settings.Global.getInt(mContentResolver,
-                        Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 0);
-        Settings.Global.putInt(mContentResolver,
-                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1);
-        mHandler = new Handler(Looper.getMainLooper());
-
-        mWifiNotificationController = new WifiNotificationController(
-                mContext, mContentResolver, mHandler, mNetworkRecommendationProvider,
-                mWifiManager, mNotificationManager, mWifiNotificationHelper);
-        mWifiNotificationController.start();
-
-        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
-                ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mContext)
-                .registerReceiver(broadcastReceiverCaptor.capture(), any(IntentFilter.class),
-                        anyString(), any(Handler.class));
-        mBroadcastReceiver = broadcastReceiverCaptor.getValue();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        Settings.Global.putInt(mContentResolver,
-                Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
-                mNotificationsEnabledOriginalValue);
-    }
-
-    private void setOpenAccessPoints(int numAccessPoints) {
-        List<ScanResult> scanResults = new ArrayList<>();
-        for (int i = 0; i < numAccessPoints; i++) {
-            ScanResult scanResult = createScanResult("testSSID" + i, "00:00:00:00:00:00");
-            scanResults.add(scanResult);
-        }
-        when(mWifiManager.getScanResults()).thenReturn(scanResults);
-    }
-
-    private ScanResult createScanResult(String ssid, String bssid) {
-        ScanResult scanResult = new ScanResult();
-        scanResult.capabilities = "[ESS]";
-        scanResult.SSID = ssid;
-        scanResult.BSSID = bssid;
-        return scanResult;
-    }
-
-    private WifiConfiguration createFakeConfig() {
-        WifiConfiguration config = new WifiConfiguration();
-        config.SSID = "\"TestSsid\"";
-        config.BSSID = "00:00:00:00:00:00";
-        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        return config;
-    }
-
-    private void createFakeBitmap() {
-        when(mWifiNotificationHelper.createNotificationBadgeBitmap(any(), any()))
-                .thenReturn(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888));
-    }
-
-    /** Verifies that a notification is displayed (and retracted) given system events. */
-    @Test
-    public void verifyNotificationDisplayedWhenNetworkRecommended() throws Exception {
-        when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
-
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.DISCONNECTED);
-        setOpenAccessPoints(3);
-        createFakeBitmap();
-
-        when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class)))
-                .thenReturn(
-                        RecommendationResult.createConnectRecommendation(createFakeConfig()));
-
-        // The notification should not be displayed after only two scan results.
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        verify(mNotificationManager, never())
-                .notify(any(String.class), anyInt(), any(Notification.class));
-
-        // Changing to and from "SCANNING" state should not affect the counter.
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.SCANNING);
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.DISCONNECTED);
-        verify(mNotificationManager, never())
-                .notify(any(String.class), anyInt(), any(Notification.class));
-        // The third scan result notification will trigger the notification.
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class),
-                any(Bitmap.class));
-        verify(mNotificationManager)
-                .notify(any(String.class), anyInt(), any(Notification.class));
-        verify(mNotificationManager, never())
-                .cancelAsUser(any(String.class), anyInt(), any(UserHandle.class));
-    }
-
-    /** Verifies that a notification is not displayed for bad networks. */
-    @Test
-    public void verifyNotificationNotDisplayedWhenNoNetworkRecommended() throws Exception {
-        when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
-
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.DISCONNECTED);
-        setOpenAccessPoints(3);
-        createFakeBitmap();
-
-        // Recommendation result with no WifiConfiguration returned.
-        when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class)))
-                .thenReturn(RecommendationResult.createDoNotConnectRecommendation());
-
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        verify(mNotificationManager, never())
-                .notify(any(String.class), anyInt(), any(Notification.class));
-
-        // DoNotConnect Recommendation result.
-        when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class)))
-                .thenReturn(RecommendationResult.createDoNotConnectRecommendation());
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        verify(mNotificationManager, never())
-                .notify(any(String.class), anyInt(), any(Notification.class));
-    }
-
-    /**
-     * Verifies the notifications flow (Connect -> connecting -> connected) when user clicks
-     * on Connect button.
-     */
-    @Test
-    public void verifyNotificationsFlowOnConnectToNetwork() {
-        when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
-
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.DISCONNECTED);
-        setOpenAccessPoints(3);
-        createFakeBitmap();
-        when(mNetworkRecommendationProvider.requestRecommendation(any(RecommendationRequest.class)))
-                .thenReturn(
-                        RecommendationResult.createConnectRecommendation(createFakeConfig()));
-
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        verify(mWifiNotificationHelper).createMainNotification(any(WifiConfiguration.class),
-                any(Bitmap.class));
-        verify(mNotificationManager)
-                .notify(any(String.class), anyInt(), any(Notification.class));
-
-        // Send connect intent, should attempt to connect to Wi-Fi
-        Intent intent = new Intent(
-                WifiNotificationController.ACTION_CONNECT_TO_RECOMMENDED_NETWORK);
-        mBroadcastReceiver.onReceive(mContext, intent);
-        verify(mWifiManager).connect(any(WifiConfiguration.class), any(ActionListener.class));
-        verify(mWifiNotificationHelper).createConnectingNotification(any(WifiConfiguration.class),
-                any(Bitmap.class));
-
-        // Show connecting notification.
-        verify(mNotificationManager, times(2))
-                .notify(any(String.class), anyInt(), any(Notification.class));
-        // Verify callback to dismiss connecting notification exists.
-        assertTrue(mHandler.hasCallbacks(
-                mWifiNotificationController.mShowFailedToConnectNotificationRunnable));
-
-        // Verify show connected notification.
-        TestUtil.sendNetworkStateChanged(mBroadcastReceiver, mContext,
-                NetworkInfo.DetailedState.CONNECTED);
-        verify(mWifiNotificationHelper).createConnectedNotification(any(WifiConfiguration.class),
-                any(Bitmap.class));
-        verify(mNotificationManager, times(3))
-                .notify(any(String.class), anyInt(), any(Notification.class));
-
-        // Verify callback to dismiss connected notification exists.
-        assertTrue(mHandler.hasCallbacks(
-                mWifiNotificationController.mDismissNotificationRunnable));
-    }
-}
diff --git a/tests/src/com/android/networkrecommendation/WifiWakeupControllerTest.java b/tests/src/com/android/networkrecommendation/WifiWakeupControllerTest.java
deleted file mode 100644
index 3869a71..0000000
--- a/tests/src/com/android/networkrecommendation/WifiWakeupControllerTest.java
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyList;
-import static org.mockito.Matchers.anyMap;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.IntentFilter;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
-import android.support.test.runner.AndroidJUnit4;
-
-import com.google.android.collect.Lists;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-/**
- * Unit tests for {@link WifiWakeupController}.
- */
-@RunWith(AndroidJUnit4.class)
-public class WifiWakeupControllerTest {
-    private static final ScanResult OPEN_SCAN_RESULT = buildScanResult("ssid");
-    private static final ScanResult SAVED_SCAN_RESULT = buildScanResult("ssid1");
-    private static final ScanResult SAVED_SCAN_RESULT2 = buildScanResult("ssid2");
-    private static final ScanResult SAVED_SCAN_RESULT_EXTERNAL = buildScanResult("ssid3");
-
-    private static final WifiConfiguration SAVED_WIFI_CONFIGURATION = new WifiConfiguration();
-    private static final WifiConfiguration SAVED_WIFI_CONFIGURATION2 = new WifiConfiguration();
-    private static final WifiConfiguration SAVED_WIFI_CONFIGURATION_EXTERNAL =
-            new WifiConfiguration();
-
-    static {
-        SAVED_WIFI_CONFIGURATION.SSID = "\"" + SAVED_SCAN_RESULT.SSID + "\"";
-        SAVED_WIFI_CONFIGURATION.status = WifiConfiguration.Status.CURRENT;
-        SAVED_WIFI_CONFIGURATION.validatedInternetAccess = true;
-        SAVED_WIFI_CONFIGURATION2.SSID = "\"" + SAVED_SCAN_RESULT.SSID + "\"";
-        SAVED_WIFI_CONFIGURATION2.status = WifiConfiguration.Status.ENABLED;
-        SAVED_WIFI_CONFIGURATION2.validatedInternetAccess = true;
-        SAVED_WIFI_CONFIGURATION_EXTERNAL.SSID = "\"" + SAVED_SCAN_RESULT_EXTERNAL.SSID + "\"";
-        SAVED_WIFI_CONFIGURATION_EXTERNAL.useExternalScores = true;
-        SAVED_WIFI_CONFIGURATION_EXTERNAL.status = WifiConfiguration.Status.ENABLED;
-        SAVED_WIFI_CONFIGURATION_EXTERNAL.validatedInternetAccess = true;
-    }
-
-    private static ScanResult buildScanResult(String ssid) {
-        ScanResult scanResult = new ScanResult(WifiSsid.createFromAsciiEncoded(ssid),
-                "00:00:00:00:00:00", 0L, -1, null, "", 0, 0, 0);
-        scanResult.informationElements = new ScanResult.InformationElement[0];
-        return scanResult;
-    }
-
-    @Mock private Context mContext;
-    @Mock private NotificationManager mNotificationManager;
-    @Mock private ContentResolver mContentResolver;
-    @Mock private WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
-    @Mock private WifiWakeupNotificationHelper mWifiWakeupNotificationHelper;
-    @Mock private WifiManager mWifiManager;
-    @Captor private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
-
-    private WifiWakeupController mWifiWakeupController;
-    private BroadcastReceiver mBroadcastReceiver;
-
-    private int mWifiWakeupEnabledOriginalValue;
-    private int mAirplaneModeOriginalValue;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mWifiWakeupEnabledOriginalValue =
-                Settings.Global.getInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED, 0);
-        mAirplaneModeOriginalValue =
-                Settings.Global.getInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON, 0);
-        Settings.Global.putInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED, 1);
-        Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON, 0);
-        when(mWifiManager.getWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_DISABLED);
-
-        mWifiWakeupController = new WifiWakeupController(mContext, mContentResolver,
-                Looper.getMainLooper(), mWifiManager, mWifiWakeupNetworkSelector,
-                mWifiWakeupNotificationHelper);
-        mWifiWakeupController.start();
-
-        verify(mContext).registerReceiver(mBroadcastReceiverCaptor.capture(),
-                any(IntentFilter.class), anyString(), any(Handler.class));
-        mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
-    }
-
-    @After
-    public void tearDown() {
-        Settings.Global.putInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED,
-                mWifiWakeupEnabledOriginalValue);
-        Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON,
-                mAirplaneModeOriginalValue);
-    }
-
-    /**
-     * When the NetworkRecommendationService associated with this WifiWakeupController is unbound,
-     * this WifiWakeupController should no longer function.
-     */
-    @Test
-    public void wifiWakeupControllerStopped() {
-        mWifiWakeupController.stop();
-
-        verify(mContext).unregisterReceiver(mBroadcastReceiver);
-    }
-
-    /**
-     * When Wi-Fi is disabled and a saved network is in the scan list, and then this network is not
-     * in the scan list 3x, and then it is, Wi-Fi should be enabled.
-     */
-    @Test
-    public void wifiEnabled_userDisabledWifiNearSavedNetwork_thenLeaves_thenMovesBack() {
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION));
-        when(mWifiManager.getScanResults()).thenReturn(
-                Lists.newArrayList(SAVED_SCAN_RESULT),
-                Lists.newArrayList(OPEN_SCAN_RESULT),
-                Lists.newArrayList(OPEN_SCAN_RESULT),
-                Lists.newArrayList(OPEN_SCAN_RESULT),
-                Lists.newArrayList(SAVED_SCAN_RESULT));
-        when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList()))
-                .thenReturn(null, SAVED_WIFI_CONFIGURATION);
-        when(mWifiManager.getWifiState())
-                .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED);
-
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verify(mWifiManager, never()).setWifiEnabled(true);
-
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verify(mWifiManager).setWifiEnabled(true);
-        verify(mWifiWakeupNotificationHelper)
-                .maybeShowWifiEnabledNotification(SAVED_WIFI_CONFIGURATION);
-    }
-
-    /**
-     * When Wi-Fi is disabled and a saved network is in the scan list, and then another scan result
-     * comes in 3x with only a different saved network, Wi-Fi should be enabled.
-     */
-    @Test
-    public void wifiEnabled_userDisabledWifiNearSavedNetwork_thenMovesToAnotherSavedNetwork() {
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION2));
-        when(mWifiManager.getScanResults()).thenReturn(
-                Lists.newArrayList(SAVED_SCAN_RESULT),
-                Lists.newArrayList(SAVED_SCAN_RESULT2));
-        when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList()))
-                .thenReturn(SAVED_WIFI_CONFIGURATION2);
-        when(mWifiManager.getWifiState())
-                .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED);
-
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-
-        verify(mWifiManager, never()).setWifiEnabled(true);
-
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verify(mWifiManager).setWifiEnabled(true);
-        verify(mWifiWakeupNotificationHelper)
-                .maybeShowWifiEnabledNotification(SAVED_WIFI_CONFIGURATION2);
-    }
-
-    /**
-     * When a user disables Wi-Fi when there is a saved network marked with
-     * {@link WifiConfiguration#useExternalScores} in the scan list, Wi-Fi should not be enabled if
-     * the {@link WifiWakeupNetworkSelector} does not return an ssid.
-     *
-     * When {@link WifiWakeupNetworkSelector} does return an ssid, Wi-Fi should
-     * be enabled (in absence of a scan list without the saved network) because networks marked
-     * with external scores are not tracked by {@link WifiWakeupController}.
-     */
-    @Test
-    public void wifiEnabled_userDisablesWifiNearExternallyScoredNetwork_thenNetworkIsSelected() {
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
-        when(mWifiManager.getScanResults())
-                .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT_EXTERNAL));
-        when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList()))
-                .thenReturn(null, SAVED_WIFI_CONFIGURATION_EXTERNAL);
-        when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verify(mWifiManager, never()).setWifiEnabled(true);
-
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verify(mWifiManager).setWifiEnabled(true);
-        verify(mWifiWakeupNotificationHelper)
-                .maybeShowWifiEnabledNotification(SAVED_WIFI_CONFIGURATION_EXTERNAL);
-    }
-
-    /**
-     * When Wi-Fi is enabled and a saved network is in the scan list, Wi-Fi should not be enabled.
-     */
-    @Test
-    public void wifiNotEnabled_wifiAlreadyEnabled() {
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
-        when(mWifiManager.getScanResults())
-                .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
-        when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_ENABLED);
-
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verifyZeroInteractions(mWifiWakeupNetworkSelector);
-        verify(mWifiManager, never()).setWifiEnabled(true);
-    }
-
-    /**
-     * When Wi-Fi is disabled and a saved network is in the scan list, but
-     * {@link WifiWakeupNetworkSelector}, does not choose this network, Wi-Fi should not be enabled.
-     */
-    @Test
-    public void wifiNotEnabled_userNearSavedNetworkButNotSelected() {
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
-        when(mWifiManager.getScanResults())
-                .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
-        when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList())).thenReturn(null);
-        when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verify(mWifiManager, never()).setWifiEnabled(true);
-    }
-
-    /**
-     * If Wi-Fi is disabled and a saved network is in the scan list, Wi-Fi should not be enabled
-     * if the user has not enabled the wifi wakeup feature.
-     */
-    @Test
-    public void wifiNotEnabled_userDisablesWifiWakeupFeature() {
-        Settings.Global.putInt(mContentResolver, Settings.Global.WIFI_WAKEUP_ENABLED, 0);
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
-        when(mWifiManager.getScanResults())
-                .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
-        when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-
-        mWifiWakeupController.mContentObserver.onChange(true);
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verifyZeroInteractions(mWifiWakeupNetworkSelector);
-        verify(mWifiManager, never()).setWifiEnabled(true);
-    }
-
-    /**
-     * If Wi-Fi is disabled and a saved network is in the scan list, Wi-Fi should not be enabled if
-     * the user is in airplane mode.
-     */
-    @Test
-    public void wifiNotEnabled_userIsInAirplaneMode() {
-        Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON, 1);
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
-        when(mWifiManager.getScanResults())
-                .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
-        when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-
-        mWifiWakeupController.mContentObserver.onChange(true);
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verifyZeroInteractions(mWifiWakeupNetworkSelector);
-        verify(mWifiManager, never()).setWifiEnabled(true);
-    }
-
-    /**
-     * If Wi-Fi is disabled and a saved network is in the scan list, Wi-Fi should not be enabled if
-     * the wifi AP state is not disabled.
-     */
-    @Test
-    public void wifiNotEnabled_wifiApStateIsNotDisabled() {
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
-        when(mWifiManager.getScanResults())
-                .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
-        when(mWifiManager.getWifiState()).thenReturn(WifiManager.WIFI_STATE_DISABLED);
-        when(mWifiManager.getWifiApState()).thenReturn(WifiManager.WIFI_AP_STATE_ENABLED);
-
-        mWifiWakeupController.mContentObserver.onChange(true);
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiApStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verifyZeroInteractions(mWifiWakeupNetworkSelector);
-        verify(mWifiManager, never()).setWifiEnabled(true);
-    }
-
-    /**
-     * If Wi-Fi is disabled when a saved network is the scan list, Wi-Fi should not be enabled no
-     * matter how many scans are performed that include the saved network.
-     */
-    @Test
-    public void wifiNotEnabled_userDisablesWifiNearSavedNetwork_thenDoesNotLeave() {
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION_EXTERNAL));
-        when(mWifiManager.getScanResults())
-                .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
-
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verifyZeroInteractions(mWifiWakeupNetworkSelector);
-        verify(mWifiManager, never()).setWifiEnabled(true);
-    }
-
-    /**
-     * If Wi-Fi is disabled when a saved network is in the scan list, and then that saved network
-     * is removed, Wi-Fi is not enabled even if the user leaves range of that network and returns.
-     */
-    @Test
-    public void wifiNotEnabled_userDisablesWifiNearSavedNetwork_thenRemovesNetwork_thenStays() {
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION),
-                Lists.<WifiConfiguration>newArrayList());
-        when(mWifiManager.getScanResults())
-                .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT))
-                .thenReturn(Lists.<ScanResult>newArrayList())
-                .thenReturn(Lists.newArrayList(SAVED_SCAN_RESULT));
-        when(mWifiWakeupNetworkSelector.selectNetwork(anyMap(), anyList())).thenReturn(null);
-        when(mWifiManager.getWifiState())
-                .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED);
-
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verify(mWifiManager, never()).setWifiEnabled(true);
-    }
-
-    /**
-     * If Wi-Fi is disabled when 2 saved networks are in the scan list, and then a scan result
-     * comes in with only 1 saved network 3x, Wi-Fi should not be enabled.
-     */
-    @Test
-    public void wifiNotEnabled_userDisablesWifiNear2SavedNetworks_thenLeavesRangeOfOneOfThem() {
-        when(mWifiManager.getConfiguredNetworks()).thenReturn(
-                Lists.newArrayList(SAVED_WIFI_CONFIGURATION, SAVED_WIFI_CONFIGURATION2));
-        when(mWifiManager.getScanResults()).thenReturn(
-                Lists.newArrayList(SAVED_SCAN_RESULT,
-                        SAVED_SCAN_RESULT2),
-                Lists.newArrayList(SAVED_SCAN_RESULT));
-        when(mWifiManager.getWifiState())
-                .thenReturn(WifiManager.WIFI_STATE_ENABLED, WifiManager.WIFI_STATE_DISABLED);
-
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendConfiguredNetworksChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendWifiStateChanged(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-        TestUtil.sendScanResultsAvailable(mBroadcastReceiver, mContext);
-
-        verifyZeroInteractions(mWifiWakeupNetworkSelector);
-        verify(mWifiManager, never()).setWifiEnabled(true);
-    }
-
-    /** Test dump() does not crash. */
-    @Test
-    public void testDump() {
-        StringWriter stringWriter = new StringWriter();
-        mWifiWakeupController.dump(
-                new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
-    }
-}
diff --git a/tests/src/com/android/networkrecommendation/WifiWakeupNetworkSelectorTest.java b/tests/src/com/android/networkrecommendation/WifiWakeupNetworkSelectorTest.java
deleted file mode 100644
index c54803c..0000000
--- a/tests/src/com/android/networkrecommendation/WifiWakeupNetworkSelectorTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.when;
-
-import android.content.res.Resources;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiSsid;
-import android.support.test.runner.AndroidJUnit4;
-import android.util.ArrayMap;
-
-import com.google.android.collect.Lists;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-/**
- * Unit tests for {@link WifiWakeupNetworkSelector}
- */
-@RunWith(AndroidJUnit4.class)
-public class WifiWakeupNetworkSelectorTest {
-    private static final String SSID = "ssid";
-    private static final String SSID2 = "ssid2";
-
-    private static final WifiConfiguration WIFI_CONFIGURATION_PSK = new WifiConfiguration();
-    private static final WifiConfiguration WIFI_CONFIGURATION_NONE = new WifiConfiguration();
-    private static final ArrayMap<String, WifiConfiguration> SAVED_WIFI_CONFIGURATION_MAP =
-            new ArrayMap<>();
-
-    static {
-        WIFI_CONFIGURATION_PSK.SSID = "\"" + SSID + "\"";
-        WIFI_CONFIGURATION_PSK.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-        SAVED_WIFI_CONFIGURATION_MAP.put(SSID, WIFI_CONFIGURATION_PSK);
-
-        WIFI_CONFIGURATION_NONE.SSID = "\"" + SSID2 + "\"";
-        WIFI_CONFIGURATION_NONE.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        SAVED_WIFI_CONFIGURATION_MAP.put(SSID2, WIFI_CONFIGURATION_NONE);
-    }
-
-    private static ScanResult buildScanResult(String ssid, int level, int frequency, String caps) {
-        return new ScanResult(WifiSsid.createFromAsciiEncoded(ssid),
-                "00:00:00:00:00:00", 0L, -1, null, caps, level, frequency, 0);
-    }
-
-    private static final int MIN_QUALIFIED_24 = -73;
-    private static final int MIN_QUALIFIED_5 = -70;
-    private static final int FREQUENCY_24 = 2450;
-    private static final int FREQUENCY_5 = 5000;
-    private static final String CAPABILITIES_NONE = "";
-    private static final String CAPABILITIES_PSK = "PSK";
-
-    @Mock private Resources mResources;
-
-    private WifiWakeupNetworkSelector mWifiWakeupNetworkSelector;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        when(mResources.getInteger(
-                R.integer.config_netrec_wifi_score_low_rssi_threshold_24GHz))
-                .thenReturn(MIN_QUALIFIED_24);
-        when(mResources.getInteger(
-                R.integer.config_netrec_wifi_score_low_rssi_threshold_5GHz))
-                .thenReturn(MIN_QUALIFIED_5);
-        when(mResources.getInteger(
-                R.integer.config_netrec_5GHz_preference_boost_factor))
-                .thenReturn(100);
-
-        mWifiWakeupNetworkSelector = new WifiWakeupNetworkSelector(mResources);
-    }
-
-    @Test
-    public void testSelectNetwork_noSavedNetworksInScanResults() {
-        List<ScanResult> scanResults = Lists.newArrayList(
-                buildScanResult("blah", MIN_QUALIFIED_5 + 1, FREQUENCY_5, CAPABILITIES_NONE),
-                buildScanResult("blahtoo", MIN_QUALIFIED_24 + 1, FREQUENCY_24, CAPABILITIES_NONE));
-
-        WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector
-                .selectNetwork(SAVED_WIFI_CONFIGURATION_MAP, scanResults);
-
-        assertNull(selectedNetwork);
-    }
-
-    @Test
-    public void testSelectNetwork_noQualifiedSavedNetworks() {
-        List<ScanResult> scanResults = Lists.newArrayList(
-                buildScanResult(SSID2, MIN_QUALIFIED_5 - 1, FREQUENCY_5, CAPABILITIES_NONE),
-                buildScanResult(SSID2, MIN_QUALIFIED_24 - 1, FREQUENCY_24, CAPABILITIES_NONE));
-
-        WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector
-                .selectNetwork(SAVED_WIFI_CONFIGURATION_MAP, scanResults);
-
-        assertNull(selectedNetwork);
-    }
-
-    @Test
-    public void testSelectNetwork_noMatchingScanResults() {
-        List<ScanResult> scanResults = Lists.newArrayList(
-                buildScanResult(SSID, MIN_QUALIFIED_5 + 1, FREQUENCY_5, CAPABILITIES_NONE),
-                buildScanResult(SSID, MIN_QUALIFIED_24 + 1, FREQUENCY_24, CAPABILITIES_NONE));
-
-        WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector
-                .selectNetwork(SAVED_WIFI_CONFIGURATION_MAP, scanResults);
-
-        assertNull(selectedNetwork);
-    }
-
-    @Test
-    public void testSelectNetwork_secureNetworkOverUnsecure() {
-        List<ScanResult> scanResults = Lists.newArrayList(
-                buildScanResult(SSID, MIN_QUALIFIED_5 + 1, FREQUENCY_5, CAPABILITIES_PSK),
-                buildScanResult(SSID2, MIN_QUALIFIED_5 + 1, FREQUENCY_5, CAPABILITIES_NONE));
-
-        WifiConfiguration selectedNetwork = mWifiWakeupNetworkSelector
-                .selectNetwork(SAVED_WIFI_CONFIGURATION_MAP, scanResults);
-
-        assertEquals(WIFI_CONFIGURATION_PSK, selectedNetwork);
-    }
-}
diff --git a/tests/src/com/android/networkrecommendation/WifiWakeupNotificationHelperTest.java b/tests/src/com/android/networkrecommendation/WifiWakeupNotificationHelperTest.java
deleted file mode 100644
index 2091335..0000000
--- a/tests/src/com/android/networkrecommendation/WifiWakeupNotificationHelperTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * 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 com.android.networkrecommendation;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiInfo;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Set;
-
-/**
- * Unit tests for {@link WifiWakeupNetworkSelector}
- */
-@RunWith(AndroidJUnit4.class)
-public class WifiWakeupNotificationHelperTest {
-    private static final String SSID = "ssid";
-    private static final WifiConfiguration WIFI_CONFIGURATION = new WifiConfiguration();
-
-    static {
-        WIFI_CONFIGURATION.SSID = "\"" + SSID + "\"";
-    }
-
-    private Context mContext;
-    @Mock private NotificationManager mNotificationManager;
-    @Mock private WifiManager mWifiManager;
-    private SharedPreferences mSharedPreferences;
-    private String mSharedPreferenceName;
-
-    private WifiWakeupNotificationHelper mWifiWakeupNotificationHelper;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mContext = InstrumentationRegistry.getTargetContext();
-        mSharedPreferenceName = "WifiWakeupNotificationHelperTest";
-        mSharedPreferences = mContext.getSharedPreferences(mSharedPreferenceName,
-                Context.MODE_PRIVATE);
-
-        mWifiWakeupNotificationHelper = new WifiWakeupNotificationHelper(mContext,
-                mContext.getResources(), new Handler(Looper.getMainLooper()), mNotificationManager,
-                mWifiManager, mSharedPreferences);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mContext.deleteSharedPreferences(mSharedPreferenceName);
-    }
-
-    @Test
-    public void notificationShowsOncePerSsid() {
-        mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
-        mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
-
-        verify(mNotificationManager, times(1))
-                .notify(anyString(), anyInt(), any(Notification.class));
-        Set<String> ssidSet = mSharedPreferences.getStringSet(
-                WifiWakeupNotificationHelper.KEY_SHOWN_SSIDS, null);
-        assertEquals(1, ssidSet.size());
-        assertTrue(ssidSet.contains(WIFI_CONFIGURATION.SSID));
-    }
-
-    @Test
-    public void notificationCanceledWhenNeverConnected() {
-        mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
-
-        mWifiWakeupNotificationHelper.mCancelNotification.run();
-
-        verify(mNotificationManager).cancel(anyString(), anyInt());
-    }
-
-    @Test
-    public void notificationCanceledWhenWifiDisabled() {
-        mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
-
-        when(mWifiManager.isWifiEnabled()).thenReturn(false);
-
-        mWifiWakeupNotificationHelper.mBroadcastReceiver.onReceive(mContext,
-                new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
-
-        verify(mNotificationManager).cancel(anyString(), anyInt());
-    }
-
-    @Test
-    public void notificationCanceledWhenSsidChanged() {
-        WifiInfo firstWifiInfo = new WifiInfo();
-        firstWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(SSID));
-        WifiInfo secondWifiInfo = new WifiInfo();
-        firstWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded("blah"));
-
-        mWifiWakeupNotificationHelper.maybeShowWifiEnabledNotification(WIFI_CONFIGURATION);
-
-        when(mWifiManager.isWifiEnabled()).thenReturn(true);
-        when(mWifiManager.getConnectionInfo()).thenReturn(firstWifiInfo, secondWifiInfo);
-
-        mWifiWakeupNotificationHelper.mBroadcastReceiver.onReceive(mContext,
-                new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
-
-        verify(mNotificationManager, never()).cancel(anyString(), anyInt());
-
-        mWifiWakeupNotificationHelper.mBroadcastReceiver.onReceive(mContext,
-                new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION));
-
-        verify(mNotificationManager).cancel(anyString(), anyInt());
-    }
-}