Allow firing the LOST_INTERNET intent immediately in config

When config_notifyNoInternetAsDialogWhenHighPriority is on, the
LOST_INTERNET notification intent should be fired immediately
rather than as a pending intent.

Bug: 281970908
Test: Improve test for this in NetworkNotificationManagerTest
(cherry picked from https://android-review.googlesource.com/q/commit:e13b833b21342d80ef940783acba4b3eecfce968)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:3bf3a59f359e2d6db3a335cad7085fe52a102e0b)
Merged-In: I88565839a12a1ab4b096f763250944ebaf6c5349
Change-Id: I88565839a12a1ab4b096f763250944ebaf6c5349
diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml
index 22d9b01..f30abc6 100644
--- a/service/ServiceConnectivityResources/res/values/config.xml
+++ b/service/ServiceConnectivityResources/res/values/config.xml
@@ -135,10 +135,17 @@
     <!-- Whether to cancel network notifications automatically when tapped -->
     <bool name="config_autoCancelNetworkNotifications">true</bool>
 
-    <!-- When no internet or partial connectivity is detected on a network, and a high priority
-         (heads up) notification would be shown due to the network being explicitly selected,
-         directly show the dialog that would normally be shown when tapping the notification
-         instead of showing the notification. -->
+    <!-- Configuration to let OEMs customize what to do when :
+         • Partial connectivity is detected on the network
+         • No internet is detected on the network, and
+           - the network was explicitly selected
+           - the system is configured to actively prefer bad wifi (see config_activelyPreferBadWifi)
+         The default behavior (false) is to post a notification with a PendingIntent so
+         the user is informed and can act if they wish.
+         Making this true instead will have the system fire the intent immediately instead
+         of showing a notification. OEMs who do this should have some intent receiver
+         listening to the intent and take the action they prefer (e.g. show a dialog,
+         show a customized notification etc).  -->
     <bool name="config_notifyNoInternetAsDialogWhenHighPriority">false</bool>
 
     <!-- When showing notifications indicating partial connectivity, display the same notifications
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 8b0cb7c..ab7b4cc 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -322,7 +322,11 @@
 
     private boolean maybeNotifyViaDialog(Resources res, NotificationType notifyType,
             PendingIntent intent) {
-        if (notifyType != NotificationType.NO_INTERNET
+        final boolean activelyPreferBadWifi = SdkLevel.isAtLeastU()
+                || (SdkLevel.isAtLeastT()
+                        && res.getInteger(R.integer.config_activelyPreferBadWifi) == 1);
+        if ((notifyType != NotificationType.LOST_INTERNET || !activelyPreferBadWifi)
+                && notifyType != NotificationType.NO_INTERNET
                 && notifyType != NotificationType.PARTIAL_CONNECTIVITY) {
             return false;
         }
@@ -432,7 +436,8 @@
      * A notification with a higher number will take priority over a notification with a lower
      * number.
      */
-    private static int priority(NotificationType t) {
+    @VisibleForTesting
+    public static int priority(NotificationType t) {
         if (t == null) {
             return 0;
         }
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index a27a0bf..d8b089d 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -62,6 +62,7 @@
 import android.util.DisplayMetrics;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.test.filters.SmallTest;
@@ -73,6 +74,7 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.connectivity.resources.R;
+import com.android.modules.utils.build.SdkLevel;
 import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
 import com.android.testutils.DevSdkIgnoreRule;
 import com.android.testutils.DevSdkIgnoreRunner;
@@ -385,15 +387,75 @@
         verify(mNotificationManager, never()).cancel(eq(tag), eq(PARTIAL_CONNECTIVITY.eventId));
     }
 
+    private static final int EXPECT_DIALOG = 0;
+    private static final int EXPECT_NOTIFICATION = 1;
     @Test
-    public void testNotifyNoInternetAsDialogWhenHighPriority() throws Exception {
-        doReturn(true).when(mResources).getBoolean(
+    public void testNotifyNoInternet_asNotification_ActivelyPrefer() throws Exception {
+        doTestNotifyNotificationAsDialogWhenHighPriority(false /* notifyAsDialog */,
+                true /* activelyPreferBadWifi */, NO_INTERNET, EXPECT_NOTIFICATION);
+    }
+
+    @Test
+    public void testNotifyNoInternet_asNotification_NotActivelyPrefer() throws Exception {
+        doTestNotifyNotificationAsDialogWhenHighPriority(false /* notifyAsDialog */,
+                false /* activelyPreferBadWifi */, NO_INTERNET, EXPECT_NOTIFICATION);
+    }
+
+    @Test
+    public void testNotifyNoInternet_asDialog_ActivelyPrefer() throws Exception {
+        doTestNotifyNotificationAsDialogWhenHighPriority(true /* notifyAsDialog */,
+                true /* activelyPreferBadWifi */, NO_INTERNET, EXPECT_DIALOG);
+    }
+
+    @Test
+    public void testNotifyNoInternet_asDialog_NotActivelyPrefer() throws Exception {
+        doTestNotifyNotificationAsDialogWhenHighPriority(true /* notifyAsDialog */,
+                false /* activelyPreferBadWifi */, NO_INTERNET, EXPECT_DIALOG);
+    }
+
+    @Test
+    public void testNotifyLostInternet_asNotification_ActivelyPrefer() throws Exception {
+        doTestNotifyNotificationAsDialogWhenHighPriority(false /* notifyAsDialog */,
+                true /* activelyPreferBadWifi */, LOST_INTERNET, EXPECT_NOTIFICATION);
+    }
+
+    @Test
+    public void testNotifyLostInternet_asNotification_NotActivelyPrefer() throws Exception {
+        doTestNotifyNotificationAsDialogWhenHighPriority(false /* notifyAsDialog */,
+                false /* activelyPreferBadWifi */, LOST_INTERNET, EXPECT_NOTIFICATION);
+    }
+
+    @Test
+    public void testNotifyLostInternet_asDialog_ActivelyPrefer() throws Exception {
+        doTestNotifyNotificationAsDialogWhenHighPriority(true /* notifyAsDialog */,
+                true /* activelyPreferBadWifi */, LOST_INTERNET,
+                SdkLevel.isAtLeastT() ? EXPECT_DIALOG : EXPECT_NOTIFICATION);
+    }
+
+    @Test
+    public void testNotifyLostInternet_asDialog_NotActivelyPrefer() throws Exception {
+        doTestNotifyNotificationAsDialogWhenHighPriority(true /* notifyAsDialog */,
+                false /* activelyPreferBadWifi */, LOST_INTERNET,
+                SdkLevel.isAtLeastU() ? EXPECT_DIALOG : EXPECT_NOTIFICATION);
+    }
+
+    // Pass EXPECT_DIALOG or EXPECT_NOTIFICATION to |expectBehavior|
+    public void doTestNotifyNotificationAsDialogWhenHighPriority(
+            final boolean notifyAsDialog, final boolean activelyPreferBadWifi,
+            @NonNull final NotificationType notifType, final int expectBehavior) throws Exception {
+        doReturn(notifyAsDialog).when(mResources).getBoolean(
                 R.bool.config_notifyNoInternetAsDialogWhenHighPriority);
+        doReturn(activelyPreferBadWifi ? 1 : 0).when(mResources).getInteger(
+                R.integer.config_activelyPreferBadWifi);
 
         final Instrumentation instr = InstrumentationRegistry.getInstrumentation();
         final UiDevice uiDevice =  UiDevice.getInstance(instr);
         final Context ctx = instr.getContext();
         final PowerManager pm = ctx.getSystemService(PowerManager.class);
+        // If the prio of this notif is < that of NETWORK_SWITCH, it's the lowest prio and
+        // therefore it can't be tested whether it cancels other lower-prio notifs.
+        final boolean isLowestPrioNotif = NetworkNotificationManager.priority(notifType)
+                < NetworkNotificationManager.priority(NETWORK_SWITCH);
 
         // Wake up the device (it has no effect if the device is already awake).
         uiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
@@ -409,9 +471,13 @@
                 uiDevice.wait(Until.hasObject(By.pkg(launcherPackageName)),
                         UI_AUTOMATOR_WAIT_TIME_MILLIS));
 
-        mManager.showNotification(TEST_NOTIF_ID, NETWORK_SWITCH, mWifiNai, mCellNai, null, false);
-        // Non-"no internet" notifications are not affected
-        verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(NETWORK_SWITCH.eventId), any());
+        if (!isLowestPrioNotif) {
+            mManager.showNotification(TEST_NOTIF_ID, NETWORK_SWITCH, mWifiNai, mCellNai,
+                    null, false);
+            // Non-"no internet" notifications are not affected
+            verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(NETWORK_SWITCH.eventId),
+                    any());
+        }
 
         final String testAction = "com.android.connectivity.coverage.TEST_DIALOG";
         final Intent intent = new Intent(testAction)
@@ -420,22 +486,30 @@
         final PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0 /* requestCode */,
                 intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
 
-        mManager.showNotification(TEST_NOTIF_ID, NO_INTERNET, mWifiNai, null /* switchToNai */,
+        mManager.showNotification(TEST_NOTIF_ID, notifType, mWifiNai, null /* switchToNai */,
                 pendingIntent, true /* highPriority */);
 
-        // Previous notifications are still dismissed
-        verify(mNotificationManager).cancel(TEST_NOTIF_TAG, NETWORK_SWITCH.eventId);
+        if (!isLowestPrioNotif) {
+            // Previous notifications are still dismissed
+            verify(mNotificationManager).cancel(TEST_NOTIF_TAG, NETWORK_SWITCH.eventId);
+        }
 
-        // Verify that the activity is shown (the activity shows the action on screen)
-        final UiObject actionText = uiDevice.findObject(new UiSelector().text(testAction));
-        assertTrue("Activity not shown", actionText.waitForExists(TEST_TIMEOUT_MS));
+        if (expectBehavior == EXPECT_DIALOG) {
+            // Verify that the activity is shown (the activity shows the action on screen)
+            final UiObject actionText = uiDevice.findObject(new UiSelector().text(testAction));
+            assertTrue("Activity not shown", actionText.waitForExists(TEST_TIMEOUT_MS));
 
-        // Tapping the text should dismiss the dialog
-        actionText.click();
-        assertTrue("Activity not dismissed", actionText.waitUntilGone(TEST_TIMEOUT_MS));
+            // Tapping the text should dismiss the dialog
+            actionText.click();
+            assertTrue("Activity not dismissed", actionText.waitUntilGone(TEST_TIMEOUT_MS));
 
-        // Verify no NO_INTERNET notification was posted
-        verify(mNotificationManager, never()).notify(any(), eq(NO_INTERNET.eventId), any());
+            // Verify that the notification was not posted
+            verify(mNotificationManager, never()).notify(any(), eq(notifType.eventId), any());
+        } else {
+            // Notification should have been posted, and will have overridden the previous
+            // one because it has the same id (hence no cancel).
+            verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(notifType.eventId), any());
+        }
     }
 
     private void doNotificationTextTest(NotificationType type, @StringRes int expectedTitleRes,