1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.connectivity; 18 19 import static android.app.Notification.FLAG_AUTO_CANCEL; 20 import static android.app.Notification.FLAG_ONGOING_EVENT; 21 22 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.LOST_INTERNET; 23 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.NETWORK_SWITCH; 24 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.NO_INTERNET; 25 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.PARTIAL_CONNECTIVITY; 26 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.PRIVATE_DNS_BROKEN; 27 import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.SIGN_IN; 28 29 import static org.junit.Assert.assertEquals; 30 import static org.junit.Assert.assertTrue; 31 import static org.mockito.Mockito.any; 32 import static org.mockito.Mockito.anyInt; 33 import static org.mockito.Mockito.clearInvocations; 34 import static org.mockito.Mockito.doReturn; 35 import static org.mockito.Mockito.eq; 36 import static org.mockito.Mockito.mock; 37 import static org.mockito.Mockito.never; 38 import static org.mockito.Mockito.reset; 39 import static org.mockito.Mockito.times; 40 import static org.mockito.Mockito.verify; 41 import static org.mockito.Mockito.when; 42 43 import android.app.Activity; 44 import android.app.Instrumentation; 45 import android.app.KeyguardManager; 46 import android.app.Notification; 47 import android.app.NotificationManager; 48 import android.app.PendingIntent; 49 import android.content.Context; 50 import android.content.Intent; 51 import android.content.pm.ApplicationInfo; 52 import android.content.pm.PackageManager; 53 import android.content.res.Resources; 54 import android.net.ConnectivityResources; 55 import android.net.NetworkCapabilities; 56 import android.net.NetworkInfo; 57 import android.os.Build; 58 import android.os.Bundle; 59 import android.os.UserHandle; 60 import android.telephony.TelephonyManager; 61 import android.util.DisplayMetrics; 62 import android.widget.TextView; 63 64 import androidx.annotation.Nullable; 65 import androidx.annotation.StringRes; 66 import androidx.test.filters.SmallTest; 67 import androidx.test.platform.app.InstrumentationRegistry; 68 import androidx.test.uiautomator.UiDevice; 69 import androidx.test.uiautomator.UiObject; 70 import androidx.test.uiautomator.UiSelector; 71 72 import com.android.connectivity.resources.R; 73 import com.android.server.connectivity.NetworkNotificationManager.NotificationType; 74 import com.android.testutils.DevSdkIgnoreRule; 75 import com.android.testutils.DevSdkIgnoreRunner; 76 77 import org.junit.After; 78 import org.junit.Before; 79 import org.junit.Ignore; 80 import org.junit.Test; 81 import org.junit.runner.RunWith; 82 import org.mockito.AdditionalAnswers; 83 import org.mockito.ArgumentCaptor; 84 import org.mockito.Mock; 85 import org.mockito.MockitoAnnotations; 86 87 import java.util.ArrayList; 88 import java.util.Arrays; 89 import java.util.Collections; 90 import java.util.List; 91 92 @RunWith(DevSdkIgnoreRunner.class) 93 @SmallTest 94 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) 95 public class NetworkNotificationManagerTest { 96 97 private static final String TEST_SSID = "Test SSID"; 98 private static final String TEST_EXTRA_INFO = "extra"; 99 private static final int TEST_NOTIF_ID = 101; 100 private static final String TEST_NOTIF_TAG = NetworkNotificationManager.tagFor(TEST_NOTIF_ID); 101 private static final long TEST_TIMEOUT_MS = 10_000L; 102 static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities(); 103 static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities(); 104 static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities(); 105 static { 106 CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); 107 CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 108 109 WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); 110 WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 111 WIFI_CAPABILITIES.setSSID(TEST_SSID); 112 113 // Set the underyling network to wifi. 114 VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); 115 VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_VPN); 116 VPN_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); 117 VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); 118 } 119 120 /** 121 * Test activity that shows the action it was started with on screen, and dismisses when the 122 * text is tapped. 123 */ 124 public static class TestDialogActivity extends Activity { 125 @Override onCreate(@ullable Bundle savedInstanceState)126 protected void onCreate(@Nullable Bundle savedInstanceState) { 127 super.onCreate(savedInstanceState); 128 setTurnScreenOn(true); 129 getSystemService(KeyguardManager.class).requestDismissKeyguard( 130 this, null /* callback */); 131 132 final TextView txt = new TextView(this); 133 txt.setText(getIntent().getAction()); 134 txt.setOnClickListener(e -> finish()); 135 setContentView(txt); 136 } 137 } 138 139 @Mock Context mCtx; 140 @Mock Resources mResources; 141 @Mock DisplayMetrics mDisplayMetrics; 142 @Mock PackageManager mPm; 143 @Mock TelephonyManager mTelephonyManager; 144 @Mock NotificationManager mNotificationManager; 145 @Mock NetworkAgentInfo mWifiNai; 146 @Mock NetworkAgentInfo mCellNai; 147 @Mock NetworkAgentInfo mVpnNai; 148 @Mock NetworkInfo mNetworkInfo; 149 ArgumentCaptor<Notification> mCaptor; 150 151 NetworkNotificationManager mManager; 152 153 @Before setUp()154 public void setUp() { 155 MockitoAnnotations.initMocks(this); 156 mCaptor = ArgumentCaptor.forClass(Notification.class); 157 mWifiNai.networkCapabilities = WIFI_CAPABILITIES; 158 mWifiNai.networkInfo = mNetworkInfo; 159 mCellNai.networkCapabilities = CELL_CAPABILITIES; 160 mCellNai.networkInfo = mNetworkInfo; 161 mVpnNai.networkCapabilities = VPN_CAPABILITIES; 162 mVpnNai.networkInfo = mNetworkInfo; 163 mDisplayMetrics.density = 2.275f; 164 doReturn(true).when(mVpnNai).isVPN(); 165 when(mCtx.getResources()).thenReturn(mResources); 166 when(mCtx.getPackageManager()).thenReturn(mPm); 167 when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo()); 168 final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mCtx)); 169 doReturn(UserHandle.ALL).when(asUserCtx).getUser(); 170 when(mCtx.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx); 171 when(mCtx.getSystemService(eq(Context.NOTIFICATION_SERVICE))) 172 .thenReturn(mNotificationManager); 173 when(mNetworkInfo.getExtraInfo()).thenReturn(TEST_EXTRA_INFO); 174 ConnectivityResources.setResourcesContextForTest(mCtx); 175 when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B); 176 when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); 177 178 // Come up with some credible-looking transport names. The actual values do not matter. 179 String[] transportNames = new String[NetworkCapabilities.MAX_TRANSPORT + 1]; 180 for (int transport = 0; transport <= NetworkCapabilities.MAX_TRANSPORT; transport++) { 181 transportNames[transport] = NetworkCapabilities.transportNameOf(transport); 182 } 183 when(mResources.getStringArray(R.array.network_switch_type_name)) 184 .thenReturn(transportNames); 185 when(mResources.getBoolean(R.bool.config_autoCancelNetworkNotifications)).thenReturn(true); 186 187 mManager = new NetworkNotificationManager(mCtx, mTelephonyManager); 188 } 189 190 @After tearDown()191 public void tearDown() { 192 ConnectivityResources.setResourcesContextForTest(null); 193 } 194 verifyTitleByNetwork(final int id, final NetworkAgentInfo nai, final int title)195 private void verifyTitleByNetwork(final int id, final NetworkAgentInfo nai, final int title) { 196 final String tag = NetworkNotificationManager.tagFor(id); 197 mManager.showNotification(id, PRIVATE_DNS_BROKEN, nai, null, null, true); 198 verify(mNotificationManager, times(1)) 199 .notify(eq(tag), eq(PRIVATE_DNS_BROKEN.eventId), any()); 200 final int transportType = NetworkNotificationManager.approximateTransportType(nai); 201 if (transportType == NetworkCapabilities.TRANSPORT_WIFI) { 202 verify(mResources, times(1)).getString(eq(title), eq(TEST_EXTRA_INFO)); 203 } else { 204 verify(mResources, times(1)).getString(title); 205 } 206 verify(mResources, times(1)).getString(eq(R.string.private_dns_broken_detailed)); 207 } 208 209 @Test testTitleOfPrivateDnsBroken()210 public void testTitleOfPrivateDnsBroken() { 211 // Test the title of mobile data. 212 verifyTitleByNetwork(100, mCellNai, R.string.mobile_no_internet); 213 clearInvocations(mResources); 214 215 // Test the title of wifi. 216 verifyTitleByNetwork(101, mWifiNai, R.string.wifi_no_internet); 217 clearInvocations(mResources); 218 219 // Test the title of other networks. 220 verifyTitleByNetwork(102, mVpnNai, R.string.other_networks_no_internet); 221 clearInvocations(mResources); 222 } 223 224 @Test testNotificationsShownAndCleared()225 public void testNotificationsShownAndCleared() { 226 final int NETWORK_ID_BASE = 100; 227 List<NotificationType> types = Arrays.asList(NotificationType.values()); 228 List<Integer> ids = new ArrayList<>(types.size()); 229 for (int i = 0; i < types.size(); i++) { 230 ids.add(NETWORK_ID_BASE + i); 231 } 232 Collections.shuffle(ids); 233 Collections.shuffle(types); 234 235 for (int i = 0; i < ids.size(); i++) { 236 mManager.showNotification(ids.get(i), types.get(i), mWifiNai, mCellNai, null, false); 237 } 238 239 List<Integer> idsToClear = new ArrayList<>(ids); 240 Collections.shuffle(idsToClear); 241 for (int i = 0; i < ids.size(); i++) { 242 mManager.clearNotification(idsToClear.get(i)); 243 } 244 245 for (int i = 0; i < ids.size(); i++) { 246 final int id = ids.get(i); 247 final int eventId = types.get(i).eventId; 248 final String tag = NetworkNotificationManager.tagFor(id); 249 verify(mNotificationManager, times(1)).notify(eq(tag), eq(eventId), any()); 250 verify(mNotificationManager, times(1)).cancel(eq(tag), eq(eventId)); 251 } 252 } 253 254 @Test 255 @Ignore 256 // Ignored because the code under test calls Log.wtf, which crashes the tests on eng builds. 257 // TODO: re-enable after fixing this (e.g., turn Log.wtf into exceptions that this test catches) testNoInternetNotificationsNotShownForCellular()258 public void testNoInternetNotificationsNotShownForCellular() { 259 mManager.showNotification(100, NO_INTERNET, mCellNai, mWifiNai, null, false); 260 mManager.showNotification(101, LOST_INTERNET, mCellNai, mWifiNai, null, false); 261 262 verify(mNotificationManager, never()).notify(any(), anyInt(), any()); 263 264 mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false); 265 266 final int eventId = NO_INTERNET.eventId; 267 final String tag = NetworkNotificationManager.tagFor(102); 268 verify(mNotificationManager, times(1)).notify(eq(tag), eq(eventId), any()); 269 } 270 271 @Test testNotificationsNotShownIfNoInternetCapability()272 public void testNotificationsNotShownIfNoInternetCapability() { 273 mWifiNai.networkCapabilities = new NetworkCapabilities(); 274 mWifiNai.networkCapabilities .addTransportType(NetworkCapabilities.TRANSPORT_WIFI); 275 mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false); 276 mManager.showNotification(103, LOST_INTERNET, mWifiNai, mCellNai, null, false); 277 mManager.showNotification(104, NETWORK_SWITCH, mWifiNai, mCellNai, null, false); 278 279 verify(mNotificationManager, never()).notify(any(), anyInt(), any()); 280 } 281 assertNotification(NotificationType type, boolean ongoing, boolean autoCancel)282 private void assertNotification(NotificationType type, boolean ongoing, boolean autoCancel) { 283 final ArgumentCaptor<Notification> noteCaptor = ArgumentCaptor.forClass(Notification.class); 284 mManager.showNotification(TEST_NOTIF_ID, type, mWifiNai, mCellNai, null, false); 285 verify(mNotificationManager, times(1)).notify(eq(TEST_NOTIF_TAG), eq(type.eventId), 286 noteCaptor.capture()); 287 288 assertEquals("Notification ongoing flag should be " + (ongoing ? "set" : "unset"), 289 ongoing, (noteCaptor.getValue().flags & FLAG_ONGOING_EVENT) != 0); 290 assertEquals("Notification autocancel flag should be " + (autoCancel ? "set" : "unset"), 291 autoCancel, (noteCaptor.getValue().flags & FLAG_AUTO_CANCEL) != 0); 292 } 293 294 @Test testDuplicatedNotificationsNoInternetThenSignIn()295 public void testDuplicatedNotificationsNoInternetThenSignIn() { 296 // Show first NO_INTERNET 297 assertNotification(NO_INTERNET, false /* ongoing */, true /* autoCancel */); 298 299 // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET 300 assertNotification(SIGN_IN, false /* ongoing */, true /* autoCancel */); 301 verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(NO_INTERNET.eventId)); 302 303 // Network disconnects 304 mManager.clearNotification(TEST_NOTIF_ID); 305 verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(SIGN_IN.eventId)); 306 } 307 308 @Test testOngoingSignInNotification()309 public void testOngoingSignInNotification() { 310 doReturn(true).when(mResources).getBoolean(R.bool.config_ongoingSignInNotification); 311 312 // Show first NO_INTERNET 313 assertNotification(NO_INTERNET, false /* ongoing */, true /* autoCancel */); 314 315 // Captive portal detection triggers SIGN_IN a bit later, clearing the previous NO_INTERNET 316 assertNotification(SIGN_IN, true /* ongoing */, true /* autoCancel */); 317 verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(NO_INTERNET.eventId)); 318 319 // Network disconnects 320 mManager.clearNotification(TEST_NOTIF_ID); 321 verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(SIGN_IN.eventId)); 322 } 323 324 @Test testNoAutoCancelNotification()325 public void testNoAutoCancelNotification() { 326 doReturn(false).when(mResources).getBoolean(R.bool.config_autoCancelNetworkNotifications); 327 328 // Show NO_INTERNET, then SIGN_IN 329 assertNotification(NO_INTERNET, false /* ongoing */, false /* autoCancel */); 330 assertNotification(SIGN_IN, false /* ongoing */, false /* autoCancel */); 331 verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(NO_INTERNET.eventId)); 332 333 mManager.clearNotification(TEST_NOTIF_ID); 334 verify(mNotificationManager, times(1)).cancel(eq(TEST_NOTIF_TAG), eq(SIGN_IN.eventId)); 335 } 336 337 @Test testDuplicatedNotificationsSignInThenNoInternet()338 public void testDuplicatedNotificationsSignInThenNoInternet() { 339 final int id = TEST_NOTIF_ID; 340 final String tag = TEST_NOTIF_TAG; 341 342 // Show first SIGN_IN 343 mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false); 344 verify(mNotificationManager, times(1)).notify(eq(tag), eq(SIGN_IN.eventId), any()); 345 reset(mNotificationManager); 346 347 // NO_INTERNET arrives after, but is ignored. 348 mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false); 349 verify(mNotificationManager, never()).cancel(any(), anyInt()); 350 verify(mNotificationManager, never()).notify(any(), anyInt(), any()); 351 352 // Network disconnects 353 mManager.clearNotification(id); 354 verify(mNotificationManager, times(1)).cancel(eq(tag), eq(SIGN_IN.eventId)); 355 } 356 357 @Test testClearNotificationByType()358 public void testClearNotificationByType() { 359 final int id = TEST_NOTIF_ID; 360 final String tag = TEST_NOTIF_TAG; 361 362 // clearNotification(int id, NotificationType notifyType) will check if given type is equal 363 // to previous type or not. If they are equal then clear the notification; if they are not 364 // equal then return. 365 mManager.showNotification(id, NO_INTERNET, mWifiNai, mCellNai, null, false); 366 verify(mNotificationManager, times(1)).notify(eq(tag), eq(NO_INTERNET.eventId), any()); 367 368 // Previous notification is NO_INTERNET and given type is NO_INTERNET too. The notification 369 // should be cleared. 370 mManager.clearNotification(id, NO_INTERNET); 371 verify(mNotificationManager, times(1)).cancel(eq(tag), eq(NO_INTERNET.eventId)); 372 373 // SIGN_IN is popped-up. 374 mManager.showNotification(id, SIGN_IN, mWifiNai, mCellNai, null, false); 375 verify(mNotificationManager, times(1)).notify(eq(tag), eq(SIGN_IN.eventId), any()); 376 377 // The notification type is not matching previous one, PARTIAL_CONNECTIVITY won't be 378 // cleared. 379 mManager.clearNotification(id, PARTIAL_CONNECTIVITY); 380 verify(mNotificationManager, never()).cancel(eq(tag), eq(PARTIAL_CONNECTIVITY.eventId)); 381 } 382 383 @Test testNotifyNoInternetAsDialogWhenHighPriority()384 public void testNotifyNoInternetAsDialogWhenHighPriority() throws Exception { 385 doReturn(true).when(mResources).getBoolean( 386 R.bool.config_notifyNoInternetAsDialogWhenHighPriority); 387 388 mManager.showNotification(TEST_NOTIF_ID, NETWORK_SWITCH, mWifiNai, mCellNai, null, false); 389 // Non-"no internet" notifications are not affected 390 verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(NETWORK_SWITCH.eventId), any()); 391 392 final Instrumentation instr = InstrumentationRegistry.getInstrumentation(); 393 final Context ctx = instr.getContext(); 394 final String testAction = "com.android.connectivity.coverage.TEST_DIALOG"; 395 final Intent intent = new Intent(testAction) 396 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 397 .setClassName(ctx.getPackageName(), TestDialogActivity.class.getName()); 398 final PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0 /* requestCode */, 399 intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); 400 401 mManager.showNotification(TEST_NOTIF_ID, NO_INTERNET, mWifiNai, null /* switchToNai */, 402 pendingIntent, true /* highPriority */); 403 404 // Previous notifications are still dismissed 405 verify(mNotificationManager).cancel(TEST_NOTIF_TAG, NETWORK_SWITCH.eventId); 406 407 // Verify that the activity is shown (the activity shows the action on screen) 408 final UiObject actionText = UiDevice.getInstance(instr).findObject( 409 new UiSelector().text(testAction)); 410 assertTrue("Activity not shown", actionText.waitForExists(TEST_TIMEOUT_MS)); 411 412 // Tapping the text should dismiss the dialog 413 actionText.click(); 414 assertTrue("Activity not dismissed", actionText.waitUntilGone(TEST_TIMEOUT_MS)); 415 416 // Verify no NO_INTERNET notification was posted 417 verify(mNotificationManager, never()).notify(any(), eq(NO_INTERNET.eventId), any()); 418 } 419 doNotificationTextTest(NotificationType type, @StringRes int expectedTitleRes, String expectedTitleArg, @StringRes int expectedContentRes)420 private void doNotificationTextTest(NotificationType type, @StringRes int expectedTitleRes, 421 String expectedTitleArg, @StringRes int expectedContentRes) { 422 final String expectedTitle = "title " + expectedTitleArg; 423 final String expectedContent = "expected content"; 424 doReturn(expectedTitle).when(mResources).getString(expectedTitleRes, expectedTitleArg); 425 doReturn(expectedContent).when(mResources).getString(expectedContentRes); 426 427 mManager.showNotification(TEST_NOTIF_ID, type, mWifiNai, mCellNai, null, false); 428 final ArgumentCaptor<Notification> notifCap = ArgumentCaptor.forClass(Notification.class); 429 430 verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(type.eventId), 431 notifCap.capture()); 432 final Notification notif = notifCap.getValue(); 433 434 assertEquals(expectedTitle, notif.extras.getString(Notification.EXTRA_TITLE)); 435 assertEquals(expectedContent, notif.extras.getString(Notification.EXTRA_TEXT)); 436 } 437 438 @Test testNotificationText_NoInternet()439 public void testNotificationText_NoInternet() { 440 doNotificationTextTest(NO_INTERNET, 441 R.string.wifi_no_internet, TEST_EXTRA_INFO, 442 R.string.wifi_no_internet_detailed); 443 } 444 445 @Test testNotificationText_Partial()446 public void testNotificationText_Partial() { 447 doNotificationTextTest(PARTIAL_CONNECTIVITY, 448 R.string.network_partial_connectivity, TEST_EXTRA_INFO, 449 R.string.network_partial_connectivity_detailed); 450 } 451 452 @Test testNotificationText_PartialAsNoInternet()453 public void testNotificationText_PartialAsNoInternet() { 454 doReturn(true).when(mResources).getBoolean( 455 R.bool.config_partialConnectivityNotifiedAsNoInternet); 456 doNotificationTextTest(PARTIAL_CONNECTIVITY, 457 R.string.wifi_no_internet, TEST_EXTRA_INFO, 458 R.string.wifi_no_internet_detailed); 459 } 460 } 461