ONA: Add connection related notifications.

This change introduces the new notifications but does not utilize them
to change the existing functionality yet. It also refactors the state
handling of the notifier to represent the new connection and
notification states.

When attempting to connect to a network, the notification will update
based on connection success and failures.

Bug: 37357441
Bug: 65257065
Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh
Change-Id: I3cf188f7f0fac1ea7d1ec882c9473bdbaccccabd
diff --git a/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java b/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java
new file mode 100644
index 0000000..c0960d4
--- /dev/null
+++ b/service/java/com/android/server/wifi/ConnectToNetworkNotificationBuilder.java
@@ -0,0 +1,143 @@
+/*
+ * 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.server.wifi;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.wifi.ScanResult;
+
+import com.android.internal.R;
+import com.android.internal.notification.SystemNotificationChannels;
+
+/**
+ * Helper to create notifications for {@link OpenNetworkNotifier}.
+ */
+public class ConnectToNetworkNotificationBuilder {
+
+    /** Intent when user dismissed the "Connect to Network" notification. */
+    public static final String ACTION_USER_DISMISSED_NOTIFICATION =
+            "com.android.server.wifi.ConnectToNetworkNotification.USER_DISMISSED_NOTIFICATION";
+
+    /** Intent when user tapped the "Connect to Network" notification. */
+    public static final String ACTION_USER_TAPPED_CONTENT =
+            "com.android.server.wifi.ConnectToNetworkNotification.USER_TAPPED_CONTENT";
+
+    /** Intent when user tapped action button to connect to recommended network. */
+    public static final String ACTION_CONNECT_TO_NETWORK =
+            "com.android.server.wifi.ConnectToNetworkNotification.CONNECT_TO_NETWORK";
+
+    /** Intent when user tapped action button to open Wi-Fi Settings. */
+    public static final String ACTION_PICK_WIFI_NETWORK =
+            "com.android.server.wifi.ConnectToNetworkNotification.PICK_WIFI_NETWORK";
+
+    /** Intent when user tapped "Failed to connect" notification to open Wi-Fi Settings. */
+    public static final String ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE =
+            "com.android.server.wifi.ConnectToNetworkNotification.PICK_NETWORK_AFTER_FAILURE";
+
+    private Context mContext;
+    private Resources mResources;
+    private FrameworkFacade mFrameworkFacade;
+
+    public ConnectToNetworkNotificationBuilder(
+            Context context,
+            FrameworkFacade framework) {
+        mContext = context;
+        mResources = context.getResources();
+        mFrameworkFacade = framework;
+    }
+
+    /**
+     * Creates the connect to network notification that alerts users of a recommended connectable
+     * network.
+     *
+     * @param numNetworks Number of available open networks nearby
+     */
+    public Notification createConnectToNetworkNotification(int numNetworks) {
+
+        CharSequence title = mResources.getQuantityText(
+                com.android.internal.R.plurals.wifi_available, numNetworks);
+        CharSequence content = mResources.getQuantityText(
+                com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
+
+        return createNotificationBuilder(title, content)
+                .setContentIntent(getPrivateBroadcast(ACTION_USER_TAPPED_CONTENT))
+                .build();
+    }
+
+    /**
+     * Creates the notification that indicates the controller is attempting to connect to the
+     * recommended network.
+     *
+     * @param network The network to be recommended
+     */
+    public Notification createNetworkConnectingNotification(ScanResult network) {
+        return createNotificationBuilder(
+                mContext.getText(R.string.wifi_available_title_connecting), network.SSID)
+                .setProgress(0 /* max */, 0 /* progress */, true /* indeterminate */)
+                .build();
+    }
+
+    /**
+     * Creates the notification that indicates the controller successfully connected to the
+     * recommended network.
+     *
+     * @param network The network to be recommended
+     */
+    public Notification createNetworkConnectedNotification(ScanResult network) {
+        return createNotificationBuilder(
+                mContext.getText(R.string.wifi_available_title_connected), network.SSID)
+                .build();
+    }
+
+    /**
+     * Creates the notification that indicates the controller failed to connect to the recommended
+     * network. Tapping this notification opens the wifi picker.
+     */
+    public Notification createNetworkFailedNotification() {
+        return createNotificationBuilder(
+                mContext.getText(R.string.wifi_available_title_failed_to_connect),
+                mContext.getText(R.string.wifi_available_content_failed_to_connect))
+                .setContentIntent(
+                        getPrivateBroadcast(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE))
+                .build();
+    }
+
+    private Notification.Builder createNotificationBuilder(
+            CharSequence title, CharSequence content) {
+        return mFrameworkFacade.makeNotificationBuilder(mContext,
+                SystemNotificationChannels.NETWORK_AVAILABLE)
+                .setSmallIcon(R.drawable.stat_notify_wifi_in_range)
+                .setAutoCancel(true)
+                .setTicker(title)
+                .setContentTitle(title)
+                .setContentText(content)
+                .setDeleteIntent(getPrivateBroadcast(ACTION_USER_DISMISSED_NOTIFICATION))
+                .setShowWhen(false)
+                .setLocalOnly(true)
+                .setColor(mResources.getColor(R.color.system_notification_accent_color,
+                        mContext.getTheme()));
+    }
+
+    private PendingIntent getPrivateBroadcast(String action) {
+        Intent intent = new Intent(action).setPackage("android");
+        return mFrameworkFacade.getBroadcast(
+                mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+}
diff --git a/service/java/com/android/server/wifi/OpenNetworkNotificationBuilder.java b/service/java/com/android/server/wifi/OpenNetworkNotificationBuilder.java
deleted file mode 100644
index 5963b57..0000000
--- a/service/java/com/android/server/wifi/OpenNetworkNotificationBuilder.java
+++ /dev/null
@@ -1,91 +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.server.wifi;
-
-import static com.android.server.wifi.OpenNetworkNotifier.ACTION_USER_DISMISSED_NOTIFICATION;
-import static com.android.server.wifi.OpenNetworkNotifier.ACTION_USER_TAPPED_CONTENT;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-
-import com.android.internal.R;
-import com.android.internal.notification.SystemNotificationChannels;
-
-/**
- * Helper to create notifications for {@link OpenNetworkNotifier}.
- */
-public class OpenNetworkNotificationBuilder {
-
-    private Context mContext;
-    private Resources mResources;
-    private FrameworkFacade mFrameworkFacade;
-
-    public OpenNetworkNotificationBuilder(
-            Context context,
-            FrameworkFacade framework) {
-        mContext = context;
-        mResources = context.getResources();
-        mFrameworkFacade = framework;
-    }
-
-    /**
-     * Creates the open network available notification that alerts users there are open networks
-     * nearby.
-     */
-    public Notification createOpenNetworkAvailableNotification(int numNetworks) {
-
-        CharSequence title = mResources.getQuantityText(
-                com.android.internal.R.plurals.wifi_available, numNetworks);
-        CharSequence content = mResources.getQuantityText(
-                com.android.internal.R.plurals.wifi_available_detailed, numNetworks);
-
-        PendingIntent contentIntent =
-                mFrameworkFacade.getBroadcast(
-                        mContext,
-                        0,
-                        new Intent(ACTION_USER_TAPPED_CONTENT),
-                        PendingIntent.FLAG_UPDATE_CURRENT);
-        return createNotificationBuilder(title, content)
-                .setContentIntent(contentIntent)
-                .build();
-    }
-
-    private Notification.Builder createNotificationBuilder(
-            CharSequence title, CharSequence content) {
-        PendingIntent deleteIntent =
-                mFrameworkFacade.getBroadcast(
-                        mContext,
-                        0,
-                        new Intent(ACTION_USER_DISMISSED_NOTIFICATION),
-                        PendingIntent.FLAG_UPDATE_CURRENT);
-        return mFrameworkFacade.makeNotificationBuilder(mContext,
-                SystemNotificationChannels.NETWORK_AVAILABLE)
-                .setSmallIcon(R.drawable.stat_notify_wifi_in_range)
-                .setAutoCancel(true)
-                .setTicker(title)
-                .setContentTitle(title)
-                .setContentText(content)
-                .setDeleteIntent(deleteIntent)
-                .setShowWhen(false)
-                .setLocalOnly(true)
-                .setColor(mResources.getColor(R.color.system_notification_accent_color,
-                        mContext.getTheme()));
-    }
-}
diff --git a/service/java/com/android/server/wifi/OpenNetworkNotifier.java b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
index 2792237..d2d45c3 100644
--- a/service/java/com/android/server/wifi/OpenNetworkNotifier.java
+++ b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
@@ -16,7 +16,15 @@
 
 package com.android.server.wifi;
 
+import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK;
+import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK;
+import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE;
+import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_USER_DISMISSED_NOTIFICATION;
+import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_USER_TAPPED_CONTENT;
+
+import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -42,6 +50,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.Set;
 
@@ -55,12 +65,39 @@
 
     private static final String TAG = "OpenNetworkNotifier";
 
-    static final String ACTION_USER_DISMISSED_NOTIFICATION =
-            "com.android.server.wifi.OpenNetworkNotifier.USER_DISMISSED_NOTIFICATION";
-    static final String ACTION_USER_TAPPED_CONTENT =
-            "com.android.server.wifi.OpenNetworkNotifier.USER_TAPPED_CONTENT";
-    static final String ACTION_CONNECT_TO_NETWORK =
-            "com.android.server.wifi.OpenNetworkNotifier.CONNECT_TO_NETWORK";
+    /** 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;
+
+    /** The state of the notification */
+    @IntDef({
+            STATE_NO_NOTIFICATION,
+            STATE_SHOWING_RECOMMENDATION_NOTIFICATION,
+            STATE_CONNECTING_IN_NOTIFICATION,
+            STATE_CONNECTED_NOTIFICATION,
+            STATE_CONNECT_FAILED_NOTIFICATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface State {}
+
+    /** No recommendation is made and no notifications are shown. */
+    private static final int STATE_NO_NOTIFICATION = 0;
+    /** The initial notification recommending an open network to connect to is shown. */
+    private static final int STATE_SHOWING_RECOMMENDATION_NOTIFICATION = 1;
+    /** The notification of status of connecting to the recommended network is shown. */
+    private static final int STATE_CONNECTING_IN_NOTIFICATION = 2;
+    /** The notification that the connection to the recommended network was successful is shown. */
+    private static final int STATE_CONNECTED_NOTIFICATION = 3;
+    /** The notification to show that connection to the recommended network failed is shown. */
+    private static final int STATE_CONNECT_FAILED_NOTIFICATION = 4;
+
+    /** Current state of the notification. */
+    @State private int mState = STATE_NO_NOTIFICATION;
 
     /** Identifier of the {@link SsidSetStoreData}. */
     private static final String STORE_DATA_IDENTIFIER = "OpenNetworkNotifierBlacklist";
@@ -79,8 +116,6 @@
 
     /** Whether the user has set the setting to show the 'available networks' notification. */
     private boolean mSettingEnabled;
-    /** Whether the notification is being shown. */
-    private boolean mNotificationShown;
     /** Whether the screen is on or not. */
     private boolean mScreenOn;
 
@@ -95,7 +130,7 @@
     private final WifiStateMachine mWifiStateMachine;
     private final Messenger mSrcMessenger;
     private final OpenNetworkRecommender mOpenNetworkRecommender;
-    private final OpenNetworkNotificationBuilder mOpenNetworkNotificationBuilder;
+    private final ConnectToNetworkNotificationBuilder mNotificationBuilder;
 
     private ScanResult mRecommendedNetwork;
 
@@ -107,7 +142,8 @@
             WifiConfigManager wifiConfigManager,
             WifiConfigStore wifiConfigStore,
             WifiStateMachine wifiStateMachine,
-            OpenNetworkRecommender openNetworkRecommender) {
+            OpenNetworkRecommender openNetworkRecommender,
+            ConnectToNetworkNotificationBuilder connectToNetworkNotificationBuilder) {
         mContext = context;
         mHandler = new Handler(looper);
         mFrameworkFacade = framework;
@@ -115,7 +151,7 @@
         mConfigManager = wifiConfigManager;
         mWifiStateMachine = wifiStateMachine;
         mOpenNetworkRecommender = openNetworkRecommender;
-        mOpenNetworkNotificationBuilder = new OpenNetworkNotificationBuilder(context, framework);
+        mNotificationBuilder = connectToNetworkNotificationBuilder;
         mScreenOn = false;
         mSrcMessenger = new Messenger(new Handler(looper, mConnectionStateCallback));
 
@@ -135,6 +171,8 @@
         filter.addAction(ACTION_USER_DISMISSED_NOTIFICATION);
         filter.addAction(ACTION_USER_TAPPED_CONTENT);
         filter.addAction(ACTION_CONNECT_TO_NETWORK);
+        filter.addAction(ACTION_PICK_WIFI_NETWORK);
+        filter.addAction(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
         mContext.registerReceiver(
                 mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
     }
@@ -153,6 +191,12 @@
                         case ACTION_CONNECT_TO_NETWORK:
                             handleConnectToNetworkAction();
                             break;
+                        case ACTION_PICK_WIFI_NETWORK:
+                            handleSeeAllNetworksAction();
+                            break;
+                        case ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE:
+                            handlePickWifiNetworkAfterConnectFailure();
+                            break;
                         default:
                             Log.e(TAG, "Unknown action " + intent.getAction());
                     }
@@ -178,17 +222,17 @@
     /**
      * Clears the pending notification. This is called by {@link WifiConnectivityManager} on stop.
      *
-     * @param resetRepeatDelay resets the time delay for repeated notification if true.
+     * @param resetRepeatTime resets the time delay for repeated notification if true.
      */
-    public void clearPendingNotification(boolean resetRepeatDelay) {
-        if (resetRepeatDelay) {
+    public void clearPendingNotification(boolean resetRepeatTime) {
+        if (resetRepeatTime) {
             mNotificationRepeatTime = 0;
         }
 
-        if (mNotificationShown) {
+        if (mState != STATE_NO_NOTIFICATION) {
             getNotificationManager().cancel(SystemMessage.NOTE_NETWORK_AVAILABLE);
+            mState = STATE_NO_NOTIFICATION;
             mRecommendedNetwork = null;
-            mNotificationShown = false;
         }
     }
 
@@ -205,11 +249,11 @@
      */
     public void handleScanResults(@NonNull List<ScanDetail> availableNetworks) {
         if (!isControllerEnabled()) {
-            clearPendingNotification(true /* resetRepeatDelay */);
+            clearPendingNotification(true /* resetRepeatTime */);
             return;
         }
         if (availableNetworks.isEmpty()) {
-            clearPendingNotification(false /* resetRepeatDelay */);
+            clearPendingNotification(false /* resetRepeatTime */);
             return;
         }
 
@@ -217,14 +261,14 @@
         // could occur between a user picking a network in settings and a network candidate picked
         // through network selection, which will happen because screen on triggers a new
         // connectivity scan.
-        if (mNotificationShown || !mScreenOn) {
+        if (mState !=  STATE_NO_NOTIFICATION || !mScreenOn) {
             return;
         }
 
         mRecommendedNetwork = mOpenNetworkRecommender.recommendNetwork(
                 availableNetworks, new ArraySet<>(mBlacklistedSsids));
 
-        postNotification(availableNetworks.size());
+        postInitialNotification(availableNetworks.size());
     }
 
     /** Handles screen state changes. */
@@ -232,28 +276,78 @@
         mScreenOn = screenOn;
     }
 
+    /**
+     * Called by {@link WifiConnectivityManager} when Wi-Fi is connected. If the notification
+     * was in the connecting state, update the notification to show that it has connected to the
+     * recommended network.
+     */
+    public void handleWifiConnected() {
+        if (mState != STATE_CONNECTING_IN_NOTIFICATION) {
+            clearPendingNotification(true /* resetRepeatTime */);
+            return;
+        }
+
+        postNotification(mNotificationBuilder.createNetworkConnectedNotification(
+                mRecommendedNetwork));
+        mState = STATE_CONNECTED_NOTIFICATION;
+        mHandler.postDelayed(
+                () -> {
+                    if (mState == STATE_CONNECTED_NOTIFICATION) {
+                        clearPendingNotification(true /* resetRepeatTime */);
+                    }
+                },
+                TIME_TO_SHOW_CONNECTED_MILLIS);
+    }
+
+    /**
+     * Handles when a Wi-Fi connection attempt failed.
+     */
+    public void handleConnectionFailure() {
+        if (mState != STATE_CONNECTING_IN_NOTIFICATION) {
+            return;
+        }
+        postNotification(mNotificationBuilder.createNetworkFailedNotification());
+        mState = STATE_CONNECT_FAILED_NOTIFICATION;
+        mHandler.postDelayed(
+                () -> {
+                    if (mState == STATE_CONNECT_FAILED_NOTIFICATION) {
+                        clearPendingNotification(false /* resetRepeatTime */);
+                    }
+                },
+                TIME_TO_SHOW_FAILED_MILLIS);
+    }
+
     private NotificationManager getNotificationManager() {
         return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
     }
 
-    private void postNotification(int numNetworks) {
+    private void postInitialNotification(int numNetworks) {
+        if (mState != STATE_NO_NOTIFICATION
+                && mState != STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
+            return;
+        }
         // Not enough time has passed to show the notification again
         if (mClock.getWallClockMillis() < mNotificationRepeatTime) {
             return;
         }
 
-        getNotificationManager().notify(
-                SystemMessage.NOTE_NETWORK_AVAILABLE,
-                mOpenNetworkNotificationBuilder.createOpenNetworkAvailableNotification(
-                        numNetworks));
-        mNotificationShown = true;
+        postNotification(mNotificationBuilder.createConnectToNetworkNotification(
+                numNetworks));
+        mState = STATE_SHOWING_RECOMMENDATION_NOTIFICATION;
         mNotificationRepeatTime = mClock.getWallClockMillis() + mNotificationRepeatDelay;
     }
 
+    private void postNotification(Notification notification) {
+        getNotificationManager().notify(SystemMessage.NOTE_NETWORK_AVAILABLE, notification);
+    }
+
     private void handleConnectToNetworkAction() {
-        if (mRecommendedNetwork == null) {
+        if (mState != STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
             return;
         }
+        postNotification(mNotificationBuilder.createNetworkConnectingNotification(
+                mRecommendedNetwork));
+
         Log.d(TAG, "User initiated connection to recommended network: " + mRecommendedNetwork.SSID);
         WifiConfiguration network = ScanResultUtil.createNetworkFromScanResult(mRecommendedNetwork);
         Message msg = Message.obtain();
@@ -262,32 +356,52 @@
         msg.obj = network;
         msg.replyTo = mSrcMessenger;
         mWifiStateMachine.sendMessage(msg);
+
+        mState = STATE_CONNECTING_IN_NOTIFICATION;
+        mHandler.postDelayed(
+                () -> {
+                    if (mState == STATE_CONNECTING_IN_NOTIFICATION) {
+                        handleConnectionFailure();
+                    }
+                },
+                TIME_TO_SHOW_CONNECTING_MILLIS);
     }
 
-    /**
-     * Handles when a Wi-Fi connection attempt failed.
-     */
-    public void handleConnectionFailure() {
-        // Stub. Should post connection failure notification once implemented.
+    private void handleSeeAllNetworksAction() {
+        startWifiSettings();
     }
 
-    /** Opens Wi-Fi picker. */
-    private void handleUserClickedContentAction() {
-        mNotificationShown = false;
+    private void startWifiSettings() {
         mContext.startActivity(
                 new Intent(Settings.ACTION_WIFI_SETTINGS)
                         .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        clearPendingNotification(false /* resetRepeatTime */);
+    }
+
+    private void handlePickWifiNetworkAfterConnectFailure() {
+        startWifiSettings();
+    }
+
+    private void handleUserClickedContentAction() {
+        startWifiSettings();
+        resetStateAndDelayNotification();
     }
 
     private void handleUserDismissedAction() {
-        if (mRecommendedNetwork != null) {
+        if (mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
             // blacklist dismissed network
             mBlacklistedSsids.add(mRecommendedNetwork.SSID);
             mConfigManager.saveToStore(false /* forceWrite */);
             Log.d(TAG, "Network is added to the open network notification blacklist: "
                     + mRecommendedNetwork.SSID);
         }
-        mNotificationShown = false;
+        resetStateAndDelayNotification();
+    }
+
+    private void resetStateAndDelayNotification() {
+        mState = STATE_NO_NOTIFICATION;
+        mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelay;
+        mRecommendedNetwork = null;
     }
 
     /** Dump ONA controller state. */
@@ -296,7 +410,7 @@
         pw.println("mSettingEnabled " + mSettingEnabled);
         pw.println("currentTime: " + mClock.getWallClockMillis());
         pw.println("mNotificationRepeatTime: " + mNotificationRepeatTime);
-        pw.println("mNotificationShown: " + mNotificationShown);
+        pw.println("mState: " + mState);
         pw.println("mBlacklistedSsids: " + mBlacklistedSsids.toString());
     }
 
@@ -327,7 +441,7 @@
         public void onChange(boolean selfChange) {
             super.onChange(selfChange);
             mSettingEnabled = getValue();
-            clearPendingNotification(true /* resetRepeatDelay */);
+            clearPendingNotification(true /* resetRepeatTime */);
         }
 
         private boolean getValue() {
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index a0d0e82..95a68f1 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -234,7 +234,8 @@
         mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
                 mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock,
                 mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
-                new OpenNetworkRecommender());
+                new OpenNetworkRecommender(),
+                new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
         mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
         mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
                 mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
diff --git a/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java b/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
index 7c1223a..3af19e1 100644
--- a/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
@@ -18,22 +18,23 @@
 
 import static com.android.server.wifi.OpenNetworkNotifier.DEFAULT_REPEAT_DELAY_SEC;
 
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
 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.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.test.TestLooper;
@@ -42,7 +43,6 @@
 
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Answers;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -65,12 +65,13 @@
     @Mock private Clock mClock;
     @Mock private WifiConfigStore mWifiConfigStore;
     @Mock private WifiConfigManager mWifiConfigManager;
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Notification.Builder mNotificationBuilder;
     @Mock private NotificationManager mNotificationManager;
     @Mock private WifiStateMachine mWifiStateMachine;
     @Mock private OpenNetworkRecommender mOpenNetworkRecommender;
+    @Mock private ConnectToNetworkNotificationBuilder mNotificationBuilder;
     @Mock private UserManager mUserManager;
     private OpenNetworkNotifier mNotificationController;
+    private TestLooper mLooper;
     private BroadcastReceiver mBroadcastReceiver;
     private ScanResult mDummyNetwork;
     private List<ScanDetail> mOpenNetworks;
@@ -88,8 +89,6 @@
         when(mFrameworkFacade.getIntegerSetting(mContext,
                 Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, DEFAULT_REPEAT_DELAY_SEC))
                 .thenReturn(DEFAULT_REPEAT_DELAY_SEC);
-        when(mFrameworkFacade.makeNotificationBuilder(any(), anyString()))
-                .thenReturn(mNotificationBuilder);
         when(mContext.getSystemService(Context.USER_SERVICE))
                 .thenReturn(mUserManager);
         when(mContext.getResources()).thenReturn(mResources);
@@ -102,10 +101,10 @@
         mOpenNetworks.add(new ScanDetail(mDummyNetwork, null /* networkDetail */));
         mBlacklistedSsids = new ArraySet<>();
 
-        TestLooper mock_looper = new TestLooper();
+        mLooper = new TestLooper();
         mNotificationController = new OpenNetworkNotifier(
-                mContext, mock_looper.getLooper(), mFrameworkFacade, mClock, mWifiConfigManager,
-                mWifiConfigStore, mWifiStateMachine, mOpenNetworkRecommender);
+                mContext, mLooper.getLooper(), mFrameworkFacade, mClock, mWifiConfigManager,
+                mWifiConfigStore, mWifiStateMachine, mOpenNetworkRecommender, mNotificationBuilder);
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
@@ -121,6 +120,7 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
     }
 
@@ -144,6 +144,7 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.handleScanResults(new ArrayList<>());
@@ -159,6 +160,7 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.handleScreenStateChanged(false);
@@ -176,6 +178,7 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
     }
 
@@ -188,6 +191,7 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(true);
@@ -220,7 +224,7 @@
     }
 
     /**
-     * When a notification is posted and cleared without reseting delay, the next scan with open
+     * When a notification is posted and cleared without resetting delay, the next scan with open
      * networks should not post another notification.
      */
     @Test
@@ -228,6 +232,7 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(false);
@@ -238,11 +243,12 @@
         verify(mOpenNetworkRecommender, times(2)).recommendNetwork(
                 mOpenNetworks, mBlacklistedSsids);
         verify(mNotificationManager).notify(anyInt(), any());
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).cancel(anyInt());
     }
 
     /**
-     * When a notification is posted and cleared without reseting delay, the next scan with open
+     * When a notification is posted and cleared without resetting delay, the next scan with open
      * networks should post a notification.
      */
     @Test
@@ -250,6 +256,7 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(true);
@@ -258,6 +265,7 @@
 
         verify(mOpenNetworkRecommender, times(2)).recommendNetwork(
                 mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder, times(2)).createConnectToNetworkNotification(1);
         verify(mNotificationManager, times(2)).notify(anyInt(), any());
     }
 
@@ -269,12 +277,16 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(
-                mContext, new Intent(OpenNetworkNotifier.ACTION_USER_TAPPED_CONTENT));
+                mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_USER_TAPPED_CONTENT));
 
-        verify(mContext).startActivity(any());
+        ArgumentCaptor<Intent> pickerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(pickerIntentCaptor.capture());
+        assertEquals(pickerIntentCaptor.getValue().getAction(), Settings.ACTION_WIFI_SETTINGS);
     }
 
     /**
@@ -286,10 +298,12 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(
-                mContext, new Intent(OpenNetworkNotifier.ACTION_USER_DISMISSED_NOTIFICATION));
+                mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_USER_DISMISSED_NOTIFICATION));
 
         verify(mWifiConfigManager).saveToStore(false /* forceWrite */);
 
@@ -301,7 +315,7 @@
     }
 
     /**
-     * When a notification is posted and cleared without reseting delay, after the delay has passed
+     * When a notification is posted and cleared without resetting delay, after the delay has passed
      * the next scan with open networks should post a notification.
      */
     @Test
@@ -309,6 +323,7 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
 
         mNotificationController.clearPendingNotification(false);
@@ -341,6 +356,7 @@
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
         verify(mNotificationManager).notify(anyInt(), any());
 
         when(mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT))
@@ -352,30 +368,172 @@
     }
 
     /**
-     * {@link OpenNetworkNotifier#ACTION_CONNECT_TO_NETWORK} does not connect to any network if
-     * there is no current recommendation.
+     * {@link ConnectToNetworkNotificationBuilder#ACTION_CONNECT_TO_NETWORK} does not connect to
+     * any network if the initial notification is not showing.
      */
     @Test
-    public void actionConnectToNetwork_currentRecommendationIsNull_doesNothing() {
+    public void actionConnectToNetwork_notificationNotShowing_doesNothing() {
         mBroadcastReceiver.onReceive(mContext,
-                new Intent(OpenNetworkNotifier.ACTION_CONNECT_TO_NETWORK));
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
 
         verify(mWifiStateMachine, never()).sendMessage(any(Message.class));
     }
 
     /**
-     * {@link OpenNetworkNotifier#ACTION_CONNECT_TO_NETWORK} connects to the currently recommended
-     * network if it exists.
+     * {@link ConnectToNetworkNotificationBuilder#ACTION_CONNECT_TO_NETWORK} connects to the
+     * currently recommended network if it exists.
      */
     @Test
-    public void actionConnectToNetwork_currentRecommendationExists_connectsToNetwork() {
+    public void actionConnectToNetwork_currentRecommendationExists_connectsAndPostsNotification() {
         mNotificationController.handleScanResults(mOpenNetworks);
 
         verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
+        verify(mNotificationManager).notify(anyInt(), any());
 
         mBroadcastReceiver.onReceive(mContext,
-                new Intent(OpenNetworkNotifier.ACTION_CONNECT_TO_NETWORK));
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
 
         verify(mWifiStateMachine).sendMessage(any(Message.class));
+        // Connecting Notification
+        verify(mNotificationBuilder).createNetworkConnectingNotification(mDummyNetwork);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleWifiConnected()} does not post connected notification if
+     * the connecting notification is not showing
+     */
+    @Test
+    public void networkConnectionSuccess_wasNotInConnectingFlow_doesNothing() {
+        mNotificationController.handleWifiConnected();
+
+        verify(mNotificationManager, never()).notify(anyInt(), any());
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleWifiConnected()} clears notification that is not connecting.
+     */
+    @Test
+    public void networkConnectionSuccess_wasShowingNotification_clearsNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mNotificationController.handleWifiConnected();
+
+        verify(mNotificationManager).cancel(anyInt());
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleWifiConnected()} posts the connected notification if
+     * the connecting notification is showing.
+     */
+    @Test
+    public void networkConnectionSuccess_wasInConnectingFlow_postsConnectedNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
+
+        // Connecting Notification
+        verify(mNotificationBuilder).createNetworkConnectingNotification(mDummyNetwork);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+
+        mNotificationController.handleWifiConnected();
+
+        // Connected Notification
+        verify(mNotificationBuilder).createNetworkConnectedNotification(mDummyNetwork);
+        verify(mNotificationManager, times(3)).notify(anyInt(), any());
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleConnectionFailure()} posts the Failed to Connect
+     * notification if the connecting notification is showing.
+     */
+    @Test
+    public void networkConnectionFailure_wasNotInConnectingFlow_doesNothing() {
+        mNotificationController.handleConnectionFailure();
+
+        verify(mNotificationManager, never()).notify(anyInt(), any());
+    }
+
+    /**
+     * {@link OpenNetworkNotifier#handleConnectionFailure()} posts the Failed to Connect
+     * notification if the connecting notification is showing.
+     */
+    @Test
+    public void networkConnectionFailure_wasInConnectingFlow_postsFailedToConnectNotification() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
+
+        // Connecting Notification
+        verify(mNotificationBuilder).createNetworkConnectingNotification(mDummyNetwork);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+
+        mNotificationController.handleConnectionFailure();
+
+        // Failed to Connect Notification
+        verify(mNotificationBuilder).createNetworkFailedNotification();
+        verify(mNotificationManager, times(3)).notify(anyInt(), any());
+    }
+
+    /**
+     * When a {@link WifiManager#CONNECT_NETWORK_FAILED} is received from the connection callback
+     * of {@link WifiStateMachine#sendMessage(Message)}, a Failed to Connect notification should
+     * be posted. On tapping this notification, Wi-Fi Settings should be launched.
+     */
+    @Test
+    public void connectionFailedCallback_postsFailedToConnectNotification() throws RemoteException {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+        // Initial Notification
+        verify(mNotificationBuilder).createConnectToNetworkNotification(1);
+        verify(mNotificationManager).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK));
+
+        ArgumentCaptor<Message> connectMessageCaptor = ArgumentCaptor.forClass(Message.class);
+        verify(mWifiStateMachine).sendMessage(connectMessageCaptor.capture());
+        Message connectMessage = connectMessageCaptor.getValue();
+
+        // Connecting Notification
+        verify(mNotificationBuilder).createNetworkConnectingNotification(mDummyNetwork);
+        verify(mNotificationManager, times(2)).notify(anyInt(), any());
+
+        Message connectFailedMsg = Message.obtain();
+        connectFailedMsg.what = WifiManager.CONNECT_NETWORK_FAILED;
+        connectMessage.replyTo.send(connectFailedMsg);
+        mLooper.dispatchAll();
+
+        // Failed to Connect Notification
+        verify(mNotificationBuilder).createNetworkFailedNotification();
+        verify(mNotificationManager, times(3)).notify(anyInt(), any());
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(ConnectToNetworkNotificationBuilder
+                        .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE));
+
+        ArgumentCaptor<Intent> pickerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivity(pickerIntentCaptor.capture());
+        assertEquals(pickerIntentCaptor.getValue().getAction(), Settings.ACTION_WIFI_SETTINGS);
     }
 }