Snap for 7513903 from 5e427a411e73e011ffeb649f3f6ead6e7d5c74d9 to sc-v2-release

Change-Id: Ieebb2802963aa8a948d2b30ca8c1e5850027cd82
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 9b95dac..2c1fd29 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -1069,11 +1069,13 @@
             throw new AssertionError("IP address array not valid IPv4 address!");
         }
 
-        final long ageMs = (now - value.lastUsed) / 1_000_000;
-        return String.format("[%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d [%s] %dms",
-                key.dstMac, key.iif, getIfName(key.iif), src4, key.srcPort,
+        final String protoStr = (key.l4proto == OsConstants.IPPROTO_TCP) ? "tcp" : "udp";
+        final String ageStr = (value.lastUsed == 0) ? "-"
+                : String.format("%dms", (now - value.lastUsed) / 1_000_000);
+        return String.format("%s [%s] %d(%s) %s:%d -> %d(%s) %s:%d -> %s:%d [%s] %s",
+                protoStr, key.dstMac, key.iif, getIfName(key.iif), src4, key.srcPort,
                 value.oif, getIfName(value.oif),
-                public4, publicPort, dst4, value.dstPort, value.ethDstMac, ageMs);
+                public4, publicPort, dst4, value.dstPort, value.ethDstMac, ageStr);
     }
 
     private void dumpIpv4ForwardingRuleMap(long now, boolean downstream,
@@ -1094,12 +1096,14 @@
 
         try (BpfMap<Tether4Key, Tether4Value> upstreamMap = mDeps.getBpfUpstream4Map();
                 BpfMap<Tether4Key, Tether4Value> downstreamMap = mDeps.getBpfDownstream4Map()) {
-            pw.println("IPv4 Upstream: [inDstMac] iif(iface) src -> nat -> dst [outDstMac] age");
+            pw.println("IPv4 Upstream: proto [inDstMac] iif(iface) src -> nat -> "
+                    + "dst [outDstMac] age");
             pw.increaseIndent();
             dumpIpv4ForwardingRuleMap(now, UPSTREAM, upstreamMap, pw);
             pw.decreaseIndent();
 
-            pw.println("IPv4 Downstream: [inDstMac] iif(iface) src -> nat -> dst [outDstMac] age");
+            pw.println("IPv4 Downstream: proto [inDstMac] iif(iface) src -> nat -> "
+                    + "dst [outDstMac] age");
             pw.increaseIndent();
             dumpIpv4ForwardingRuleMap(now, DOWNSTREAM, downstreamMap, pw);
             pw.decreaseIndent();
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index a381621..e91ae85 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -1039,7 +1039,7 @@
             final boolean rndisEnabled = intent.getBooleanExtra(USB_FUNCTION_RNDIS, false);
             final boolean ncmEnabled = intent.getBooleanExtra(USB_FUNCTION_NCM, false);
 
-            mLog.log(String.format("USB bcast connected:%s configured:%s rndis:%s ncm:%s",
+            mLog.i(String.format("USB bcast connected:%s configured:%s rndis:%s ncm:%s",
                     usbConnected, usbConfigured, rndisEnabled, ncmEnabled));
 
             // There are three types of ACTION_USB_STATE:
@@ -1416,7 +1416,7 @@
 
         // If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
         // available.
-        if (mConfig.isUsingNcm()) return TETHER_ERROR_SERVICE_UNAVAIL;
+        if (mConfig.isUsingNcm() && enable) return TETHER_ERROR_SERVICE_UNAVAIL;
 
         UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
         usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_NCM : UsbManager.FUNCTION_NONE);
@@ -2552,7 +2552,7 @@
             return;
         }
 
-        mLog.log("adding IpServer for: " + iface);
+        mLog.i("adding IpServer for: " + iface);
         final TetherState tetherState = new TetherState(
                 new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
                              makeControlCallback(), mConfig.enableLegacyDhcpServer,
@@ -2567,7 +2567,7 @@
         if (tetherState == null) return;
 
         tetherState.ipServer.stop();
-        mLog.log("removing IpServer for: " + iface);
+        mLog.i("removing IpServer for: " + iface);
         mTetherStates.remove(iface);
     }
 
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
index 31fcea4..d2f44d3 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java
@@ -176,7 +176,9 @@
         // us an interface name. Careful consideration needs to be given to
         // implications for Settings and for provisioning checks.
         tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs);
-        tetherableWigigRegexs = getResourceStringArray(res, R.array.config_tether_wigig_regexs);
+        // TODO: Remove entire wigig code once tethering module no longer support R devices.
+        tetherableWigigRegexs = SdkLevel.isAtLeastS()
+                ? new String[0] : getResourceStringArray(res, R.array.config_tether_wigig_regexs);
         tetherableWifiP2pRegexs = getResourceStringArray(
                 res, R.array.config_tether_wifi_p2p_regexs);
         tetherableBluetoothRegexs = getResourceStringArray(
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index af28dd7..9e0c880 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -2610,12 +2610,57 @@
         reset(mBluetoothAdapter, mBluetoothPan);
     }
 
+    private void runDualStackUsbTethering(final String expectedIface) throws Exception {
+        when(mNetd.interfaceGetList()).thenReturn(new String[] {expectedIface});
+        when(mRouterAdvertisementDaemon.start())
+                .thenReturn(true);
+        final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
+        runUsbTethering(upstreamState);
+
+        verify(mNetd).interfaceGetList();
+        verify(mNetd).tetherAddForward(expectedIface, TEST_MOBILE_IFNAME);
+        verify(mNetd).ipfwdAddInterfaceForward(expectedIface, TEST_MOBILE_IFNAME);
+
+        verify(mRouterAdvertisementDaemon).start();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
+                any(), any());
+        sendIPv6TetherUpdates(upstreamState);
+        assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
+        verify(mRouterAdvertisementDaemon).buildNewRa(any(), notNull());
+        verify(mNetd).tetherApplyDnsInterfaces();
+    }
+
+    private void forceUsbTetheringUse(final int function) {
+        Settings.Global.putInt(mContentResolver, TETHER_FORCE_USB_FUNCTIONS, function);
+        final ContentObserver observer = mTethering.getSettingsObserverForTest();
+        observer.onChange(false /* selfChange */);
+        mLooper.dispatchAll();
+    }
+
+    private void verifyUsbTetheringStopDueToSettingChange(final String iface) {
+        verify(mUsbManager, times(2)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        mTethering.interfaceRemoved(iface);
+        sendUsbBroadcast(true, true, -1 /* no functions enabled */);
+        reset(mUsbManager, mNetd, mDhcpServer, mRouterAdvertisementDaemon,
+                mIPv6TetheringCoordinator, mDadProxy);
+    }
+
     @Test
-    public void testUsbTetheringWithNcmFunction() throws Exception {
-        when(mResources.getInteger(R.integer.config_tether_usb_functions)).thenReturn(
-                TetheringConfiguration.TETHER_USB_NCM_FUNCTION);
+    public void testUsbFunctionConfigurationChange() throws Exception {
+        // Run TETHERING_NCM.
+        runNcmTethering();
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
+
+        // Change the USB tethering function to NCM. Because the USB tethering function was set to
+        // RNDIS (the default), tethering is stopped.
+        forceUsbTetheringUse(TETHER_USB_NCM_FUNCTION);
+        verifyUsbTetheringStopDueToSettingChange(TEST_NCM_IFNAME);
+
+        // TODO: move this into setup after allowing configure TEST_NCM_REGEX into
+        // config_tether_usb_regexs and config_tether_ncm_regexs at the same time.
         when(mResources.getStringArray(R.array.config_tether_usb_regexs))
-                .thenReturn(new String[] {TEST_NCM_REGEX});
+                .thenReturn(new String[] {TEST_RNDIS_REGEX, TEST_NCM_REGEX});
         sendConfigurationChanged();
 
         // If TETHERING_USB is forced to use ncm function, TETHERING_NCM would no longer be
@@ -2625,30 +2670,16 @@
         mLooper.dispatchAll();
         ncmResult.assertHasResult();
 
-        final UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
-        runUsbTethering(upstreamState);
+        // Run TETHERING_USB with ncm configuration.
+        runDualStackUsbTethering(TEST_NCM_IFNAME);
 
-        verify(mNetd).interfaceGetList();
-        verify(mNetd).tetherAddForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
-        verify(mNetd).ipfwdAddInterfaceForward(TEST_NCM_IFNAME, TEST_MOBILE_IFNAME);
+        // Change configuration to rndis.
+        forceUsbTetheringUse(TETHER_USB_RNDIS_FUNCTION);
+        verifyUsbTetheringStopDueToSettingChange(TEST_NCM_IFNAME);
 
-        verify(mRouterAdvertisementDaemon).start();
-        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS)).startWithCallbacks(
-                any(), any());
-        sendIPv6TetherUpdates(upstreamState);
-        assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
-        verify(mRouterAdvertisementDaemon).buildNewRa(any(), notNull());
-        verify(mNetd).tetherApplyDnsInterfaces();
-
-        Settings.Global.putInt(mContentResolver, TETHER_FORCE_USB_FUNCTIONS,
-                TETHER_USB_RNDIS_FUNCTION);
-        final ContentObserver observer = mTethering.getSettingsObserverForTest();
-        observer.onChange(false /* selfChange */);
-        mLooper.dispatchAll();
-        // stop TETHERING_USB and TETHERING_NCM
-        verify(mUsbManager, times(2)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
-        mTethering.interfaceRemoved(TEST_NCM_IFNAME);
-        sendUsbBroadcast(true, true, -1 /* function */);
+        // Run TETHERING_USB with rndis configuration.
+        runDualStackUsbTethering(TEST_RNDIS_IFNAME);
+        runStopUSBTethering();
     }
     // TODO: Test that a request for hotspot mode doesn't interfere with an
     // already operating tethering mode interface.
diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java
index 7e62d28..9791cbf 100644
--- a/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java
@@ -182,7 +182,7 @@
     public void setTestAllowBadWifiUntil(long timeMs) {
         Log.d(TAG, "setTestAllowBadWifiUntil: " + mTestAllowBadWifiUntilMs);
         mTestAllowBadWifiUntilMs = timeMs;
-        updateAvoidBadWifi();
+        reevaluateInternal();
     }
 
     @VisibleForTesting
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 70ddb9a..bf32ad5 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -111,4 +111,7 @@
          notification that can be dismissed. -->
     <bool name="config_ongoingSignInNotification">false</bool>
 
+    <!-- Whether to cancel network notifications automatically when tapped -->
+    <bool name="config_autoCancelNetworkNotifications">true</bool>
+
 </resources>
diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml
index fd23566..6ac6a0e 100644
--- a/service/ServiceConnectivityResources/res/values/overlayable.xml
+++ b/service/ServiceConnectivityResources/res/values/overlayable.xml
@@ -31,7 +31,9 @@
             <item type="integer" name="config_networkNotifySwitchType"/>
             <item type="array" name="config_networkNotifySwitches"/>
             <item type="bool" name="config_ongoingSignInNotification"/>
-
+            <item type="bool" name="config_autoCancelNetworkNotifications"/>
+            <item type="drawable" name="stat_notify_wifi_in_range"/>
+            <item type="drawable" name="stat_notify_rssi_in_range"/>
         </policy>
     </overlayable>
 </resources>
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 94f652d..7b58503 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -6288,7 +6288,8 @@
                 callingAttributionTag);
         if (VDBG) log("pendingListenForNetwork for " + nri);
 
-        mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
+        mHandler.sendMessage(mHandler.obtainMessage(
+                    EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT, nri));
     }
 
     /** Returns the next Network provider ID. */
diff --git a/service/src/com/android/server/connectivity/KeepaliveTracker.java b/service/src/com/android/server/connectivity/KeepaliveTracker.java
index 7d922a4..3b58823 100644
--- a/service/src/com/android/server/connectivity/KeepaliveTracker.java
+++ b/service/src/com/android/server/connectivity/KeepaliveTracker.java
@@ -366,12 +366,10 @@
                     Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
                 }
             }
-            // Ignore the case when the network disconnects immediately after stop() has been
-            // called and the keepalive code is waiting for the response from the modem. This
-            // might happen when the caller listens for a lower-layer network disconnect
-            // callback and stop the keepalive at that time. But the stop() races with the
-            // stop() generated in ConnectivityService network disconnection code path.
-            if (mStartedState == STOPPING && reason == ERROR_INVALID_NETWORK) return;
+            // To prevent races from re-entrance of stop(), return if the state is already stopping.
+            // This might happen if multiple event sources stop keepalive in a short time. Such as
+            // network disconnect after user calls stop(), or tear down socket after binder died.
+            if (mStartedState == STOPPING) return;
 
             // Store the reason of stopping, and report it after the keepalive is fully stopped.
             if (mStopReason != ERROR_STOP_REASON_UNINITIALIZED) {
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 3dc79c5..ae98d92 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -274,7 +274,7 @@
                 .setWhen(System.currentTimeMillis())
                 .setShowWhen(notifyType == NotificationType.NETWORK_SWITCH)
                 .setSmallIcon(icon)
-                .setAutoCancel(true)
+                .setAutoCancel(r.getBoolean(R.bool.config_autoCancelNetworkNotifications))
                 .setTicker(title)
                 .setColor(mContext.getColor(android.R.color.system_notification_accent_color))
                 .setContentTitle(title)
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 9ad9522..be1efb6 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -76,6 +76,7 @@
 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
 import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
 import static com.android.testutils.MiscAsserts.assertThrows;
@@ -506,6 +507,7 @@
     @Test
     public void testGetAllNetworkStateSnapshots()
             throws InterruptedException {
+        assumeTrue(mPackageManager.hasSystemFeature(FEATURE_TELEPHONY));
         // Make sure cell is active to retrieve IMSI for verification in later step.
         final Network cellNetwork = mCtsNetUtils.connectToCell();
         final String subscriberId = getSubscriberIdForCellNetwork(cellNetwork);
@@ -943,8 +945,8 @@
             // noticeably flaky.
             Thread.sleep(NO_CALLBACK_TIMEOUT_MS);
 
-            // TODO: BUG (b/189868426): this should also apply to listens
-            if (!useListen) {
+            // For R- frameworks, listens will receive duplicated callbacks. See b/189868426.
+            if (isAtLeastS() || !useListen) {
                 assertEquals("PendingIntent should only be received once", 1, receivedCount.get());
             }
         } finally {
@@ -959,8 +961,8 @@
             boolean useListen) {
         assertArrayEquals(filed.networkCapabilities.getCapabilities(),
                 broadcasted.networkCapabilities.getCapabilities());
-        // TODO: BUG (b/189868426): this should also apply to listens
-        if (useListen) return;
+        // For R- frameworks, listens will receive duplicated callbacks. See b/189868426.
+        if (!isAtLeastS() && useListen) return;
         assertArrayEquals(filed.networkCapabilities.getTransportTypes(),
                 broadcasted.networkCapabilities.getTransportTypes());
     }
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index f6ea964..2aa95ee 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -5566,6 +5566,8 @@
             // the follow-up network disconnection will be processed first.
             mWiFiNetworkAgent.setKeepaliveResponseDelay(3 * TIMEOUT_MS);
             ka.stop();
+            // Call stop() twice shouldn't result in crash, b/182586681.
+            ka.stop();
 
             // Make sure the stop has been processed. Wait for executor idle is needed to prevent
             // flaky since the actual stop call to the service is delegated to executor thread.
@@ -5863,37 +5865,59 @@
     @Test
     public void testNetworkCallbackMaximum() throws Exception {
         final int MAX_REQUESTS = 100;
-        final int CALLBACKS = 89;
-        final int INTENTS = 11;
+        final int CALLBACKS = 87;
+        final int DIFF_INTENTS = 10;
+        final int SAME_INTENTS = 10;
         final int SYSTEM_ONLY_MAX_REQUESTS = 250;
-        assertEquals(MAX_REQUESTS, CALLBACKS + INTENTS);
+        // Assert 1 (Default request filed before testing) + CALLBACKS + DIFF_INTENTS +
+        // 1 (same intent) = MAX_REQUESTS - 1, since the capacity is MAX_REQUEST - 1.
+        assertEquals(MAX_REQUESTS - 1, 1 + CALLBACKS + DIFF_INTENTS + 1);
 
         NetworkRequest networkRequest = new NetworkRequest.Builder().build();
         ArrayList<Object> registered = new ArrayList<>();
 
-        int j = 0;
-        while (j++ < CALLBACKS / 2) {
-            NetworkCallback cb = new NetworkCallback();
-            mCm.requestNetwork(networkRequest, cb);
+        for (int j = 0; j < CALLBACKS; j++) {
+            final NetworkCallback cb = new NetworkCallback();
+            if (j < CALLBACKS / 2) {
+                mCm.requestNetwork(networkRequest, cb);
+            } else {
+                mCm.registerNetworkCallback(networkRequest, cb);
+            }
             registered.add(cb);
         }
-        while (j++ < CALLBACKS) {
-            NetworkCallback cb = new NetworkCallback();
-            mCm.registerNetworkCallback(networkRequest, cb);
-            registered.add(cb);
+
+        // Since ConnectivityService will de-duplicate the request with the same intent,
+        // register multiple times does not really increase multiple requests.
+        final PendingIntent same_pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
+                new Intent("same"), FLAG_IMMUTABLE);
+        for (int j = 0; j < SAME_INTENTS; j++) {
+            mCm.registerNetworkCallback(networkRequest, same_pi);
+            // Wait for the requests with the same intent to be de-duplicated. Because
+            // ConnectivityService side incrementCountOrThrow in binder, decrementCount in handler
+            // thread, waitForIdle is needed to ensure decrementCount being invoked for same intent
+            // requests before doing further tests.
+            waitForIdle();
         }
-        j = 0;
-        while (j++ < INTENTS / 2) {
-            final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
-                    new Intent("a" + j), FLAG_IMMUTABLE);
-            mCm.requestNetwork(networkRequest, pi);
-            registered.add(pi);
+        for (int j = 0; j < SAME_INTENTS; j++) {
+            mCm.requestNetwork(networkRequest, same_pi);
+            // Wait for the requests with the same intent to be de-duplicated.
+            // Refer to the reason above.
+            waitForIdle();
         }
-        while (j++ < INTENTS) {
-            final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
-                    new Intent("b" + j), FLAG_IMMUTABLE);
-            mCm.registerNetworkCallback(networkRequest, pi);
-            registered.add(pi);
+        registered.add(same_pi);
+
+        for (int j = 0; j < DIFF_INTENTS; j++) {
+            if (j < DIFF_INTENTS / 2) {
+                final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
+                        new Intent("a" + j), FLAG_IMMUTABLE);
+                mCm.requestNetwork(networkRequest, pi);
+                registered.add(pi);
+            } else {
+                final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */,
+                        new Intent("b" + j), FLAG_IMMUTABLE);
+                mCm.registerNetworkCallback(networkRequest, pi);
+                registered.add(pi);
+            }
         }
 
         // Test that the limit is enforced when MAX_REQUESTS simultaneous requests are added.
@@ -5943,10 +5967,10 @@
 
         for (Object o : registered) {
             if (o instanceof NetworkCallback) {
-                mCm.unregisterNetworkCallback((NetworkCallback)o);
+                mCm.unregisterNetworkCallback((NetworkCallback) o);
             }
             if (o instanceof PendingIntent) {
-                mCm.unregisterNetworkCallback((PendingIntent)o);
+                mCm.unregisterNetworkCallback((PendingIntent) o);
             }
         }
         waitForIdle();
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index 20be5f4..1eac4ea 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -18,10 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -40,6 +42,7 @@
 import com.android.server.NsdService.DaemonConnection;
 import com.android.server.NsdService.DaemonConnectionSupplier;
 import com.android.server.NsdService.NativeCallbackReceiver;
+import com.android.testutils.HandlerUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -48,6 +51,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 // TODOs:
 //  - test client can send requests and receive replies
@@ -58,14 +62,13 @@
 
     static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD;
     private static final long CLEANUP_DELAY_MS = 500;
-
-    long mTimeoutMs = 100; // non-final so that tests can adjust the value.
+    private static final long TIMEOUT_MS = 500;
 
     @Mock Context mContext;
     @Mock ContentResolver mResolver;
     @Mock NsdService.NsdSettings mSettings;
-    @Mock DaemonConnection mDaemon;
     NativeCallbackReceiver mDaemonCallback;
+    @Spy DaemonConnection mDaemon = new DaemonConnection(mDaemonCallback);
     HandlerThread mThread;
     TestHandler mHandler;
 
@@ -74,6 +77,7 @@
         MockitoAnnotations.initMocks(this);
         mThread = new HandlerThread("mock-service-handler");
         mThread.start();
+        doReturn(true).when(mDaemon).execute(any());
         mHandler = new TestHandler(mThread.getLooper());
         when(mContext.getContentResolver()).thenReturn(mResolver);
     }
@@ -95,14 +99,17 @@
         // Creating an NsdManager will not cause any cmds executed, which means
         // no daemon is started.
         NsdManager client1 = connectClient(service);
+        waitForIdle();
         verify(mDaemon, never()).execute(any());
 
         // Creating another NsdManager will not cause any cmds executed.
         NsdManager client2 = connectClient(service);
+        waitForIdle();
         verify(mDaemon, never()).execute(any());
 
         client1.disconnect();
         // Still 1 client remains, daemon shouldn't be stopped.
+        waitForIdle();
         verify(mDaemon, never()).maybeStop();
 
         client2.disconnect();
@@ -116,11 +123,11 @@
     @Test
     public void testClientRequestsAreGCedAtDisconnection() {
         when(mSettings.isEnabled()).thenReturn(true);
-        when(mDaemon.execute(any())).thenReturn(true);
 
         NsdService service = makeService();
         NsdManager client = connectClient(service);
 
+        waitForIdle();
         verify(mDaemon, never()).maybeStart();
         verify(mDaemon, never()).execute(any());
 
@@ -130,27 +137,30 @@
         // Client registration request
         NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
         client.registerService(request, PROTOCOL, listener1);
-        verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
-        verifyDaemonCommand("register 2 a_name a_type 2201");
+        waitForIdle();
+        verify(mDaemon, times(1)).maybeStart();
+        verifyDaemonCommands("start-service", "register 2 a_name a_type 2201");
 
         // Client discovery request
         NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class);
         client.discoverServices("a_type", PROTOCOL, listener2);
-        verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
+        waitForIdle();
+        verify(mDaemon, times(1)).maybeStart();
         verifyDaemonCommand("discover 3 a_type");
 
         // Client resolve request
         NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class);
         client.resolveService(request, listener3);
-        verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
+        waitForIdle();
+        verify(mDaemon, times(1)).maybeStart();
         verifyDaemonCommand("resolve 4 a_name a_type local.");
 
         // Client disconnects, stop the daemon after CLEANUP_DELAY_MS.
         client.disconnect();
         verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
-
         // checks that request are cleaned
-        verifyDaemonCommands("stop-register 2", "stop-discover 3", "stop-resolve 4");
+        verifyDaemonCommands("stop-register 2", "stop-discover 3",
+                "stop-resolve 4", "stop-service");
 
         client.disconnect();
     }
@@ -158,7 +168,6 @@
     @Test
     public void testCleanupDelayNoRequestActive() {
         when(mSettings.isEnabled()).thenReturn(true);
-        when(mDaemon.execute(any())).thenReturn(true);
 
         NsdService service = makeService();
         NsdManager client = connectClient(service);
@@ -167,19 +176,25 @@
         request.setPort(2201);
         NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class);
         client.registerService(request, PROTOCOL, listener1);
-        verify(mDaemon, timeout(mTimeoutMs).times(1)).maybeStart();
-        verifyDaemonCommand("register 2 a_name a_type 2201");
+        waitForIdle();
+        verify(mDaemon, times(1)).maybeStart();
+        verifyDaemonCommands("start-service", "register 2 a_name a_type 2201");
 
         client.unregisterService(listener1);
         verifyDaemonCommand("stop-register 2");
 
         verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
+        verifyDaemonCommand("stop-service");
         reset(mDaemon);
         client.disconnect();
         // Client disconnects, after CLEANUP_DELAY_MS, maybeStop the daemon.
         verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS);
     }
 
+    private void waitForIdle() {
+        HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
+    }
+
     NsdService makeService() {
         DaemonConnectionSupplier supplier = (callback) -> {
             mDaemonCallback = callback;
@@ -196,10 +211,11 @@
     }
 
     void verifyDelayMaybeStopDaemon(long cleanupDelayMs) {
+        waitForIdle();
         // Stop daemon shouldn't be called immediately.
-        verify(mDaemon, timeout(mTimeoutMs).times(0)).maybeStop();
+        verify(mDaemon, never()).maybeStop();
         // Clean up the daemon after CLEANUP_DELAY_MS.
-        verify(mDaemon, timeout(cleanupDelayMs + mTimeoutMs)).maybeStop();
+        verify(mDaemon, timeout(cleanupDelayMs + TIMEOUT_MS)).maybeStop();
     }
 
     void verifyDaemonCommands(String... wants) {
@@ -211,8 +227,9 @@
     }
 
     void verifyDaemonCommand(String want, int n) {
-        ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
-        verify(mDaemon, timeout(mTimeoutMs).times(n)).execute(argumentsCaptor.capture());
+        waitForIdle();
+        final ArgumentCaptor<Object> argumentsCaptor = ArgumentCaptor.forClass(Object.class);
+        verify(mDaemon, times(n)).execute(argumentsCaptor.capture());
         String got = "";
         for (Object o : argumentsCaptor.getAllValues()) {
             got += o + " ";
@@ -220,7 +237,7 @@
         assertEquals(want, got.trim());
         // rearm deamon for next command verification
         reset(mDaemon);
-        when(mDaemon.execute(any())).thenReturn(true);
+        doReturn(true).when(mDaemon).execute(any());
     }
 
     public static class TestHandler extends Handler {
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index c353cea..115efbb 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.connectivity;
 
+import static android.app.Notification.FLAG_AUTO_CANCEL;
 import static android.app.Notification.FLAG_ONGOING_EVENT;
 
 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.LOST_INTERNET;
@@ -78,6 +79,8 @@
 
     private static final String TEST_SSID = "Test SSID";
     private static final String TEST_EXTRA_INFO = "extra";
+    private static final int TEST_NOTIF_ID = 101;
+    private static final String TEST_NOTIF_TAG = NetworkNotificationManager.tagFor(TEST_NOTIF_ID);
     static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
     static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
@@ -142,6 +145,7 @@
         }
         when(mResources.getStringArray(R.array.network_switch_type_name))
             .thenReturn(transportNames);
+        when(mResources.getBoolean(R.bool.config_autoCancelNetworkNotifications)).thenReturn(true);
 
         mManager = new NetworkNotificationManager(mCtx, mTelephonyManager);
     }
@@ -238,57 +242,65 @@
         verify(mNotificationManager, never()).notify(any(), anyInt(), any());
     }
 
-    private void assertNotification(NotificationType type, boolean ongoing) {
-        final int id = 101;
-        final String tag = NetworkNotificationManager.tagFor(id);
+    private void assertNotification(NotificationType type, boolean ongoing, boolean autoCancel) {
         final ArgumentCaptor<Notification> noteCaptor = ArgumentCaptor.forClass(Notification.class);
-        mManager.showNotification(id, type, mWifiNai, mCellNai, null, false);
-        verify(mNotificationManager, times(1)).notify(eq(tag), eq(type.eventId),
+        mManager.showNotification(TEST_NOTIF_ID, type, mWifiNai, mCellNai, null, false);
+        verify(mNotificationManager, times(1)).notify(eq(TEST_NOTIF_TAG), eq(type.eventId),
                 noteCaptor.capture());
 
         assertEquals("Notification ongoing flag should be " + (ongoing ? "set" : "unset"),
                 ongoing, (noteCaptor.getValue().flags & FLAG_ONGOING_EVENT) != 0);
+        assertEquals("Notification autocancel flag should be " + (autoCancel ? "set" : "unset"),
+                autoCancel, (noteCaptor.getValue().flags & FLAG_AUTO_CANCEL) != 0);
     }
 
     @Test
     public void testDuplicatedNotificationsNoInternetThenSignIn() {
-        final int id = 101;
-        final String tag = NetworkNotificationManager.tagFor(id);
-
         // Show first NO_INTERNET
-        assertNotification(NO_INTERNET, false /* ongoing */);
+        assertNotification(NO_INTERNET, false /* ongoing */, true /* autoCancel */);
 
         // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET
-        assertNotification(SIGN_IN, false /* ongoing */);
-        verify(mNotificationManager, times(1)).cancel(eq(tag), eq(NO_INTERNET.eventId));
+        assertNotification(SIGN_IN, false /* ongoing */, true /* autoCancel */);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(NO_INTERNET.eventId));
 
         // Network disconnects
-        mManager.clearNotification(id);
-        verify(mNotificationManager, times(1)).cancel(eq(tag), eq(SIGN_IN.eventId));
+        mManager.clearNotification(TEST_NOTIF_ID);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(SIGN_IN.eventId));
     }
 
     @Test
     public void testOngoingSignInNotification() {
         doReturn(true).when(mResources).getBoolean(R.bool.config_ongoingSignInNotification);
-        final int id = 101;
-        final String tag = NetworkNotificationManager.tagFor(id);
 
         // Show first NO_INTERNET
-        assertNotification(NO_INTERNET, false /* ongoing */);
+        assertNotification(NO_INTERNET, false /* ongoing */, true /* autoCancel */);
 
         // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET
-        assertNotification(SIGN_IN, true /* ongoing */);
-        verify(mNotificationManager, times(1)).cancel(eq(tag), eq(NO_INTERNET.eventId));
+        assertNotification(SIGN_IN, true /* ongoing */, true /* autoCancel */);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(NO_INTERNET.eventId));
 
         // Network disconnects
-        mManager.clearNotification(id);
-        verify(mNotificationManager, times(1)).cancel(eq(tag), eq(SIGN_IN.eventId));
+        mManager.clearNotification(TEST_NOTIF_ID);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(SIGN_IN.eventId));
+    }
+
+    @Test
+    public void testNoAutoCancelNotification() {
+        doReturn(false).when(mResources).getBoolean(R.bool.config_autoCancelNetworkNotifications);
+
+        // Show NO_INTERNET, then SIGN_IN
+        assertNotification(NO_INTERNET, false /* ongoing */, false /* autoCancel */);
+        assertNotification(SIGN_IN, false /* ongoing */, false /* autoCancel */);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(NO_INTERNET.eventId));
+
+        mManager.clearNotification(TEST_NOTIF_ID);
+        verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(SIGN_IN.eventId));
     }
 
     @Test
     public void testDuplicatedNotificationsSignInThenNoInternet() {
-        final int id = 101;
-        final String tag = NetworkNotificationManager.tagFor(id);
+        final int id = TEST_NOTIF_ID;
+        final String tag = TEST_NOTIF_TAG;
 
         // Show first SIGN_IN
         mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false);
@@ -307,8 +319,8 @@
 
     @Test
     public void testClearNotificationByType() {
-        final int id = 101;
-        final String tag = NetworkNotificationManager.tagFor(id);
+        final int id = TEST_NOTIF_ID;
+        final String tag = TEST_NOTIF_TAG;
 
         // clearNotification(int id, NotificationType notifyType) will check if given type is equal
         // to previous type or not. If they are equal then clear the notification; if they are not