• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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