DO NOT MERGE WifiController: Add CMD_RESET_WIFI command

(cherry-pick from master)

Allow WifiController to safely restart the WiFi stack.  This will
initially be used by the WifiLastResortWatchdog.  The CMD_RESET_WIFI
message is only handled in the StaEnabledState where it would have been
attempting to connect.  The process of bringing the wifi back up is
handled through the use of the new CMD_RESET_WIFI_CONTINUE command.
Tests are also added to verify that the new CMD_RESET_WIFI command
is ignored in other states (explicitly tests Emergency mode and
AP enabled).

BUG: 27856267
Change-Id: I778ccd6f7d555f6ee6abb195c1c16c106c2e66b7
diff --git a/service/java/com/android/server/wifi/WifiController.java b/service/java/com/android/server/wifi/WifiController.java
index 07bb882..94310e9 100644
--- a/service/java/com/android/server/wifi/WifiController.java
+++ b/service/java/com/android/server/wifi/WifiController.java
@@ -109,22 +109,26 @@
 
     private static final int BASE = Protocol.BASE_WIFI_CONTROLLER;
 
-    static final int CMD_EMERGENCY_MODE_CHANGED       = BASE + 1;
-    static final int CMD_SCREEN_ON                    = BASE + 2;
-    static final int CMD_SCREEN_OFF                   = BASE + 3;
-    static final int CMD_BATTERY_CHANGED              = BASE + 4;
-    static final int CMD_DEVICE_IDLE                  = BASE + 5;
-    static final int CMD_LOCKS_CHANGED                = BASE + 6;
-    static final int CMD_SCAN_ALWAYS_MODE_CHANGED     = BASE + 7;
-    static final int CMD_WIFI_TOGGLED                 = BASE + 8;
-    static final int CMD_AIRPLANE_TOGGLED             = BASE + 9;
-    static final int CMD_SET_AP                       = BASE + 10;
-    static final int CMD_DEFERRED_TOGGLE              = BASE + 11;
-    static final int CMD_USER_PRESENT                 = BASE + 12;
-    static final int CMD_AP_START_FAILURE             = BASE + 13;
-    static final int CMD_EMERGENCY_CALL_STATE_CHANGED = BASE + 14;
-    static final int CMD_AP_STOPPED                   = BASE + 15;
-    static final int CMD_STA_START_FAILURE            = BASE + 16;
+    static final int CMD_EMERGENCY_MODE_CHANGED        = BASE + 1;
+    static final int CMD_SCREEN_ON                     = BASE + 2;
+    static final int CMD_SCREEN_OFF                    = BASE + 3;
+    static final int CMD_BATTERY_CHANGED               = BASE + 4;
+    static final int CMD_DEVICE_IDLE                   = BASE + 5;
+    static final int CMD_LOCKS_CHANGED                 = BASE + 6;
+    static final int CMD_SCAN_ALWAYS_MODE_CHANGED      = BASE + 7;
+    static final int CMD_WIFI_TOGGLED                  = BASE + 8;
+    static final int CMD_AIRPLANE_TOGGLED              = BASE + 9;
+    static final int CMD_SET_AP                        = BASE + 10;
+    static final int CMD_DEFERRED_TOGGLE               = BASE + 11;
+    static final int CMD_USER_PRESENT                  = BASE + 12;
+    static final int CMD_AP_START_FAILURE              = BASE + 13;
+    static final int CMD_EMERGENCY_CALL_STATE_CHANGED  = BASE + 14;
+    static final int CMD_AP_STOPPED                    = BASE + 15;
+    static final int CMD_STA_START_FAILURE             = BASE + 16;
+    // Command used to trigger a wifi stack restart when in active mode
+    static final int CMD_RESTART_WIFI                  = BASE + 17;
+    // Internal command used to complete wifi stack restart
+    private static final int CMD_RESTART_WIFI_CONTINUE = BASE + 18;
 
     private DefaultState mDefaultState = new DefaultState();
     private StaEnabledState mStaEnabledState = new StaEnabledState();
@@ -408,6 +412,8 @@
                 case CMD_AP_START_FAILURE:
                 case CMD_AP_STOPPED:
                 case CMD_STA_START_FAILURE:
+                case CMD_RESTART_WIFI:
+                case CMD_RESTART_WIFI_CONTINUE:
                     break;
                 case CMD_USER_PRESENT:
                     mFirstUserSignOnSeen = true;
@@ -483,6 +489,9 @@
                     log("DEFERRED_TOGGLE handled");
                     sendMessage((Message)(msg.obj));
                     break;
+                case CMD_RESTART_WIFI_CONTINUE:
+                    transitionTo(mDeviceActiveState);
+                    break;
                 default:
                     return NOT_HANDLED;
             }
@@ -824,6 +833,10 @@
                 }
                 mFirstUserSignOnSeen = true;
                 return HANDLED;
+            } else if (msg.what == CMD_RESTART_WIFI) {
+                deferMessage(obtainMessage(CMD_RESTART_WIFI_CONTINUE));
+                transitionTo(mApStaDisabledState);
+                return HANDLED;
             }
             return NOT_HANDLED;
         }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java b/service/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
index 70f1b5f..8f1b23e 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/WifiControllerTest.java
@@ -22,6 +22,7 @@
 import static com.android.server.wifi.WifiController.CMD_DEVICE_IDLE;
 import static com.android.server.wifi.WifiController.CMD_EMERGENCY_CALL_STATE_CHANGED;
 import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED;
+import static com.android.server.wifi.WifiController.CMD_RESTART_WIFI;
 import static com.android.server.wifi.WifiController.CMD_SET_AP;
 import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
 
@@ -303,4 +304,122 @@
         inOrder.verify(mWifiStateMachine).setDriverStart(true);
         assertEquals("FullLockHeldState", getCurrentState().getName());
     }
+
+    /**
+     * The command to trigger a WiFi reset should not trigger any action by WifiController if we
+     * are not in STA mode.
+     * WiFi is not in connect mode, so any calls to reset the wifi stack due to connection failures
+     * should be ignored.
+     * Create and start WifiController in ApStaDisabledState, send command to restart WiFi
+     * <p>
+     * Expected: WiFiController should not call WifiStateMachine.setSupplicantRunning(false)
+     */
+    @Test
+    public void testRestartWifiStackInApStaDisabledState() throws Exception {
+        // Start a new WifiController with wifi disabled
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        when(mSettingsStore.isWifiToggleEnabled()).thenReturn(false);
+        when(mSettingsStore.isScanAlwaysAvailable()).thenReturn(false);
+
+        when(mContext.getContentResolver()).thenReturn(mock(ContentResolver.class));
+
+        mWifiController = new WifiController(mContext, mWifiStateMachine,
+                mSettingsStore, mWifiLockManager, mLooper.getLooper(), mFacade);
+
+        mWifiController.start();
+        mLooper.dispatchAll();
+
+        reset(mWifiStateMachine);
+        assertEquals("ApStaDisabledState", getCurrentState().getName());
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        verifyZeroInteractions(mWifiStateMachine);
+    }
+
+    /**
+     * The command to trigger a WiFi reset should not trigger any action by WifiController if we
+     * are not in STA mode, even if scans are allowed.
+     * WiFi is not in connect mode, so any calls to reset the wifi stack due to connection failures
+     * should be ignored.
+     * Create and start WifiController in StaDisablediWithScanState, send command to restart WiFi
+     * <p>
+     * Expected: WiFiController should not call WifiStateMachine.setSupplicantRunning(false)
+     */
+    @Test
+    public void testRestartWifiStackInStaDisabledWithScanState() throws Exception {
+        reset(mWifiStateMachine);
+        assertEquals("StaDisabledWithScanState", getCurrentState().getName());
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        verifyZeroInteractions(mWifiStateMachine);
+    }
+
+    /**
+     * The command to trigger a WiFi reset should trigger a wifi reset in WifiStateMachine through
+     * the WifiStateMachine.setSupplicantRunning(false) call when in STA mode.
+     * WiFi is in connect mode, calls to reset the wifi stack due to connection failures
+     * should trigger a supplicant stop, and subsequently, a driver reload.
+     * Create and start WifiController in DeviceActiveState, send command to restart WiFi
+     * <p>
+     * Expected: WiFiController should call WifiStateMachine.setSupplicantRunning(false),
+     * WifiStateMachine should enter CONNECT_MODE and the wifi driver should be started.
+     */
+    @Test
+    public void testRestartWifiStackInStaEnabledState() throws Exception {
+        enableWifi();
+
+        reset(mWifiStateMachine);
+        assertEquals("DeviceActiveState", getCurrentState().getName());
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        InOrder inOrder = inOrder(mWifiStateMachine);
+        inOrder.verify(mWifiStateMachine).setSupplicantRunning(false);
+        inOrder.verify(mWifiStateMachine).setSupplicantRunning(true);
+        inOrder.verify(mWifiStateMachine).setOperationalMode(WifiStateMachine.CONNECT_MODE);
+        inOrder.verify(mWifiStateMachine).setDriverStart(true);
+        assertEquals("DeviceActiveState", getCurrentState().getName());
+    }
+
+    /**
+     * The command to trigger a WiFi reset should not trigger a reset when in ECM mode.
+     * Enable wifi and enter ECM state, send command to restart wifi.
+     * <p>
+     * Expected: The command to trigger a wifi reset should be ignored and we should remain in ECM
+     * mode.
+     */
+    @Test
+    public void testRestartWifiStackDoesNotExitECMMode() throws Exception {
+        enableWifi();
+        assertEquals("DeviceActiveState", getCurrentState().getName());
+        when(mFacade.getConfigWiFiDisableInECBM(mContext)).thenReturn(true);
+
+        mWifiController.sendMessage(CMD_EMERGENCY_CALL_STATE_CHANGED, 1);
+        mLooper.dispatchAll();
+        assertInEcm(true);
+
+        reset(mWifiStateMachine);
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        assertInEcm(true);
+        verifyZeroInteractions(mWifiStateMachine);
+    }
+
+    /**
+     * The command to trigger a WiFi reset should not trigger a reset when in AP mode.
+     * Enter AP mode, send command to restart wifi.
+     * <p>
+     * Expected: The command to trigger a wifi reset should be ignored and we should remain in AP
+     * mode.
+     */
+    @Test
+    public void testRestartWifiStackDoesNotExitAPMode() throws Exception {
+        mWifiController.obtainMessage(CMD_SET_AP, 1).sendToTarget();
+        mLooper.dispatchAll();
+        assertEquals("ApEnabledState", getCurrentState().getName());
+
+        reset(mWifiStateMachine);
+        mWifiController.sendMessage(CMD_RESTART_WIFI);
+        mLooper.dispatchAll();
+        verifyZeroInteractions(mWifiStateMachine);
+    }
 }