• 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.notification.functional;
18 
19 import static org.junit.Assert.assertFalse;
20 import static org.junit.Assert.assertTrue;
21 
22 import android.app.AppOpsManager;
23 import android.app.Notification;
24 import android.app.NotificationChannel;
25 import android.app.NotificationManager;
26 import android.app.PendingIntent;
27 import android.app.usage.UsageEvents;
28 import android.app.usage.UsageEvents.Event;
29 import android.app.usage.UsageStatsManager;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.metrics.LogMaker;
33 import android.provider.Settings;
34 import android.service.notification.StatusBarNotification;
35 import android.support.test.metricshelper.MetricsAsserts;
36 import android.support.test.uiautomator.By;
37 import android.support.test.uiautomator.Direction;
38 import android.support.test.uiautomator.UiDevice;
39 import android.support.test.uiautomator.UiObject2;
40 import android.support.test.uiautomator.Until;
41 import android.test.InstrumentationTestCase;
42 import android.test.suitebuilder.annotation.MediumTest;
43 import android.util.Log;
44 
45 import android.metrics.MetricsReader;
46 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
47 
48 import java.text.MessageFormat;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Queue;
55 import org.junit.Test;
56 
57 public class NotificationInteractionTests extends InstrumentationTestCase {
58     private static final String LOG_TAG = NotificationInteractionTests.class.getSimpleName();
59     private static final int LONG_TIMEOUT = 3000;
60     private static final int SHORT_TIMEOUT = 200;
61     private static final int GROUP_NOTIFICATION_ID = 1;
62     private static final int CHILD_NOTIFICATION_ID = 100;
63     private static final int SECOND_CHILD_NOTIFICATION_ID = 101;
64     private static final String BUNDLE_GROUP_KEY = "group key ";
65     private static final String CHANNEL_ID = "my_channel";
66     private final boolean DEBUG = false;
67     private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} " +
68             AppOpsManager.OPSTR_GET_USAGE_STATS + " {1}";
69     private NotificationManager mNotificationManager;
70     private UiDevice mDevice = null;
71     private Context mContext;
72     private NotificationHelper mHelper;
73     private static final int CUSTOM_NOTIFICATION_ID = 1;
74     private static final int NOTIFICATIONS_COUNT = 3;
75     private MetricsReader mMetricsReader;
76 
77     @Override
setUp()78     public void setUp() throws Exception {
79         super.setUp();
80         mDevice = UiDevice.getInstance(getInstrumentation());
81         mContext = getInstrumentation().getContext();
82         mNotificationManager = (NotificationManager) mContext
83                 .getSystemService(Context.NOTIFICATION_SERVICE);
84         mHelper = new NotificationHelper(mDevice, getInstrumentation(), mNotificationManager);
85         mDevice.setOrientationNatural();
86         mNotificationManager.cancelAll();
87         mMetricsReader = new MetricsReader();
88         mMetricsReader.checkpoint(); // clear out old logs
89     }
90 
91     @Override
tearDown()92     public void tearDown() throws Exception {
93         super.tearDown();
94         mDevice.unfreezeRotation();
95         mDevice.pressHome();
96         mNotificationManager.cancelAll();
97     }
98 
99     @MediumTest
testNonDismissNotification()100     public void testNonDismissNotification() throws Exception {
101         String text = "USB debugging connected";
102         mDevice.openNotification();
103         Thread.sleep(LONG_TIMEOUT);
104         UiObject2 obj = findByText(text);
105         assertNotNull(String.format("Couldn't find %s notification", text), obj);
106         obj.swipe(Direction.LEFT, 1.0f);
107         Thread.sleep(LONG_TIMEOUT);
108         obj = mDevice.wait(Until.findObject(By.text(text)),
109                 LONG_TIMEOUT);
110         assertNotNull("USB debugging notification has been dismissed", obj);
111     }
112 
113     /** send out multiple notifications in order to test CLEAR ALL function */
114     @MediumTest
testDismissAll()115     public void testDismissAll() throws Exception {
116         String text = "Clear all";
117         Map<Integer, String> lists = new HashMap<Integer, String>();
118         StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
119         int currentSbns = sbns.length;
120         for (int i = 0; i < NOTIFICATIONS_COUNT; i++) {
121             lists.put(CUSTOM_NOTIFICATION_ID + i, Integer.toString(CUSTOM_NOTIFICATION_ID + i));
122         }
123         mHelper.sendNotifications(lists, false);
124 
125         if (DEBUG) {
126             Log.d(LOG_TAG,
127                     String.format("posted %s notifications, here they are: ", NOTIFICATIONS_COUNT));
128             sbns = mNotificationManager.getActiveNotifications();
129             for (StatusBarNotification sbn : sbns) {
130                 Log.d(LOG_TAG, "  " + sbn);
131             }
132         }
133         if (mDevice.openNotification()) {
134             Thread.sleep(LONG_TIMEOUT);
135             UiObject2 clearAll = findByText(text);
136             assertNotNull("could not find clear all target", clearAll);
137             clearAll.click();
138         }
139         Thread.sleep(LONG_TIMEOUT);
140         sbns = mNotificationManager.getActiveNotifications();
141         assertTrue(String.format("%s notifications have not been cleared", sbns.length),
142                 sbns.length == currentSbns);
143         MetricsAsserts.assertHasLog("missing notification cancel log", mMetricsReader,
144                 new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
145                         .setType(MetricsEvent.TYPE_DISMISS)
146                         .addTaggedData(MetricsEvent.NOTIFICATION_ID, CUSTOM_NOTIFICATION_ID)
147                         .setPackageName(mContext.getPackageName()));
148         MetricsAsserts.assertHasActionLog("missing dismiss-all log", mMetricsReader,
149                 MetricsEvent.ACTION_DISMISS_ALL_NOTES);
150     }
151 
152     /** send notifications, then open and close the shade to test visibility metrics. */
153     @MediumTest
testNotificationShadeMetrics()154     public void testNotificationShadeMetrics() throws Exception {
155         Map<Integer, String> lists = new HashMap<Integer, String>();
156         int firstId = CUSTOM_NOTIFICATION_ID;
157         int secondId = CUSTOM_NOTIFICATION_ID + 1;
158         lists.put(firstId, Integer.toString(firstId));
159         lists.put(secondId, Integer.toString(secondId));
160         // post
161         mHelper.sendNotifications(lists, true);
162         Thread.sleep(LONG_TIMEOUT);
163         // update
164         mHelper.sendNotifications(lists, true);
165 
166         if (mDevice.openNotification()) {
167             Thread.sleep(LONG_TIMEOUT);
168         }
169         MetricsAsserts.assertHasVisibilityLog("missing panel revealed log", mMetricsReader,
170                 MetricsEvent.NOTIFICATION_PANEL, true);
171         Queue<LogMaker> firstLog = MetricsAsserts.findMatchingLogs(mMetricsReader,
172                 new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
173                         .setType(MetricsEvent.TYPE_OPEN)
174                         .addTaggedData(MetricsEvent.NOTIFICATION_ID, firstId)
175                         .setPackageName(mContext.getPackageName()));
176         assertTrue("missing first note visibility log", !firstLog.isEmpty());
177         Queue<LogMaker> secondLog = MetricsAsserts.findMatchingLogs(mMetricsReader,
178                 new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
179                         .setType(MetricsEvent.TYPE_OPEN)
180                         .addTaggedData(MetricsEvent.NOTIFICATION_ID, secondId));
181         assertTrue("missing second note visibility log", !secondLog.isEmpty());
182         int firstRank = (Integer) firstLog.peek()
183                 .getTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX);
184         int secondRank = (Integer) secondLog.peek()
185                 .getTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX);
186         assertTrue("note must have distinct ranks", firstRank != secondRank);
187         int lifespan = (Integer) firstLog.peek()
188                 .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS);
189         int freshness = (Integer) firstLog.peek()
190                 .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_UPDATE_MILLIS);
191         int exposure = (Integer) firstLog.peek()
192                 .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
193         assertTrue("first note updated before it was created", lifespan >  freshness);
194         assertTrue("first note visible before it was updated", freshness >  exposure);
195         assertTrue("first note visibility log should have zero exposure time", exposure == 0);
196         int secondLifespan = (Integer) secondLog.peek()
197                 .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_CREATE_MILLIS);
198         assertTrue("first note created after second note", lifespan >  secondLifespan);
199 
200         mMetricsReader.checkpoint(); // clear out old logs again
201         firstLog.clear();
202         secondLog.clear();
203         // close the shade
204         if (mDevice.pressHome()) {
205             Thread.sleep(LONG_TIMEOUT);
206         }
207 
208         MetricsAsserts.assertHasVisibilityLog("missing panel hidden log", mMetricsReader,
209                 MetricsEvent.NOTIFICATION_PANEL, false);
210         firstLog = MetricsAsserts.findMatchingLogs(mMetricsReader,
211                 new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
212                         .setType(MetricsEvent.TYPE_CLOSE)
213                         .addTaggedData(MetricsEvent.NOTIFICATION_ID, firstId)
214                         .setPackageName(mContext.getPackageName()));
215         assertTrue("missing first note hidden log", !firstLog.isEmpty());
216         exposure = (Integer) firstLog.peek()
217                 .getTaggedData(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
218         assertTrue("first note visibility log should have nonzero exposure time", exposure > 0);
219         secondLog = MetricsAsserts.findMatchingLogs(mMetricsReader,
220                 new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
221                         .setType(MetricsEvent.TYPE_CLOSE)
222                         .addTaggedData(MetricsEvent.NOTIFICATION_ID, secondId)
223                         .setPackageName(mContext.getPackageName()));
224         assertTrue("missing second note hidden log", !secondLog.isEmpty());
225     }
226 
227     /** send a notification, click on first it. */
228     @MediumTest
testNotificationClicks()229     public void testNotificationClicks() throws Exception {
230         int id = CUSTOM_NOTIFICATION_ID;
231         mHelper.sendNotification(id, Notification.VISIBILITY_PUBLIC,
232                 NotificationHelper.CONTENT_TITLE, true);
233 
234         UiObject2 target = null;
235         if (mDevice.openNotification()) {
236             target = mDevice.wait(
237                     Until.findObject(By.text(NotificationHelper.FIRST_ACTION)),
238                     LONG_TIMEOUT);
239             assertNotNull("could not find first action button", target);
240             target.click();
241         }
242         Thread.sleep(SHORT_TIMEOUT);
243         MetricsAsserts.assertHasLog("missing notification alert log", mMetricsReader,
244                 new LogMaker(MetricsEvent.NOTIFICATION_ALERT)
245                         .setType(MetricsEvent.TYPE_OPEN)
246                         .addTaggedData(MetricsEvent.NOTIFICATION_ID, id)
247                         .setSubtype(MetricsEvent.ALERT_BUZZ) // no BEEP or BLINK
248                         .setPackageName(mContext.getPackageName()));
249         MetricsAsserts.assertHasLog("missing notification action 0 click log", mMetricsReader,
250                 new LogMaker(MetricsEvent.NOTIFICATION_ITEM_ACTION)
251                         .setType(MetricsEvent.TYPE_ACTION)
252                         .addTaggedData(MetricsEvent.NOTIFICATION_ID, id)
253                         .setSubtype(0)  // first action button, zero indexed
254                         .setPackageName(mContext.getPackageName()));
255 
256         mMetricsReader.checkpoint(); // clear out old logs again
257         target = mDevice.wait(Until.findObject(By.text(NotificationHelper.SECOND_ACTION)),
258                 LONG_TIMEOUT);
259         assertNotNull("could not find second action button", target);
260         target.click();
261         Thread.sleep(SHORT_TIMEOUT);
262         MetricsAsserts.assertHasLog("missing notification action 1 click log", mMetricsReader,
263                 new LogMaker(MetricsEvent.NOTIFICATION_ITEM_ACTION)
264                         .setType(MetricsEvent.TYPE_ACTION)
265                         .addTaggedData(MetricsEvent.NOTIFICATION_ID, id)
266                         .setSubtype(1)  // second action button, zero indexed
267                         .setPackageName(mContext.getPackageName()));
268 
269         mMetricsReader.checkpoint(); // clear out old logs again\
270         target = mDevice.wait(Until.findObject(By.text(NotificationHelper.CONTENT_TITLE)),
271                 LONG_TIMEOUT);
272         assertNotNull("could not find content click target", target);
273         target.click();
274         Thread.sleep(SHORT_TIMEOUT);
275         MetricsAsserts.assertHasLog("missing notification content click log", mMetricsReader,
276                 new LogMaker(MetricsEvent.NOTIFICATION_ITEM)
277                         .setType(MetricsEvent.TYPE_ACTION)
278                         .addTaggedData(MetricsEvent.NOTIFICATION_ID, id)
279                         .setPackageName(mContext.getPackageName()));
280     }
281 
282     @MediumTest
testReceiveAndExpandRedactNotification()283     public void testReceiveAndExpandRedactNotification() throws Exception {
284         List<Integer> lists = new ArrayList<Integer>(Arrays.asList(GROUP_NOTIFICATION_ID,
285                 CHILD_NOTIFICATION_ID, SECOND_CHILD_NOTIFICATION_ID));
286         mHelper.sendBundlingNotifications(lists, BUNDLE_GROUP_KEY);
287         Thread.sleep(LONG_TIMEOUT);
288         mDevice.openNotification();
289         UiObject2 notification = mDevice.wait(
290                 Until.findObject(By.text(lists.get(1).toString())),
291                 LONG_TIMEOUT * 2);
292         assertNotNull("The second notification has not been found", notification);
293         int currentY = notification.getVisibleCenter().y;
294         mDevice.wait(Until.findObject(By.res("android:id/expand_button")), LONG_TIMEOUT * 2)
295                 .click();
296         Thread.sleep(LONG_TIMEOUT);
297         notification = mDevice.wait(Until.findObject(By.text(lists.get(1).toString())),
298                 LONG_TIMEOUT);
299         assertFalse("The notifications has not been bundled",
300                 notification.getVisibleCenter().y == currentY);
301         mDevice.wait(Until.findObject(By.res("android:id/expand_button")), LONG_TIMEOUT).click();
302         Thread.sleep(LONG_TIMEOUT);
303         notification = mDevice.wait(Until.findObject(By.text(lists.get(1).toString())),
304                 LONG_TIMEOUT * 2);
305         assertTrue("The notifications can not be redacted",
306                 notification.getVisibleCenter().y == currentY);
307         mNotificationManager.cancelAll();
308     }
309 
setAppOpsMode(String mode)310     private void setAppOpsMode(String mode) throws Exception {
311         final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND,
312                 getInstrumentation().getContext().getPackageName(), mode);
313         mDevice.executeShellCommand(command);
314     }
315 
316     @MediumTest
testNotificationClickedEvents()317     public void testNotificationClickedEvents() throws Exception {
318         UsageStatsManager usm = (UsageStatsManager) getInstrumentation()
319                 .getContext().getSystemService(Context.USAGE_STATS_SERVICE);
320         setAppOpsMode("allow");
321         final long startTime = System.currentTimeMillis();
322         Context context = getInstrumentation().getContext();
323         NotificationManager mNotificationManager =
324             (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
325         int importance = NotificationManager.IMPORTANCE_DEFAULT;
326         NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, "Channel",
327                 importance);
328         // Configure the notification channel.
329         mChannel.setDescription("Test channel");
330         mNotificationManager.createNotificationChannel(mChannel);
331         Notification.Builder mBuilder =
332                 new Notification.Builder(context, CHANNEL_ID)
333                     .setSmallIcon(R.drawable.stat_notify_email)
334                     .setContentTitle("My notification")
335                     .setContentText("Hello World!");
336         PendingIntent pi = PendingIntent.getActivity(context, 1,
337                 new Intent(Settings.ACTION_SETTINGS), 0);
338         mBuilder.setContentIntent(pi);
339         mNotificationManager.notify(1, mBuilder.build());
340         Thread.sleep(500);
341         long endTime = System.currentTimeMillis();
342 
343         // Pull down shade
344         mDevice.openNotification();
345         UiObject2 notification = mDevice.wait(
346                 Until.findObject(By.text("My notification")),LONG_TIMEOUT * 2);
347         notification.click();
348         boolean found = false;
349 
350         outer:
351         for (int i = 0; i < 5; i++) {
352             Thread.sleep(500);
353             endTime = System.currentTimeMillis();
354             UsageEvents events = usm.queryEvents(startTime, endTime);
355             UsageEvents.Event event = new UsageEvents.Event();
356             while (events.hasNextEvent()) {
357                 events.getNextEvent(event);
358                 if (event.mEventType == Event.USER_INTERACTION) {
359                     found = true;
360                     break outer;
361                 }
362             }
363         }
364         mDevice.pressHome();
365         assertTrue(found);
366     }
367 
findByText(String text)368     private UiObject2 findByText(String text) throws Exception {
369         int maxAttempt = 5;
370         UiObject2 item = null;
371         while (maxAttempt-- > 0) {
372             item = mDevice.wait(Until.findObject(By.text(text)), LONG_TIMEOUT);
373             if (item == null) {
374                 mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight() / 2,
375                         mDevice.getDisplayWidth() / 2, 0, 30);
376             } else {
377                 return item;
378             }
379         }
380         return null;
381     }
382 }
383