Merge "ONA: Implement connection attempt and failure callback." into oc-mr1-dev
diff --git a/service/java/com/android/server/wifi/OpenNetworkNotifier.java b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
index 692c8e2..2792237 100644
--- a/service/java/com/android/server/wifi/OpenNetworkNotifier.java
+++ b/service/java/com/android/server/wifi/OpenNetworkNotifier.java
@@ -24,8 +24,12 @@
 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.os.Message;
+import android.os.Messenger;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -34,6 +38,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.server.wifi.util.ScanResultUtil;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -54,6 +59,8 @@
             "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";
 
     /** Identifier of the {@link SsidSetStoreData}. */
     private static final String STORE_DATA_IDENTIFIER = "OpenNetworkNotifierBlacklist";
@@ -85,6 +92,8 @@
     private final FrameworkFacade mFrameworkFacade;
     private final Clock mClock;
     private final WifiConfigManager mConfigManager;
+    private final WifiStateMachine mWifiStateMachine;
+    private final Messenger mSrcMessenger;
     private final OpenNetworkRecommender mOpenNetworkRecommender;
     private final OpenNetworkNotificationBuilder mOpenNetworkNotificationBuilder;
 
@@ -97,15 +106,18 @@
             Clock clock,
             WifiConfigManager wifiConfigManager,
             WifiConfigStore wifiConfigStore,
+            WifiStateMachine wifiStateMachine,
             OpenNetworkRecommender openNetworkRecommender) {
         mContext = context;
         mHandler = new Handler(looper);
         mFrameworkFacade = framework;
         mClock = clock;
         mConfigManager = wifiConfigManager;
+        mWifiStateMachine = wifiStateMachine;
         mOpenNetworkRecommender = openNetworkRecommender;
         mOpenNetworkNotificationBuilder = new OpenNetworkNotificationBuilder(context, framework);
         mScreenOn = false;
+        mSrcMessenger = new Messenger(new Handler(looper, mConnectionStateCallback));
 
         mBlacklistedSsids = new ArraySet<>();
         wifiConfigStore.registerStoreData(new SsidSetStoreData(
@@ -122,6 +134,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_USER_DISMISSED_NOTIFICATION);
         filter.addAction(ACTION_USER_TAPPED_CONTENT);
+        filter.addAction(ACTION_CONNECT_TO_NETWORK);
         mContext.registerReceiver(
                 mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
     }
@@ -130,14 +143,38 @@
             new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
-                    if (ACTION_USER_TAPPED_CONTENT.equals(intent.getAction())) {
-                        handleUserClickedContentAction();
-                    } else if (ACTION_USER_DISMISSED_NOTIFICATION.equals(intent.getAction())) {
-                        handleUserDismissedAction();
+                    switch (intent.getAction()) {
+                        case ACTION_USER_TAPPED_CONTENT:
+                            handleUserClickedContentAction();
+                            break;
+                        case ACTION_USER_DISMISSED_NOTIFICATION:
+                            handleUserDismissedAction();
+                            break;
+                        case ACTION_CONNECT_TO_NETWORK:
+                            handleConnectToNetworkAction();
+                            break;
+                        default:
+                            Log.e(TAG, "Unknown action " + intent.getAction());
                     }
                 }
             };
 
+    private final Handler.Callback mConnectionStateCallback = (Message msg) -> {
+        switch (msg.what) {
+            // Success here means that an attempt to connect to the network has been initiated.
+            // Successful connection updates are received via the
+            // WifiConnectivityManager#handleConnectionStateChanged() callback.
+            case WifiManager.CONNECT_NETWORK_SUCCEEDED:
+                break;
+            case WifiManager.CONNECT_NETWORK_FAILED:
+                handleConnectionFailure();
+                break;
+            default:
+                Log.e(TAG, "Unknown message " + msg.what);
+        }
+        return true;
+    };
+
     /**
      * Clears the pending notification. This is called by {@link WifiConnectivityManager} on stop.
      *
@@ -213,6 +250,27 @@
         mNotificationRepeatTime = mClock.getWallClockMillis() + mNotificationRepeatDelay;
     }
 
+    private void handleConnectToNetworkAction() {
+        if (mRecommendedNetwork == null) {
+            return;
+        }
+        Log.d(TAG, "User initiated connection to recommended network: " + mRecommendedNetwork.SSID);
+        WifiConfiguration network = ScanResultUtil.createNetworkFromScanResult(mRecommendedNetwork);
+        Message msg = Message.obtain();
+        msg.what = WifiManager.CONNECT_NETWORK;
+        msg.arg1 = WifiConfiguration.INVALID_NETWORK_ID;
+        msg.obj = network;
+        msg.replyTo = mSrcMessenger;
+        mWifiStateMachine.sendMessage(msg);
+    }
+
+    /**
+     * Handles when a Wi-Fi connection attempt failed.
+     */
+    public void handleConnectionFailure() {
+        // Stub. Should post connection failure notification once implemented.
+    }
+
     /** Opens Wi-Fi picker. */
     private void handleUserClickedContentAction() {
         mNotificationShown = false;
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index e417fde..1c5ac28 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -1088,6 +1088,17 @@
     }
 
     /**
+     * Handler when a WiFi connection attempt ended.
+     *
+     * @param failureCode {@link WifiMetrics.ConnectionEvent} failure code.
+     */
+    public void handleConnectionAttemptEnded(int failureCode) {
+        if (failureCode != WifiMetrics.ConnectionEvent.FAILURE_NONE) {
+            mOpenNetworkNotifier.handleConnectionFailure();
+        }
+    }
+
+    /**
      * Handler when user toggles whether untrusted connection is allowed
      */
     public void setUntrustedConnectionAllowed(boolean allowed) {
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 734c28d..7778c2c 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -226,7 +226,8 @@
         mCertManager = new WifiCertManager(mContext);
         mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
                 mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock,
-                mWifiConfigManager, mWifiConfigStore, new OpenNetworkRecommender());
+                mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
+                new OpenNetworkRecommender());
         mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
         mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
                 mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
diff --git a/service/java/com/android/server/wifi/WifiStateMachine.java b/service/java/com/android/server/wifi/WifiStateMachine.java
index 38c767f..41a5994 100644
--- a/service/java/com/android/server/wifi/WifiStateMachine.java
+++ b/service/java/com/android/server/wifi/WifiStateMachine.java
@@ -3429,11 +3429,12 @@
     }
 
     /**
-     * Inform other components (WifiMetrics, WifiDiagnostics, etc.) that the current connection attempt
-     * has concluded.
+     * Inform other components (WifiMetrics, WifiDiagnostics, WifiConnectivityManager, etc.) that
+     * the current connection attempt has concluded.
      */
     private void reportConnectionAttemptEnd(int level2FailureCode, int connectivityFailureCode) {
         mWifiMetrics.endConnectionEvent(level2FailureCode, connectivityFailureCode);
+        mWifiConnectivityManager.handleConnectionAttemptEnded(level2FailureCode);
         switch (level2FailureCode) {
             case WifiMetrics.ConnectionEvent.FAILURE_NONE:
                 // Ideally, we'd wait until IP reachability has been confirmed. this code falls
diff --git a/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java b/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
index 957fc22..7c1223a 100644
--- a/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/OpenNetworkNotifierTest.java
@@ -33,6 +33,7 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.net.wifi.ScanResult;
+import android.os.Message;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.test.TestLooper;
@@ -66,6 +67,7 @@
     @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 UserManager mUserManager;
     private OpenNetworkNotifier mNotificationController;
@@ -103,7 +105,7 @@
         TestLooper mock_looper = new TestLooper();
         mNotificationController = new OpenNetworkNotifier(
                 mContext, mock_looper.getLooper(), mFrameworkFacade, mClock, mWifiConfigManager,
-                mWifiConfigStore, mOpenNetworkRecommender);
+                mWifiConfigStore, mWifiStateMachine, mOpenNetworkRecommender);
         ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
         verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
@@ -348,4 +350,32 @@
 
         verify(mNotificationManager).cancel(anyInt());
     }
+
+    /**
+     * {@link OpenNetworkNotifier#ACTION_CONNECT_TO_NETWORK} does not connect to any network if
+     * there is no current recommendation.
+     */
+    @Test
+    public void actionConnectToNetwork_currentRecommendationIsNull_doesNothing() {
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(OpenNetworkNotifier.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.
+     */
+    @Test
+    public void actionConnectToNetwork_currentRecommendationExists_connectsToNetwork() {
+        mNotificationController.handleScanResults(mOpenNetworks);
+
+        verify(mOpenNetworkRecommender).recommendNetwork(mOpenNetworks, mBlacklistedSsids);
+
+        mBroadcastReceiver.onReceive(mContext,
+                new Intent(OpenNetworkNotifier.ACTION_CONNECT_TO_NETWORK));
+
+        verify(mWifiStateMachine).sendMessage(any(Message.class));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index a8278d3..cb1160c 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -614,7 +614,7 @@
      * Expected behavior: ONA handles scan results
      */
     @Test
-    public void wifiDisconnected_noConnectionCandidate_openNetworkNotificationScanResultsHandled() {
+    public void wifiDisconnected_noConnectionCandidate_openNetworkNotifierScanResultsHandled() {
         // no connection candidate selected
         when(mWifiNS.selectNetwork(anyObject(), anyObject(), anyObject(), anyBoolean(),
                 anyBoolean(), anyBoolean())).thenReturn(null);
@@ -643,12 +643,12 @@
      * Expected behavior: ONA clears pending notification and does not reset repeat delay.
      */
     @Test
-    public void wifiConnected_openNetworkNotificationClearsPendingNotification() {
+    public void wifiConnected_openNetworkNotifierClearsPendingNotification() {
         // Set WiFi to connected state
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_CONNECTED);
 
-        verify(mOpenNetworkNotifier).clearPendingNotification(false /* isRepeatDelayReset*/);
+        verify(mOpenNetworkNotifier).clearPendingNotification(false /* resetRepeatDelay*/);
     }
 
     /**
@@ -658,7 +658,7 @@
      * Expected behavior: ONA does not clear pending notification.
      */
     @Test
-    public void wifiDisconnected_openNetworkNotificationDoesNotClearPendingNotification() {
+    public void wifiDisconnected_openNetworkNotifierDoesNotClearPendingNotification() {
         // Set WiFi to disconnected state
         mWifiConnectivityManager.handleConnectionStateChanged(
                 WifiConnectivityManager.WIFI_STATE_DISCONNECTED);
@@ -667,22 +667,52 @@
     }
 
     /**
+     * When a Wi-Fi connection attempt ends, {@link OpenNetworkNotifier} handles the connection
+     * failure. A failure code that is not {@link WifiMetrics.ConnectionEvent#FAILURE_NONE}
+     * represents a connection failure.
+     *
+     * Expected behavior: ONA handles connection failure.
+     */
+    @Test
+    public void wifiConnectionEndsWithFailure_openNetworkNotifierHandlesConnectionFailure() {
+        mWifiConnectivityManager.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_CONNECT_NETWORK_FAILED);
+
+        verify(mOpenNetworkNotifier).handleConnectionFailure();
+    }
+
+    /**
+     * When a Wi-Fi connection attempt ends, {@link OpenNetworkNotifier} does not handle connection
+     * failure after a successful connection. {@link WifiMetrics.ConnectionEvent#FAILURE_NONE}
+     * represents a successful connection.
+     *
+     * Expected behavior: ONA does nothing.
+     */
+    @Test
+    public void wifiConnectionEndsWithSuccess_openNetworkNotifierDoesNotHandleConnectionFailure() {
+        mWifiConnectivityManager.handleConnectionAttemptEnded(
+                WifiMetrics.ConnectionEvent.FAILURE_NONE);
+
+        verify(mOpenNetworkNotifier, never()).handleConnectionFailure();
+    }
+
+    /**
      * When Wi-Fi is disabled, clear the pending notification and reset notification repeat delay.
      *
      * Expected behavior: clear pending notification and reset notification repeat delay
      * */
     @Test
-    public void openNetworkNotificationControllerToggledOnWifiStateChanges() {
+    public void openNetworkNotifierClearsPendingNotificationOnWifiDisabled() {
         mWifiConnectivityManager.setWifiEnabled(false);
 
-        verify(mOpenNetworkNotifier).clearPendingNotification(true /* isRepeatDelayReset */);
+        verify(mOpenNetworkNotifier).clearPendingNotification(true /* resetRepeatDelay */);
     }
 
     /**
      * Verify that the ONA controller tracks screen state changes.
      */
     @Test
-    public void openNetworkNotificationControllerTracksScreenStateChanges() {
+    public void openNetworkNotifierTracksScreenStateChanges() {
         mWifiConnectivityManager.handleScreenStateChanged(false);
 
         verify(mOpenNetworkNotifier).handleScreenStateChanged(false);