Merge "ONA: Add connection related notifications." into oc-mr1-dev
am: f0e94d946b

Change-Id: I576d80c0feba43b8f7892351f0e782b2efc649bb
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 7778c2c..fc3af83 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -227,7 +227,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);
     }
 }