ActiveModeWarden: Defer APM toggle if handling a previous toggle

Back to back mode toggles (wifi, softap) for regular modes are handled by
the respective ActiveModeManager (For ex: ClientModeManager handles the
wifi toggle debounce while IMS dereg is ongoing). However, for back to
back airplane mode toggle handling, the ActiveModeWarden needs to know
if there is an ongoing stop to defer processing of toggle on.

Bug: 157711806
Test: atest com.android.server.wifi
Test: Sent the patch to OEM for verification.
Change-Id: I9595be86d823c7afc5b019efab8d5ffbb834a90b
Merged-In: I9595be86d823c7afc5b019efab8d5ffbb834a90b
diff --git a/service/java/com/android/server/wifi/ActiveModeManager.java b/service/java/com/android/server/wifi/ActiveModeManager.java
index aa0bf9f..0983e9b 100644
--- a/service/java/com/android/server/wifi/ActiveModeManager.java
+++ b/service/java/com/android/server/wifi/ActiveModeManager.java
@@ -59,6 +59,11 @@
      */
     void stop();
 
+    /**
+     * Method used to indicate if the mode manager is still stopping.
+     */
+    boolean isStopping();
+
     /** Roles assigned to each mode manager. */
     int ROLE_UNSPECIFIED = -1;
     // SoftApManager - Tethering, will respond to public APIs.
diff --git a/service/java/com/android/server/wifi/ActiveModeWarden.java b/service/java/com/android/server/wifi/ActiveModeWarden.java
index 0333458..e0a0a3d 100644
--- a/service/java/com/android/server/wifi/ActiveModeWarden.java
+++ b/service/java/com/android/server/wifi/ActiveModeWarden.java
@@ -282,6 +282,16 @@
     }
 
     /**
+     * @return true if any mode manager is stopping
+     */
+    private boolean hasAnyModeManagerStopping() {
+        for (ActiveModeManager manager : mActiveModeManagers) {
+            if (manager.isStopping()) return true;
+        }
+        return false;
+    }
+
+    /**
      * @return true if all the client mode managers are in scan only role,
      * false if there are no client mode managers present or if any of them are not in scan only
      * role.
@@ -886,8 +896,16 @@
                         if (mSettingsStore.isAirplaneModeOn()) {
                             return NOT_HANDLED;
                         } else {
-                            // when airplane mode is toggled off, but wifi is on, we can keep it on
-                            log("airplane mode toggled - and airplane mode is off. return handled");
+                            if (hasAnyModeManagerStopping()) {
+                                // previous airplane mode toggle on is being processed, defer the
+                                // message toggle off until previous processing is completed.
+                                deferMessage(msg);
+                            } else {
+                                // when airplane mode is toggled off, but wifi is on, we can keep it
+                                // on
+                                log("airplane mode toggled - and airplane mode is off. return "
+                                        + "handled");
+                            }
                             return HANDLED;
                         }
                     case CMD_AP_STOPPED:
diff --git a/service/java/com/android/server/wifi/ClientModeManager.java b/service/java/com/android/server/wifi/ClientModeManager.java
index 61df519..abb6f62 100644
--- a/service/java/com/android/server/wifi/ClientModeManager.java
+++ b/service/java/com/android/server/wifi/ClientModeManager.java
@@ -76,9 +76,9 @@
 
     private String mClientInterfaceName;
     private boolean mIfaceIsUp = false;
-    private @Role int mRole = ROLE_UNSPECIFIED;
     private DeferStopHandler mDeferStopHandler;
-    private int mTargetRole = ROLE_UNSPECIFIED;
+    private @Role int mRole = ROLE_UNSPECIFIED;
+    private @Role int mTargetRole = ROLE_UNSPECIFIED;
     private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     ClientModeManager(Context context, @NonNull Looper looper, Clock clock, WifiNative wifiNative,
@@ -122,6 +122,11 @@
         mDeferStopHandler.start(getWifiOffDeferringTimeMs());
     }
 
+    @Override
+    public boolean isStopping() {
+        return mTargetRole == ROLE_UNSPECIFIED && mRole != ROLE_UNSPECIFIED;
+    }
+
     private class DeferStopHandler extends WifiHandler {
         private boolean mIsDeferring = false;
         private ImsMmTelManager mImsMmTelManager = null;
diff --git a/service/java/com/android/server/wifi/DefaultModeManager.java b/service/java/com/android/server/wifi/DefaultModeManager.java
index cda4978..00b2117 100644
--- a/service/java/com/android/server/wifi/DefaultModeManager.java
+++ b/service/java/com/android/server/wifi/DefaultModeManager.java
@@ -45,6 +45,11 @@
     @Override
     public void stop() { };
 
+    @Override
+    public boolean isStopping() {
+        return false;
+    }
+
     /**
      * No role specified in default mode.
      */
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index 3bf12ca..f972047 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -122,6 +122,7 @@
     private BaseWifiDiagnostics mWifiDiagnostics;
 
     private @Role int mRole = ROLE_UNSPECIFIED;
+    private @Role int mTargetRole = ROLE_UNSPECIFIED;
 
     private boolean mEverReportMetricsForMaxClient = false;
 
@@ -219,6 +220,7 @@
     @Override
     public void stop() {
         Log.d(TAG, " currentstate: " + getCurrentStateName());
+        mTargetRole = ROLE_UNSPECIFIED;
         if (mApInterfaceName != null) {
             if (mIfaceIsUp) {
                 updateApState(WifiManager.WIFI_AP_STATE_DISABLING,
@@ -232,6 +234,11 @@
     }
 
     @Override
+    public boolean isStopping() {
+        return mTargetRole == ROLE_UNSPECIFIED && mRole != ROLE_UNSPECIFIED;
+    }
+
+    @Override
     public @Role int getRole() {
         return mRole;
     }
@@ -241,6 +248,7 @@
         // softap does not allow in-place switching of roles.
         Preconditions.checkState(mRole == ROLE_UNSPECIFIED);
         Preconditions.checkState(SOFTAP_ROLES.contains(role));
+        mTargetRole = role;
         mRole = role;
     }
 
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ActiveModeWardenTest.java b/service/tests/wifitests/src/com/android/server/wifi/ActiveModeWardenTest.java
index db7b4e2..ceaf76d 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ActiveModeWardenTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ActiveModeWardenTest.java
@@ -2172,4 +2172,112 @@
         when(mWifiNative.isStaApConcurrencySupported()).thenReturn(true);
         assertTrue(mActiveModeWarden.isStaApConcurrencySupported());
     }
+
+    @Test
+    public void airplaneModeToggleOnDisablesWifi() throws Exception {
+        enterClientModeActiveState();
+        assertInEnabledState();
+
+        assertWifiShutDown(() -> {
+            when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
+            mActiveModeWarden.airplaneModeToggled();
+            mLooper.dispatchAll();
+        });
+
+        mClientListener.onStopped();
+        mLooper.dispatchAll();
+        assertInDisabledState();
+    }
+
+    @Test
+    public void airplaneModeToggleOnDisablesSoftAp() throws Exception {
+        enterSoftApActiveMode();
+        assertInEnabledState();
+
+        assertWifiShutDown(() -> {
+            when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
+            mActiveModeWarden.airplaneModeToggled();
+            mLooper.dispatchAll();
+        });
+
+        mSoftApListener.onStopped();
+        mLooper.dispatchAll();
+        assertInDisabledState();
+    }
+
+    @Test
+    public void airplaneModeToggleOffIsDeferredWhileProcessingToggleOnWithOneModeManager()
+            throws Exception {
+        enterClientModeActiveState();
+        assertInEnabledState();
+
+        // APM toggle on
+        assertWifiShutDown(() -> {
+            when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
+            mActiveModeWarden.airplaneModeToggled();
+            mLooper.dispatchAll();
+        });
+
+
+        // APM toggle off before the stop is complete.
+        assertInEnabledState();
+        when(mClientModeManager.isStopping()).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        mActiveModeWarden.airplaneModeToggled();
+        mLooper.dispatchAll();
+
+        mClientListener.onStopped();
+        mLooper.dispatchAll();
+
+        verify(mClientModeManager, times(2)).start();
+        verify(mClientModeManager, times(2)).setRole(ROLE_CLIENT_PRIMARY);
+
+        mClientListener.onStarted();
+        mLooper.dispatchAll();
+
+        // We should be back to enabled state.
+        assertInEnabledState();
+    }
+
+    @Test
+    public void airplaneModeToggleOffIsDeferredWhileProcessingToggleOnWithTwoModeManager()
+            throws Exception {
+        enterClientModeActiveState();
+        enterSoftApActiveMode();
+        assertInEnabledState();
+
+        // APM toggle on
+        assertWifiShutDown(() -> {
+            when(mSettingsStore.isAirplaneModeOn()).thenReturn(true);
+            mActiveModeWarden.airplaneModeToggled();
+            mLooper.dispatchAll();
+        });
+
+
+        // APM toggle off before the stop is complete.
+        assertInEnabledState();
+        when(mClientModeManager.isStopping()).thenReturn(true);
+        when(mSoftApManager.isStopping()).thenReturn(true);
+        when(mSettingsStore.isAirplaneModeOn()).thenReturn(false);
+        mActiveModeWarden.airplaneModeToggled();
+        mLooper.dispatchAll();
+
+        // AP stopped, should not process APM toggle.
+        mSoftApListener.onStopped();
+        mLooper.dispatchAll();
+        verify(mClientModeManager, times(1)).start();
+        verify(mClientModeManager, times(1)).setRole(ROLE_CLIENT_PRIMARY);
+
+        // STA also stopped, should process APM toggle.
+        mClientListener.onStopped();
+        mLooper.dispatchAll();
+        verify(mClientModeManager, times(2)).start();
+        verify(mClientModeManager, times(2)).setRole(ROLE_CLIENT_PRIMARY);
+
+        mClientListener.onStarted();
+        mLooper.dispatchAll();
+
+        // We should be back to enabled state.
+        assertInEnabledState();
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java
index b733ec0..1102d1f 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ClientModeManagerTest.java
@@ -28,6 +28,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -469,8 +470,10 @@
         reset(mContext, mListener);
         setUpSystemServiceForContext();
         mClientModeManager.stop();
+        assertTrue(mClientModeManager.isStopping());
         mLooper.dispatchAll();
         verify(mListener).onStopped();
+        assertFalse(mClientModeManager.isStopping());
 
         verify(mImsMmTelManager, never()).registerImsRegistrationCallback(any(), any());
         verify(mImsMmTelManager, never()).unregisterImsRegistrationCallback(any());
diff --git a/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java b/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
index ca7bc62..99cd2db 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -36,6 +36,8 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
@@ -676,6 +678,7 @@
         InOrder order = inOrder(mCallback, mListener, mContext);
 
         mSoftApManager.stop();
+        assertTrue(mSoftApManager.isStopping());
         mLooper.dispatchAll();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -694,6 +697,8 @@
         checkApStateChangedBroadcast(intentCaptor.getValue(), WIFI_AP_STATE_DISABLED,
                 WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
                 softApModeConfig.getTargetMode());
+        order.verify(mListener).onStopped();
+        assertFalse(mSoftApManager.isStopping());
     }
 
     /**
@@ -729,6 +734,7 @@
                 WIFI_AP_STATE_DISABLING, HOTSPOT_NO_ERROR, TEST_INTERFACE_NAME,
                 softApModeConfig.getTargetMode());
         order.verify(mListener).onStopped();
+        assertFalse(mSoftApManager.isStopping());
     }
 
     /**
@@ -1784,6 +1790,7 @@
 
 
         mSoftApManager.start();
+        mSoftApManager.setRole(ActiveModeManager.ROLE_SOFTAP_TETHERED);
         mLooper.dispatchAll();
         verify(mFakeSoftApNotifier).dismissSoftApShutDownTimeoutExpiredNotification();
         order.verify(mWifiNative).setupInterfaceForSoftApMode(