wifi: Fix SoftAp bridged mode idle shutdown timer

The shutdown timer is shared between two bridged AP instances.
It causes the timer doesn't reset if client connect/disconnect before
the timer expired.

Separate timer for each instance and maintain it separately.

Bug: 204097824
Test: atest -c FrameworksWifiTests
Test: Manual Test, test on single AP mode and bridged Ap mode to check
timer works normally.

Change-Id: I53632260cfc512471ab415a0c407d6fe692310df
diff --git a/service/java/com/android/server/wifi/SoftApManager.java b/service/java/com/android/server/wifi/SoftApManager.java
index 2e99de6..813f733 100644
--- a/service/java/com/android/server/wifi/SoftApManager.java
+++ b/service/java/com/android/server/wifi/SoftApManager.java
@@ -62,7 +62,6 @@
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -81,12 +80,7 @@
 
     @VisibleForTesting
     public static final String SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG = TAG
-            + " Soft AP Send Message Timeout";
-    @VisibleForTesting
-    public static final String SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG = TAG
-            + " Soft AP Send Message Bridged Mode Idle Timeout";
-
-
+            + " Soft AP Send Message Timeout on ";
     private final WifiContext mContext;
     private final FrameworkFacade mFrameworkFacade;
     private final WifiNative mWifiNative;
@@ -181,13 +175,23 @@
     @NonNull
     private Set<Integer> mSafeChannelFrequencyList = new HashSet<>();
 
-    @VisibleForTesting
-    public WakeupMessage mSoftApTimeoutMessage;
-    @VisibleForTesting
-    public WakeupMessage mSoftApBridgedModeIdleInstanceTimeoutMessage;
 
-    // Internal flag which is used to avoid the timer re-schedule.
-    private boolean mIsBridgedModeIdleInstanceTimerActive = false;
+    /**
+     * A map stores shutdown timeouts for each Soft Ap instance.
+     * There are three timeout messages now.
+     * 1. <mApInterfaceName, timeout> which uses to monitor whole Soft AP interface.
+     * It works on single AP mode and bridged AP mode.
+     *
+     * 2. <instance_lower_band, timeout> which is used to shutdown the AP when there are no
+     * connected devices. It is scheduled only in bridged mode to move dual mode AP to single
+     * mode AP in lower band.
+     *
+     * 3. <instance_higher_band, timeout> which is used to shutdown the AP when there are no
+     * connected devices. It is scheduled only in bridged mode to move dual mode AP to single
+     * mode AP in higher band.
+     */
+    @VisibleForTesting
+    public Map<String, WakeupMessage> mSoftApTimeoutMessageMap = new HashMap<>();
 
     /**
      * Listener for soft AP events.
@@ -979,37 +983,42 @@
         }
 
         private class StartedState extends State {
-            private void scheduleTimeoutMessages() {
-                // When SAP started, the mCurrentSoftApInfoMap is 0 because info does not update.
+            /**
+             * Schedule timeout message depends on Soft Ap instance
+             *
+             * @param changedInstance the schedule should change on specific instance only.
+             *                        If changedInstance is mApInterfaceName, it means that
+             *                        we need to reschedule all of timeout message.
+             */
+            private void rescheduleTimeoutMessages(@NonNull String changedInstance) {
                 // Don't trigger bridged mode shutdown timeout when only one active instance
                 // In Dual AP, one instance may already be closed due to LTE coexistence or DFS
                 // restrictions or due to inactivity. i.e. mCurrentSoftApInfoMap.size() is 1)
-                final int connectedClients = getConnectedClientList().size();
-                if (isBridgedMode() && mCurrentSoftApInfoMap.size() != 1) {
-                    if (mBridgedModeOpportunisticsShutdownTimeoutEnabled
-                            && (connectedClients == 0 || getIdleInstances().size() != 0)) {
-                        if (!mIsBridgedModeIdleInstanceTimerActive) {
-                            mSoftApBridgedModeIdleInstanceTimeoutMessage.schedule(SystemClock
-                                    .elapsedRealtime()
-                                    + mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis);
-                            mIsBridgedModeIdleInstanceTimerActive = true;
-                            Log.d(getTag(), "Bridged mode instance opportunistic timeout message"
-                                    + " scheduled, delay = "
-                                    + mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis);
+                if (isBridgedMode() &&  mCurrentSoftApInfoMap.size() == 2) {
+                    if (TextUtils.equals(mApInterfaceName, changedInstance)) {
+                        Set<String> instances = mCurrentSoftApInfoMap.keySet();
+                        String lowerFrequencyInstance = null;
+                        String HighestFrequencyInstance = getHighestFrequencyInstance(instances);
+                        // Schedule bridged mode timeout on all instances
+                        for (String instance : instances) {
+                            //  Make sure that we schedule the higher frequency instance first since
+                            //  the current shutdown design is shutdown higher band instance first
+                            //  when both of intstances are idle.
+                            if (TextUtils.equals(instance, HighestFrequencyInstance)) {
+                                rescheduleTimeoutMessageIfNeeded(instance);
+                            } else {
+                                lowerFrequencyInstance = instance;
+                            }
                         }
+                        // Make sure that we schedule the lower frequency instance later.
+                        rescheduleTimeoutMessageIfNeeded(lowerFrequencyInstance);
                     } else {
-                        cancelBridgedModeIdleInstanceTimeoutMessage();
+                        rescheduleTimeoutMessageIfNeeded(changedInstance);
                     }
                 }
-                if (!mTimeoutEnabled || connectedClients != 0) {
-                    cancelTimeoutMessage();
-                    return;
-                }
-                long timeout = getShutdownTimeoutMillis();
-                mSoftApTimeoutMessage.schedule(SystemClock.elapsedRealtime()
-                        + timeout);
-                Log.d(getTag(), "Timeout message scheduled, delay = "
-                        + timeout);
+
+                // Always evaluate timeout schedule on tetheringInterface
+                rescheduleTimeoutMessageIfNeeded(mApInterfaceName);
             }
 
             private String getHighestFrequencyInstance(Set<String> candidateInstances) {
@@ -1045,26 +1054,47 @@
                 }
             }
 
-            private Set<String> getIdleInstances() {
-                Set<String> idleInstances = new HashSet<String>();
-                for (String instance : mConnectedClientWithApInfoMap.keySet()) {
-                    if (mConnectedClientWithApInfoMap.getOrDefault(
-                            instance, Collections.emptyList()).size() == 0) {
-                        idleInstances.add(instance);
-                    }
+            /**
+             * Schedule the timeout message when timeout control is enabled and there is no client
+             * connect to the instance.
+             *
+             * @param instance The key of the {@code mSoftApTimeoutMessageMap},
+             *                 @see mSoftApTimeoutMessageMap for details.
+             */
+            private void rescheduleTimeoutMessageIfNeeded(String instance) {
+                final boolean isTetheringInterface =
+                        TextUtils.equals(mApInterfaceName, instance);
+                final boolean timeoutEnabled = isTetheringInterface
+                        ? mTimeoutEnabled : mBridgedModeOpportunisticsShutdownTimeoutEnabled;
+                final int clientNumber = isTetheringInterface
+                        ? getConnectedClientList().size()
+                        : mConnectedClientWithApInfoMap.get(instance).size();
+                final long timeoutValue = isTetheringInterface
+                        ? getShutdownTimeoutMillis()
+                        : mDefaultShutdownIdleInstanceInBridgedModeTimeoutMillis;
+                Log.d(getTag(), "rescheduleTimeoutMessageIfNeeded " + instance + ", timeoutEnabled="
+                        + timeoutEnabled + ", clientNumber=" + clientNumber);
+                if (!timeoutEnabled || clientNumber != 0) {
+                    cancelTimeoutMessage(instance);
+                    return;
                 }
-                return idleInstances;
+                scheduleTimeoutMessage(instance, timeoutValue);
             }
 
-            private void cancelTimeoutMessage() {
-                mSoftApTimeoutMessage.cancel();
-                Log.d(getTag(), "Timeout message canceled");
+            private void scheduleTimeoutMessage(String instance, long timeout) {
+                if (mSoftApTimeoutMessageMap.containsKey(instance)) {
+                    mSoftApTimeoutMessageMap.get(instance).schedule(
+                            SystemClock.elapsedRealtime() + timeout);
+                    Log.d(getTag(), "Timeout message scheduled, on " + instance + ", delay = "
+                            + timeout);
+                }
             }
 
-            private void cancelBridgedModeIdleInstanceTimeoutMessage() {
-                mSoftApBridgedModeIdleInstanceTimeoutMessage.cancel();
-                mIsBridgedModeIdleInstanceTimerActive = false;
-                Log.d(getTag(), "Bridged mode idle instance timeout message canceled");
+            private void cancelTimeoutMessage(String instance) {
+                if (mSoftApTimeoutMessageMap.containsKey(instance)) {
+                    mSoftApTimeoutMessageMap.get(instance).cancel();
+                    Log.d(getTag(), "Timeout message canceled on " + instance);
+                }
             }
 
             /**
@@ -1186,7 +1216,7 @@
                         mOriginalModeConfiguration.getTargetMode(),
                         mCurrentSoftApInfoMap.get(apInstanceIdentifier));
 
-                scheduleTimeoutMessages();
+                rescheduleTimeoutMessages(apInstanceIdentifier);
             }
 
             /**
@@ -1208,6 +1238,7 @@
                         boolean isClientConnected =
                                 mConnectedClientWithApInfoMap.get(changedInstance).size() > 0;
                         mCurrentSoftApInfoMap.remove(changedInstance);
+                        mSoftApTimeoutMessageMap.remove(changedInstance);
                         mConnectedClientWithApInfoMap.remove(changedInstance);
                         mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap,
                                 mConnectedClientWithApInfoMap, isBridgedMode());
@@ -1240,11 +1271,37 @@
                         isBridgedMode() && mCurrentSoftApInfoMap.size() == 0;
 
                 mCurrentSoftApInfoMap.put(changedInstance, new SoftApInfo(apInfo));
+
                 if (!waitForAnotherSoftApInfoInBridgedMode) {
                     mSoftApCallback.onConnectedClientsOrInfoChanged(mCurrentSoftApInfoMap,
                             mConnectedClientWithApInfoMap, isBridgedMode());
                 }
 
+                boolean isNeedToScheduleTimeoutMessage = false;
+                if (!mSoftApTimeoutMessageMap.containsKey(mApInterfaceName)) {
+                    // First info update, create WakeupMessage for mApInterfaceName.
+                    mSoftApTimeoutMessageMap.put(mApInterfaceName, new WakeupMessage(
+                            mContext, mStateMachine.getHandler(),
+                            SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + mApInterfaceName,
+                            SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT));
+                    isNeedToScheduleTimeoutMessage = true;
+                }
+
+                if (isBridgedMode()
+                        && !mSoftApTimeoutMessageMap.containsKey(changedInstance)) {
+                    mSoftApTimeoutMessageMap.put(changedInstance,
+                            new WakeupMessage(mContext, mStateMachine.getHandler(),
+                            SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + changedInstance,
+                            SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT_ON_ONE_INSTANCE,
+                            0, 0, changedInstance));
+                    isNeedToScheduleTimeoutMessage = true;
+                }
+
+                // Trigger schedule after mCurrentSoftApInfoMap is updated.
+                if (isNeedToScheduleTimeoutMessage) {
+                    rescheduleTimeoutMessages(mApInterfaceName);
+                }
+
                 // ignore invalid freq and softap disable case for metrics
                 if (apInfo.getFrequency() > 0
                         && apInfo.getBandwidth() != SoftApInfo.CHANNEL_WIDTH_INVALID) {
@@ -1295,13 +1352,6 @@
                 onUpChanged(mWifiNative.isInterfaceUp(mApInterfaceName));
 
                 Handler handler = mStateMachine.getHandler();
-                mSoftApTimeoutMessage = new WakeupMessage(mContext, handler,
-                        SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG,
-                        SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT);
-
-                mSoftApBridgedModeIdleInstanceTimeoutMessage = new WakeupMessage(mContext, handler,
-                        SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG,
-                        SoftApStateMachine.CMD_NO_ASSOCIATED_STATIONS_TIMEOUT_ON_ONE_INSTANCE);
                 if (SdkLevel.isAtLeastS()) {
                     mCoexManager.registerCoexListener(mCoexListener);
                 }
@@ -1310,7 +1360,6 @@
                 mConnectedClientWithApInfoMap.clear();
                 mPendingDisconnectClients.clear();
                 mEverReportMetricsForMaxClient = false;
-                scheduleTimeoutMessages();
             }
 
             @Override
@@ -1338,9 +1387,10 @@
                     }
                 }
                 mPendingDisconnectClients.clear();
-                cancelTimeoutMessage();
-                cancelBridgedModeIdleInstanceTimeoutMessage();
-
+                for (String key : mSoftApTimeoutMessageMap.keySet()) {
+                    cancelTimeoutMessage(key);
+                }
+                mSoftApTimeoutMessageMap.clear();
                 // Need this here since we are exiting |Started| state and won't handle any
                 // future CMD_INTERFACE_STATUS_CHANGED events after this point
                 mWifiMetrics.addSoftApUpChangedEvent(false,
@@ -1448,9 +1498,10 @@
                         quitNow();
                         break;
                     case CMD_NO_ASSOCIATED_STATIONS_TIMEOUT_ON_ONE_INSTANCE:
+                        String idleInstance = (String) message.obj;
                         if (!isBridgedMode() || mCurrentSoftApInfoMap.size() != 2) {
                             Log.wtf(getTag(), "Ignore Bridged Mode Timeout message received"
-                                    + " in single AP state. Dropping");
+                                    + " in single AP state. Dropping it from " + idleInstance);
                             break;
                         }
                         if (!mBridgedModeOpportunisticsShutdownTimeoutEnabled) {
@@ -1458,14 +1509,8 @@
                                     + " while timeout is disabled. Dropping.");
                             break;
                         }
-                        Set<String> idleInstances = getIdleInstances();
-                        if (idleInstances.size() == 0) {
-                            break;
-                        }
-                        Log.d(getTag(), "Instance idle timout, the number of the idle instances is "
-                                + idleInstances.size());
-                        removeIfaceInstanceFromBridgedApIface(
-                                getHighestFrequencyInstance(idleInstances));
+                        Log.d(getTag(), "Instance idle timout on " + idleInstance);
+                        removeIfaceInstanceFromBridgedApIface(idleInstance);
                         break;
                     case CMD_INTERFACE_DESTROYED:
                         Log.d(getTag(), "Interface was cleanly destroyed.");
@@ -1506,7 +1551,7 @@
                                 Log.d(getTag(), "Max Client changed, reset to record the metrics");
                                 mEverReportMetricsForMaxClient = false;
                             }
-                            boolean needRescheduleTimer =
+                            boolean needRescheduleTimeoutMessage =
                                     mCurrentSoftApConfiguration.getShutdownTimeoutMillis()
                                     != newConfig.getShutdownTimeoutMillis()
                                     || mTimeoutEnabled != newConfig.isAutoShutdownEnabled()
@@ -1515,10 +1560,11 @@
                                     .isBridgedModeOpportunisticShutdownEnabledInternal();
                             updateChangeableConfiguration(newConfig);
                             updateClientConnection();
-                            if (needRescheduleTimer) {
-                                cancelTimeoutMessage();
-                                cancelBridgedModeIdleInstanceTimeoutMessage();
-                                scheduleTimeoutMessages();
+                            if (needRescheduleTimeoutMessage) {
+                                for (String key : mSoftApTimeoutMessageMap.keySet()) {
+                                    cancelTimeoutMessage(key);
+                                }
+                                rescheduleTimeoutMessages(mApInterfaceName);
                                 // Update SoftApInfo
                                 for (SoftApInfo info : mCurrentSoftApInfoMap.values()) {
                                     SoftApInfo newInfo = new SoftApInfo(info);
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 cea870e..3a2fade 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/SoftApManagerTest.java
@@ -75,6 +75,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.util.WakeupMessage;
 import com.android.modules.utils.build.SdkLevel;
 import com.android.server.wifi.coex.CoexManager;
 import com.android.wifi.resources.R;
@@ -105,7 +106,8 @@
     private static final String TEST_PASSWORD = "TestPassword";
     private static final String TEST_COUNTRY_CODE = "TestCountry";
     private static final String TEST_INTERFACE_NAME = "testif0";
-    private static final String TEST_SECOND_INTERFACE_NAME = "testif1";
+    private static final String TEST_FIRST_INSTANCE_NAME = "testif1";
+    private static final String TEST_SECOND_INSTANCE_NAME = "testif2";
     private static final String OTHER_INTERFACE_NAME = "otherif";
     private static final long TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLIS = 600_000;
     private static final long TEST_DEFAULT_SHUTDOWN_IDLE_INSTANCE_IN_BRIDGED_MODE_TIMEOUT_MILLIS =
@@ -129,7 +131,7 @@
     private static final NativeWifiClient TEST_NATIVE_CLIENT_2 = new NativeWifiClient(
             TEST_CLIENT_MAC_ADDRESS_2);
     private static final WifiClient TEST_CONNECTED_CLIENT_ON_SECOND_IFACE =
-            new WifiClient(TEST_CLIENT_MAC_ADDRESS_ON_SECOND_IFACE, TEST_SECOND_INTERFACE_NAME);
+            new WifiClient(TEST_CLIENT_MAC_ADDRESS_ON_SECOND_IFACE, TEST_SECOND_INSTANCE_NAME);
     private static final int TEST_AP_FREQUENCY = 2412;
     private static final int TEST_AP_FREQUENCY_5G = 5220;
     private static final int TEST_AP_BANDWIDTH_FROM_IFACE_CALLBACK =
@@ -149,12 +151,15 @@
 
     private TestLooper mLooper;
     private TestAlarmManager mAlarmManager;
-    private SoftApInfo mTestSoftApInfo;
-    private SoftApInfo mTestSoftApInfoOnSecondInterface;
+    private SoftApInfo mTestSoftApInfo; // Use for single Ap mode test case
+    private SoftApInfo mTestSoftApInfoOnFirstInstance; // Use for briged Ap mode test case
+    private SoftApInfo mTestSoftApInfoOnSecondInstance; // Use for briged Ap mode test case
     private Map<String, SoftApInfo> mTestSoftApInfoMap = new HashMap<>();
     private Map<String, List<WifiClient>> mTestWifiClientsMap = new HashMap<>();
-    private List<WifiClient> mCurrentConnectedTestedClientListOnTestInterface = new ArrayList();
-    private List<WifiClient> mCurrentConnectedTestedClientListOnSecondInterface = new ArrayList();
+    private Map<String, List<WifiClient>> mTempConnectedClientListMap = new HashMap<>() {{
+            put(TEST_INTERFACE_NAME, new ArrayList());
+            put(TEST_FIRST_INSTANCE_NAME, new ArrayList());
+            put(TEST_SECOND_INSTANCE_NAME, new ArrayList()); }};
     private SoftApCapability mTestSoftApCapability;
     private List<ClientModeManager> mTestClientModeManagers = new ArrayList<>();
 
@@ -215,12 +220,7 @@
                 apIfaceInstance, mac, isConnected);
         if (mac == null || !updateTheTestMap) return;
         WifiClient client = new WifiClient(mac, apIfaceInstance);
-        List<WifiClient> targetList;
-        if (apIfaceInstance.equals(TEST_INTERFACE_NAME)) {
-            targetList = mCurrentConnectedTestedClientListOnTestInterface;
-        } else {
-            targetList = mCurrentConnectedTestedClientListOnSecondInterface;
-        }
+        List<WifiClient> targetList = mTempConnectedClientListMap.get(apIfaceInstance);
         if (isConnected) {
             targetList.add(client);
         } else {
@@ -229,6 +229,45 @@
         mTestWifiClientsMap.put(apIfaceInstance, targetList);
     }
 
+    private void mockSoftApInfoUpdateAndVerifyAfterSapStarted(
+            boolean isBridged, boolean isNeedToVerifyTimerScheduled) {
+        reset(mCallback);
+        if (!isBridged) {
+            mockApInfoChangedEvent(mTestSoftApInfo);
+            mLooper.dispatchAll();
+            verify(mCallback).onConnectedClientsOrInfoChanged(
+                    mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        } else {
+            // SoftApInfo updated
+            mockApInfoChangedEvent(mTestSoftApInfoOnFirstInstance);
+            mockApInfoChangedEvent(mTestSoftApInfoOnSecondInstance);
+            mLooper.dispatchAll();
+            verify(mCallback).onConnectedClientsOrInfoChanged(
+                    mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        }
+
+        if (isNeedToVerifyTimerScheduled) {
+            // Verify timer is scheduled
+            verify(mAlarmManager.getAlarmManager(), isBridged ? times(2) : times(1)).setExact(
+                    anyInt(), anyLong(),
+                    eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG
+                            + TEST_INTERFACE_NAME),
+                    any(), any());
+            if (isBridged) {
+                // Verify the bridged mode timer is scheduled
+                verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                        eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG
+                                  + TEST_FIRST_INSTANCE_NAME),
+                        any(), any());
+                verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                        eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG
+                                  + TEST_SECOND_INSTANCE_NAME),
+                        any(), any());
+            }
+        }
+    }
+
+
     /** Sets up test. */
     @Before
     public void setUp() throws Exception {
@@ -280,18 +319,22 @@
         when(mConcreteClientModeManager.syncRequestConnectionInfo())
                 .thenReturn(mPrimaryWifiInfo);
         when(mWifiNative.forceClientDisconnect(any(), any(), anyInt())).thenReturn(true);
+
+        // Init Test SoftAp infos
         mTestSoftApInfo = new SoftApInfo();
         mTestSoftApInfo.setFrequency(TEST_AP_FREQUENCY);
         mTestSoftApInfo.setBandwidth(TEST_AP_BANDWIDTH_IN_SOFTAPINFO);
         mTestSoftApInfo.setBssid(TEST_INTERFACE_MAC_ADDRESS);
         mTestSoftApInfo.setApInstanceIdentifier(TEST_INTERFACE_NAME);
         mTestSoftApInfo.setAutoShutdownTimeoutMillis(TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLIS);
-        mTestSoftApInfoOnSecondInterface = new SoftApInfo();
-        mTestSoftApInfoOnSecondInterface.setFrequency(TEST_AP_FREQUENCY_5G);
-        mTestSoftApInfoOnSecondInterface.setBandwidth(TEST_AP_BANDWIDTH_IN_SOFTAPINFO);
-        mTestSoftApInfoOnSecondInterface.setBssid(TEST_SECOND_INTERFACE_MAC_ADDRESS);
-        mTestSoftApInfoOnSecondInterface.setApInstanceIdentifier(TEST_SECOND_INTERFACE_NAME);
-        mTestSoftApInfoOnSecondInterface.setAutoShutdownTimeoutMillis(
+        mTestSoftApInfoOnFirstInstance = new SoftApInfo(mTestSoftApInfo);
+        mTestSoftApInfoOnFirstInstance.setApInstanceIdentifier(TEST_FIRST_INSTANCE_NAME);
+        mTestSoftApInfoOnSecondInstance = new SoftApInfo();
+        mTestSoftApInfoOnSecondInstance.setFrequency(TEST_AP_FREQUENCY_5G);
+        mTestSoftApInfoOnSecondInstance.setBandwidth(TEST_AP_BANDWIDTH_IN_SOFTAPINFO);
+        mTestSoftApInfoOnSecondInstance.setBssid(TEST_SECOND_INTERFACE_MAC_ADDRESS);
+        mTestSoftApInfoOnSecondInstance.setApInstanceIdentifier(TEST_SECOND_INSTANCE_NAME);
+        mTestSoftApInfoOnSecondInstance.setAutoShutdownTimeoutMillis(
                 TEST_DEFAULT_SHUTDOWN_TIMEOUT_MILLIS);
         // Default set up all features support.
         long testSoftApFeature = SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT
@@ -309,8 +352,7 @@
 
         mTestSoftApInfoMap.clear();
         mTestWifiClientsMap.clear();
-        mCurrentConnectedTestedClientListOnTestInterface.clear();
-        mCurrentConnectedTestedClientListOnSecondInterface.clear();
+        mTempConnectedClientListMap.forEach((key, value) -> value.clear());
     }
 
     private SoftApConfiguration createDefaultApConfig() {
@@ -934,9 +976,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
 
         verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
                 new ArrayList<>(mTestSoftApInfoMap.values()),
@@ -957,6 +997,7 @@
         SoftApInfo testSoftApInfo = new SoftApInfo(mTestSoftApInfo);
         testSoftApInfo.setFrequency(5220);
         testSoftApInfo.setBandwidth(SoftApInfo.CHANNEL_WIDTH_20MHZ_NOHT);
+
         mockApInfoChangedEvent(testSoftApInfo);
         mLooper.dispatchAll();
 
@@ -977,8 +1018,8 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
+
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
 
         verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
                 new ArrayList<>(mTestSoftApInfoMap.values()),
@@ -997,8 +1038,8 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
+
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
 
         verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
                 new ArrayList<>(mTestSoftApInfoMap.values()),
@@ -1016,12 +1057,8 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
 
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
 
         verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
                 new ArrayList<>(mTestSoftApInfoMap.values()),
@@ -1038,11 +1075,9 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
+
         verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
                 new ArrayList<>(mTestSoftApInfoMap.values()),
                 apConfig.getTargetMode(), false);
@@ -1083,11 +1118,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         order.verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
                 new ArrayList<>(mTestSoftApInfoMap.values()),
                 apConfig.getTargetMode(), false);
@@ -1109,11 +1140,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         reset(mCallback);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
@@ -1135,11 +1162,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
 
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
@@ -1176,10 +1199,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                         mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
@@ -1200,6 +1220,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         reset(mCallback);
         /* Invalid values should be ignored */
         mockClientConnectedEvent(null, true, TEST_INTERFACE_NAME, false);
@@ -1217,11 +1238,12 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
+
+        // Get timer for verification
+        WakeupMessage timerOnTestInterface =
+                mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME);
+
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
@@ -1229,7 +1251,7 @@
         order.verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
                 apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(timerOnTestInterface));
 
         mSoftApManager.stop();
         mLooper.dispatchAll();
@@ -1240,7 +1262,7 @@
         order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
                   mTestWifiClientsMap, false);
         // Verify timer is canceled after stop softap
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(timerOnTestInterface));
     }
 
     @Test
@@ -1259,8 +1281,8 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
+        reset(mCallback);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
 
@@ -1288,11 +1310,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
@@ -1334,11 +1352,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         reset(mCallback);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
@@ -1367,12 +1381,8 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         reset(mWifiMetrics);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
         reset(mCallback);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
         mLooper.dispatchAll();
@@ -1421,11 +1431,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         reset(mWifiMetrics);
         reset(mCallback);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, false);
@@ -1481,11 +1487,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
@@ -1494,7 +1496,8 @@
         verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
                 apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
 
         // Second client connect and max client set is 1.
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
@@ -1537,14 +1540,14 @@
                 .getInteger(R.integer
                 .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
 
-
-        // Verify timer is scheduled
-        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
 
         // The single AP should not start the bridged mode timer
         verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_FIRST_INSTANCE_NAME),
+                any(), any());
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_SECOND_INSTANCE_NAME),
                 any(), any());
     }
 
@@ -1560,18 +1563,17 @@
         startSoftApAndVerifyEnabled(apConfig);
 
         reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-
         SoftApInfo expectedInfo = new SoftApInfo(mTestSoftApInfo);
         expectedInfo.setAutoShutdownTimeoutMillis(50000);
-        mTestSoftApInfoMap.put(TEST_INTERFACE_NAME, expectedInfo);
+        mockApInfoChangedEvent(expectedInfo);
+        mLooper.dispatchAll();
         verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, false);
 
         // Verify timer is scheduled
         verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_INTERFACE_NAME),
+                any(), any());
     }
 
     @Test
@@ -1580,11 +1582,16 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
+
+        WakeupMessage timerOnTestInterface =
+                mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME);
+
         mSoftApManager.stop();
         mLooper.dispatchAll();
 
         // Verify timer is canceled
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(eq(timerOnTestInterface));
     }
 
     @Test
@@ -1593,11 +1600,15 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
+
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
+
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
         // Verify timer is canceled
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
     }
 
     @Test
@@ -1607,19 +1618,21 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         order.verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
                   mTestWifiClientsMap, false);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, false, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         // Verify timer is scheduled again
         verify(mAlarmManager.getAlarmManager(), times(2)).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_INTERFACE_NAME),
+                any(), any());
     }
 
     @Test
@@ -1628,9 +1641,13 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
+
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
+
         doNothing().when(mFakeSoftApNotifier)
                 .showSoftApShutdownTimeoutExpiredNotification();
-        mAlarmManager.dispatch(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG);
+        mAlarmManager.dispatch(
+                mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_INTERFACE_NAME);
         mLooper.dispatchAll();
 
         verify(mWifiNative).teardownInterface(TEST_INTERFACE_NAME);
@@ -1644,6 +1661,8 @@
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
 
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
+
         SoftApConfiguration newConfig = new SoftApConfiguration.Builder(mDefaultApConfig)
                 .setAutoShutdownEnabled(false)
                 .build();
@@ -1651,7 +1670,8 @@
         mLooper.dispatchAll();
 
         // Verify timer is canceled
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
     }
 
     @Test
@@ -1665,6 +1685,20 @@
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
 
+        reset(mCallback);
+        SoftApInfo expectedInfo = new SoftApInfo(mTestSoftApInfo);
+        // the timeout is 0 when shutdown is disable
+        expectedInfo.setAutoShutdownTimeoutMillis(0);
+        mTestSoftApInfoMap.put(TEST_INTERFACE_NAME, expectedInfo);
+        mockApInfoChangedEvent(expectedInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+
+        // Verify timer is not scheduled
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                any(), any(), any());
+
         SoftApConfiguration newConfig = new SoftApConfiguration.Builder(mDefaultApConfig)
                 .setAutoShutdownEnabled(true)
                 .build();
@@ -1673,7 +1707,8 @@
 
         // Verify timer is scheduled
         verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_INTERFACE_NAME),
+                any(), any());
     }
 
     @Test
@@ -1686,10 +1721,16 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-
+        reset(mCallback);
+        SoftApInfo expectedInfo = new SoftApInfo(mTestSoftApInfo);
+        expectedInfo.setAutoShutdownTimeoutMillis(0);
+        mockApInfoChangedEvent(expectedInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
         // Verify timer is not scheduled
-        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(), any(),
+                any(), any());
     }
 
     @Test
@@ -1703,6 +1744,15 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
+
+        reset(mCallback);
+        SoftApInfo expectedInfo = new SoftApInfo(mTestSoftApInfo);
+        expectedInfo.setAutoShutdownTimeoutMillis(0);
+        mockApInfoChangedEvent(expectedInfo);
+        mLooper.dispatchAll();
+        verify(mCallback).onConnectedClientsOrInfoChanged(
+                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+
         // add client
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
@@ -1711,7 +1761,8 @@
         mLooper.dispatchAll();
         // Verify timer is not scheduled
         verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_INTERFACE_NAME),
+                any(), any());
     }
 
     @Test
@@ -1837,11 +1888,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
         verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
@@ -1850,7 +1897,8 @@
         verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
                 apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
 
         reset(mWifiMetrics);
         // Second client connect and max client set is 1.
@@ -1875,9 +1923,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
                   mTestWifiClientsMap, false);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
@@ -1889,7 +1935,8 @@
         verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
                 apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
 
         // Second client connect and max client set is 1.
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
@@ -2033,11 +2080,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 noClientControlCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
@@ -2047,7 +2090,8 @@
         verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
                 apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
 
         // Second client connect and max client set is 1.
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
@@ -2166,19 +2210,12 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
 
-        // Verify timer is scheduled
-        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
         verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
                   mTestWifiClientsMap, false);
         verify(mWifiMetrics).updateSoftApConfiguration(configBuilder.build(),
                 WifiManager.IFACE_IP_MODE_TETHERED, false);
 
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
 
         reset(mCallback);
         // Trigger Configuration Change
@@ -2192,10 +2229,12 @@
         verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, false);
         // Verify timer is canceled at this point since timeout changed
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
         // Verify timer setup again
         verify(mAlarmManager.getAlarmManager(), times(2)).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_INTERFACE_NAME),
+                any(), any());
         verify(mWifiMetrics).updateSoftApConfiguration(configBuilder.build(),
                 WifiManager.IFACE_IP_MODE_TETHERED, false);
     }
@@ -2210,11 +2249,7 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
 
-        // Verify timer is scheduled
-        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
-
-        mLooper.dispatchAll();
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
 
         // Trigger Configuration Change
         configBuilder.setShutdownTimeoutMillis(500000);
@@ -2223,7 +2258,7 @@
         mLooper.dispatchAll();
         // Verify timer cancel will not apply since changed config need to apply via restart.
         verify(mAlarmManager.getAlarmManager(), never()).cancel(
-                eq(mSoftApManager.mSoftApTimeoutMessage));
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
     }
 
     @Test
@@ -2236,11 +2271,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
@@ -2250,7 +2281,8 @@
         verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
                 apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
 
         // Second client connect and max client set is 2.
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
@@ -2280,22 +2312,18 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 configBuilder.build(), mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
-
         verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
                   mTestWifiClientsMap, false);
 
         verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
                 apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
 
         // Second client connect and max client set is 1.
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, false);
@@ -2349,10 +2377,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED,
                 customizedBssidConfig, mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        order.verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         order.verify(mWifiMetrics).addSoftApChannelSwitchedEvent(
                 new ArrayList<>(mTestSoftApInfoMap.values()),
                 apConfig.getTargetMode(), false);
@@ -2405,12 +2430,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(mTestSoftApInfoMap,
-                  mTestWifiClientsMap, false);
-
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
 
@@ -2420,7 +2440,8 @@
         verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
                 apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
 
         reset(mWifiMetrics);
         // Second client connect and max client set is 1.
@@ -2456,11 +2477,7 @@
                 new SoftApModeConfiguration(WifiManager.IFACE_IP_MODE_TETHERED, null,
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
-        reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, false);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(false, true);
         reset(mCallback);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
         mLooper.dispatchAll();
@@ -2471,7 +2488,8 @@
         verify(mWifiMetrics).addSoftApNumAssociatedStationsChangedEvent(1, 1,
                 apConfig.getTargetMode(), mTestSoftApInfo);
         // Verify timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
 
         reset(mWifiMetrics);
         // Second client connect and max client set is 1.
@@ -2519,12 +2537,12 @@
                 mTestSoftApCapability);
         startSoftApAndVerifyEnabled(apConfig);
         reset(mCallback);
-        mockApInfoChangedEvent(mTestSoftApInfo);
+        mockApInfoChangedEvent(mTestSoftApInfoOnFirstInstance);
         mLooper.dispatchAll();
         verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, false);
 
-        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
+        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInstance);
         mLooper.dispatchAll();
         verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, false);
@@ -2549,42 +2567,24 @@
                 .getInteger(R.integer
                 .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
 
-
-        // Verify timer is scheduled
-        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
-
-        // Verify the bridged mode timer is scheduled
-        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
-                any(), any());
-
         // SoftApInfo updated
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
-        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
-        mLooper.dispatchAll();
-        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(true /* bridged mode*/, true);
         // Trigger the alarm
-        mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage.onAlarm();
+        mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_SECOND_INSTANCE_NAME).onAlarm();
         mLooper.dispatchAll();
         // Verify the remove correct iface and instance
         verify(mWifiNative).removeIfaceInstanceFromBridgedApIface(eq(TEST_INTERFACE_NAME),
-                eq(TEST_SECOND_INTERFACE_NAME));
+                eq(TEST_SECOND_INSTANCE_NAME));
         mLooper.dispatchAll();
         mTestSoftApInfoMap.clear();
         mTestWifiClientsMap.clear();
 
-        mTestSoftApInfoMap.put(mTestSoftApInfo.getApInstanceIdentifier(), mTestSoftApInfo);
-        mTestWifiClientsMap.put(mTestSoftApInfo.getApInstanceIdentifier(),
+        mTestSoftApInfoMap.put(mTestSoftApInfoOnFirstInstance.getApInstanceIdentifier(),
+                mTestSoftApInfoOnFirstInstance);
+        mTestWifiClientsMap.put(mTestSoftApInfoOnFirstInstance.getApInstanceIdentifier(),
                 new ArrayList<WifiClient>());
 
-        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(
+        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, true);
     }
 
@@ -2607,100 +2607,94 @@
                 .getInteger(R.integer
                 .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
 
-
-        // Verify timer is scheduled
-        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
-
-        // Verify idle timer in bridged mode is scheduled
-        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
-                any(), any());
-
         // SoftApInfo updated
-        mockApInfoChangedEvent(mTestSoftApInfo);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(true /* bridged mode*/, true);
+
+        // One Client connected
+        reset(mCallback);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_FIRST_INSTANCE_NAME, true);
         mLooper.dispatchAll();
         verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
-        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
-        mLooper.dispatchAll();
-        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
-
-        // One Client connected
-        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, true, TEST_INTERFACE_NAME, true);
-        mLooper.dispatchAll();
-        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-        // Verify original timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager()).cancel(eq(mSoftApManager.mSoftApTimeoutMessage));
+        // Verify whole SAP timer is canceled at this point
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_INTERFACE_NAME)));
+        // Verify correct instance timer is canceled at this point
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_FIRST_INSTANCE_NAME)));
         // Verify idle timer is NOT canceled at this point
-        verify(mAlarmManager.getAlarmManager(), never())
-                .cancel(eq(mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager(), never()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_SECOND_INSTANCE_NAME)));
 
         // Second client connected to same interface
-        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_INTERFACE_NAME, true);
+        reset(mCallback);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_FIRST_INSTANCE_NAME, true);
         mLooper.dispatchAll();
-        verify(mCallback, times(4)).onConnectedClientsOrInfoChanged(
+        verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, true);
         // Verify idle timer is NOT canceled at this point
         verify(mAlarmManager.getAlarmManager(), never())
-                .cancel(eq(mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage));
+                .cancel(eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_SECOND_INSTANCE_NAME)));
 
         // Second client disconnected from the current interface and connected to another one
-        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, false, TEST_INTERFACE_NAME, true);
+        reset(mCallback);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, false, TEST_FIRST_INSTANCE_NAME, true);
         mLooper.dispatchAll();
-        verify(mCallback, times(5)).onConnectedClientsOrInfoChanged(
+        verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, true);
-        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_SECOND_INTERFACE_NAME, true);
+        reset(mCallback);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, true, TEST_SECOND_INSTANCE_NAME, true);
         mLooper.dispatchAll();
-        verify(mCallback, times(6)).onConnectedClientsOrInfoChanged(
+        verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, true);
         // Verify idle timer is canceled at this point
-        verify(mAlarmManager.getAlarmManager())
-                .cancel(eq(mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage));
+        verify(mAlarmManager.getAlarmManager()).cancel(
+                eq(mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_SECOND_INSTANCE_NAME)));
         // Second client disconnect
+        reset(mCallback);
         mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS_2, false,
-                TEST_SECOND_INTERFACE_NAME, true);
+                TEST_SECOND_INSTANCE_NAME, true);
         mLooper.dispatchAll();
-        verify(mCallback, times(7)).onConnectedClientsOrInfoChanged(
+        verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, true);
         // Verify idle timer in bridged mode is scheduled again
         verify(mAlarmManager.getAlarmManager(), times(2)).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_SECOND_INSTANCE_NAME),
                 any(), any());
         // Trigger the alarm
-        mSoftApManager.mSoftApBridgedModeIdleInstanceTimeoutMessage.onAlarm();
+        reset(mCallback);
+        mSoftApManager.mSoftApTimeoutMessageMap.get(TEST_SECOND_INSTANCE_NAME).onAlarm();
         mLooper.dispatchAll();
         // Verify the remove correct iface and instance
         verify(mWifiNative).removeIfaceInstanceFromBridgedApIface(eq(TEST_INTERFACE_NAME),
-                eq(TEST_SECOND_INTERFACE_NAME));
+                eq(TEST_SECOND_INSTANCE_NAME));
 
         mTestSoftApInfoMap.clear();
         mTestWifiClientsMap.clear();
-        WifiClient client = new WifiClient(TEST_CLIENT_MAC_ADDRESS, TEST_INTERFACE_NAME);
+        WifiClient client = new WifiClient(TEST_CLIENT_MAC_ADDRESS, TEST_FIRST_INSTANCE_NAME);
         List<WifiClient> targetList = new ArrayList();
         targetList.add(client);
 
-        mTestSoftApInfoMap.put(mTestSoftApInfo.getApInstanceIdentifier(), mTestSoftApInfo);
-        mTestWifiClientsMap.put(mTestSoftApInfo.getApInstanceIdentifier(), targetList);
+        mTestSoftApInfoMap.put(mTestSoftApInfoOnFirstInstance.getApInstanceIdentifier(),
+                mTestSoftApInfoOnFirstInstance);
+        mTestWifiClientsMap.put(mTestSoftApInfoOnFirstInstance.getApInstanceIdentifier(),
+                targetList);
         mLooper.dispatchAll();
-        verify(mCallback, times(8)).onConnectedClientsOrInfoChanged(
+        verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, true);
 
         // Force all client disconnected
         // reset the alarm mock
         reset(mAlarmManager.getAlarmManager());
-        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, false, TEST_INTERFACE_NAME, true);
+        mockClientConnectedEvent(TEST_CLIENT_MAC_ADDRESS, false, TEST_FIRST_INSTANCE_NAME, true);
         mLooper.dispatchAll();
         // Verify timer is scheduled
         verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_INTERFACE_NAME),
+                any(), any());
         // The single AP should not start the bridged mode timer.
         verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_FIRST_INSTANCE_NAME),
                 any(), any());
     }
 
@@ -2725,14 +2719,19 @@
                 .getInteger(R.integer
                 .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
 
-
-        // Verify timer is scheduled
-        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
-
-        // Verify the bridged mode timer is scheduled
+        // SoftApInfo updated
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(true /* bridged mode*/,
+                false /* no verify timer*/);
+        verify(mAlarmManager.getAlarmManager(), times(2)).setExact(
+                anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_INTERFACE_NAME),
+                any(), any());
+        // Verify the bridged mode timer is NOT scheduled
         verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_FIRST_INSTANCE_NAME),
+                any(), any());
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_SECOND_INSTANCE_NAME),
                 any(), any());
     }
 
@@ -2757,21 +2756,30 @@
                 .getInteger(R.integer
                 .config_wifiFrameworkSoftApShutDownIdleInstanceInBridgedModeTimeoutMillisecond);
 
-
-        // Verify timer is scheduled
-        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG), any(), any());
-
-        // Verify the bridged mode timer is scheduled
-        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+        // SoftApInfo updated
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(true /* bridged mode*/,
+                false /* no verify timer*/);
+        verify(mAlarmManager.getAlarmManager(), times(2)).setExact(
+                anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_INTERFACE_NAME),
                 any(), any());
+        // Verify the bridged mode timer is NOT scheduled
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_FIRST_INSTANCE_NAME),
+                any(), any());
+        verify(mAlarmManager.getAlarmManager(), never()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_SECOND_INSTANCE_NAME),
+                any(), any());
+
         configBuilder.setBridgedModeOpportunisticShutdownEnabled(true);
         mSoftApManager.updateConfiguration(configBuilder.build());
         mLooper.dispatchAll();
         // Verify the bridged mode timer is scheduled
         verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
-                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_IDLE_IN_BRIDGED_MODE_TIMEOUT_TAG),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_FIRST_INSTANCE_NAME),
+                any(), any());
+        verify(mAlarmManager.getAlarmManager()).setExact(anyInt(), anyLong(),
+                eq(mSoftApManager.SOFT_AP_SEND_MESSAGE_TIMEOUT_TAG + TEST_SECOND_INSTANCE_NAME),
                 any(), any());
     }
 
@@ -2928,15 +2936,7 @@
         startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
 
         // SoftApInfo updated
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
-        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
-        mLooper.dispatchAll();
-        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(true /* bridged mode*/, true);
 
         // Test with soft unsafe channels
         // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe.
@@ -2972,15 +2972,7 @@
         startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
 
         // SoftApInfo updated
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
-        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
-        mLooper.dispatchAll();
-        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(true /* bridged mode*/, true);
 
         // Test with hard unsafe channels
         when(mCoexManager.getCoexRestrictions()).thenReturn(WifiManager.COEX_RESTRICTION_SOFTAP);
@@ -2990,21 +2982,23 @@
                 new CoexUnsafeChannel(WifiScanner.WIFI_BAND_5_GHZ, 149)
         ));
 
+        reset(mCallback);
         // Trigger coex unsafe channel changed
         mCoexListenerCaptor.getValue().onCoexUnsafeChannelsChanged();
         mLooper.dispatchAll();
         // Verify the remove correct iface and instance
         verify(mWifiNative).removeIfaceInstanceFromBridgedApIface(eq(TEST_INTERFACE_NAME),
-                eq(TEST_SECOND_INTERFACE_NAME));
+                eq(TEST_SECOND_INSTANCE_NAME));
         mLooper.dispatchAll();
         mTestSoftApInfoMap.clear();
         mTestWifiClientsMap.clear();
 
-        mTestSoftApInfoMap.put(mTestSoftApInfo.getApInstanceIdentifier(), mTestSoftApInfo);
-        mTestWifiClientsMap.put(mTestSoftApInfo.getApInstanceIdentifier(),
+        mTestSoftApInfoMap.put(mTestSoftApInfoOnFirstInstance.getApInstanceIdentifier(),
+                mTestSoftApInfoOnFirstInstance);
+        mTestWifiClientsMap.put(mTestSoftApInfoOnFirstInstance.getApInstanceIdentifier(),
                 new ArrayList<WifiClient>());
 
-        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(
+        verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, true);
     }
 
@@ -3024,18 +3018,8 @@
                 WifiManager.IFACE_IP_MODE_TETHERED, configBuilder.build(),
                 testCapability);
         startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
-
         // SoftApInfo updated
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
-        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
-        mLooper.dispatchAll();
-        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(true /* bridged mode*/, true);
 
         // TEST_SUPPORTED_5G_CHANNELS = 36, 149, only mark 36 is unsafe.
         when(mCoexManager.getCoexUnsafeChannels()).thenReturn(Arrays.asList(
@@ -3065,15 +3049,7 @@
         startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
 
         // SoftApInfo updated
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
-        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
-        mLooper.dispatchAll();
-        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(true /* bridged mode*/, true);
 
         // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe. Let Wifi connect to 5180 (CH36)
         when(mPrimaryWifiInfo.getFrequency()).thenReturn(5180);
@@ -3100,34 +3076,28 @@
         startSoftApAndVerifyEnabled(apConfig, TEST_COUNTRY_CODE, configBuilder.build());
 
         // SoftApInfo updated
-        mockApInfoChangedEvent(mTestSoftApInfo);
-        mLooper.dispatchAll();
-        verify(mCallback).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
-
-        mockApInfoChangedEvent(mTestSoftApInfoOnSecondInterface);
-        mLooper.dispatchAll();
-        verify(mCallback, times(2)).onConnectedClientsOrInfoChanged(
-                mTestSoftApInfoMap, mTestWifiClientsMap, true);
+        mockSoftApInfoUpdateAndVerifyAfterSapStarted(true /* bridged mode*/, true);
 
         // TEST_SUPPORTED_5G_CHANNELS = 36, 149, mark to unsafe. Let Wifi connect to 5945 (6G)
         when(mPrimaryWifiInfo.getFrequency()).thenReturn(5945);
 
+        reset(mCallback);
         // Trigger wifi connected
         mCmiListenerCaptor.getValue().onL2Connected(mConcreteClientModeManager);
         mLooper.dispatchAll();
         // Verify the remove correct iface and instance
         verify(mWifiNative).removeIfaceInstanceFromBridgedApIface(eq(TEST_INTERFACE_NAME),
-                eq(TEST_SECOND_INTERFACE_NAME));
+                eq(TEST_SECOND_INSTANCE_NAME));
         mLooper.dispatchAll();
         mTestSoftApInfoMap.clear();
         mTestWifiClientsMap.clear();
 
-        mTestSoftApInfoMap.put(mTestSoftApInfo.getApInstanceIdentifier(), mTestSoftApInfo);
-        mTestWifiClientsMap.put(mTestSoftApInfo.getApInstanceIdentifier(),
+        mTestSoftApInfoMap.put(mTestSoftApInfoOnFirstInstance.getApInstanceIdentifier(),
+                mTestSoftApInfoOnFirstInstance);
+        mTestWifiClientsMap.put(mTestSoftApInfoOnFirstInstance.getApInstanceIdentifier(),
                 new ArrayList<WifiClient>());
 
-        verify(mCallback, times(3)).onConnectedClientsOrInfoChanged(
+        verify(mCallback).onConnectedClientsOrInfoChanged(
                 mTestSoftApInfoMap, mTestWifiClientsMap, true);
     }