Commit 8054ad8c authored by Xing Liu's avatar Xing Liu Committed by Commit Bot

Fix NotificationIntentInterceptorTest.

NotificationIntentInterceptorTest is used to be an instrumentation
test that calls into real Android notification API to show notification
in tray. This is super flaky due to unexpected behavior or bugs in
Android code.

Now we change this to junit tests with Robolectric shadows which is
much more flexible than current instrumentation setup. Also improve the
test coverage.

Bug: 910870
Change-Id: Ifd759b57c82010349316f2b6a928ad67799348ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2045142Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarPeter Beverloo <peter@chromium.org>
Commit-Queue: Xing Liu <xingliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#740775}
parent 830acea3
......@@ -114,6 +114,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/night_mode/GlobalNightModeStateControllerTest.java",
"junit/src/org/chromium/chrome/browser/night_mode/GlobalNightModeStateProviderHolderTest.java",
"junit/src/org/chromium/chrome/browser/night_mode/NightModeReparentingControllerTest.java",
"junit/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptorTest.java",
"junit/src/org/chromium/chrome/browser/notifications/NotificationSystemStatusUtilUnitTest.java",
"junit/src/org/chromium/chrome/browser/notifications/NotificationTriggerBackgroundTaskTest.java",
"junit/src/org/chromium/chrome/browser/notifications/NotificationTriggerSchedulerTest.java",
......
......@@ -246,7 +246,6 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilderTest.java",
"javatests/src/org/chromium/chrome/browser/notifications/CustomNotificationBuilderTest.java",
"javatests/src/org/chromium/chrome/browser/notifications/NotificationBuilderBaseTest.java",
"javatests/src/org/chromium/chrome/browser/notifications/NotificationIntentInterceptorTest.java",
"javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeIntentTest.java",
"javatests/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeTest.java",
"javatests/src/org/chromium/chrome/browser/notifications/NotificationTestRule.java",
......
......@@ -37,6 +37,8 @@ public class NotificationIntentInterceptor {
"notifications.NotificationIntentInterceptor.EXTRA_ACTION_TYPE";
private static final String EXTRA_CREATE_TIME =
"notifications.NotificationIntentInterceptor.EXTRA_CREATE_TIME";
public static final String INTENT_ACTION =
"notifications.NotificationIntentInterceptor.INTENT_ACTION";
public static final long INVALID_CREATE_TIME = -1;
/**
......@@ -115,6 +117,7 @@ public class NotificationIntentInterceptor {
}
Context applicationContext = ContextUtils.getApplicationContext();
Intent intent = new Intent(applicationContext, Receiver.class);
intent.setAction(INTENT_ACTION);
intent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
intent.putExtra(EXTRA_INTENT_TYPE, intentType);
intent.putExtra(EXTRA_NOTIFICATION_TYPE, metadata.type);
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.notifications;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.ContextUtils;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
/**
* Test to verify intercepting notification pending intents with broadcast receiver.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class NotificationIntentInterceptorTest {
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
private static final String TEST_NOTIFICATION_TITLE = "Test notification title.";
private static final long WAIT_FOR_NOTIFICATION_SHOWN_TIMEOUT_MILLISECONDS = 3000;
@Before
public void setUp() throws Exception {
mActivityTestRule.startMainActivityOnBlankPage();
}
// Builds a simple notification used in tests.
private Notification buildSimpleNotification(String title) {
ChromeNotificationBuilder builder =
NotificationBuilderFactory.createChromeNotificationBuilder(true /* preferCompat */,
ChannelDefinitions.ChannelId.DOWNLOADS, null /* remoteAppPackageName */,
NotificationTestUtil.getTestNotificationMetadata());
// Set content intent. UI automator may tap the notification and expand the action buttons,
// in order to reduce flakiness, don't add action button.
Context context = ContextUtils.getApplicationContext();
Intent contentIntent = new Intent(context, ChromeTabbedActivity.class);
Uri uri = Uri.parse("www.example.com");
contentIntent.setData(uri);
contentIntent.setAction(Intent.ACTION_VIEW);
PendingIntentProvider contentPendingIntent =
PendingIntentProvider.getActivity(context, 0, contentIntent, 0);
assert contentPendingIntent != null;
builder.setContentIntent(contentPendingIntent);
builder.setContentTitle(title);
builder.setSmallIcon(R.drawable.offline_pin);
return builder.build();
}
/**
* Clicks the notification with UI automator. Notice the notification bar is not part of the
* app, so we have to use UI automator.
* @param text The text of notification UI.
*/
private void clickNotification(String text) {
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
device.openNotification();
device.wait(Until.hasObject(By.textContains(text)),
WAIT_FOR_NOTIFICATION_SHOWN_TIMEOUT_MILLISECONDS);
UiObject2 textObject = device.findObject(By.textContains(text));
Assert.assertEquals(text, textObject.getText());
textObject.click();
}
/**
* Verifies {@link Notification#contentIntent} can be intercepted by broadcast receiver.
* Action button and dismiss have no test coverage due to difficulty in simulation with UI
* automator. On different Android version, the way to dismiss or find the action button can be
* different.
*/
@Test
@DisabledTest(message = "https://crbug.com/910870")
@MediumTest
@RetryOnFailure
@MinAndroidSdkLevel(Build.VERSION_CODES.LOLLIPOP)
public void testContentIntentInterception() {
// Send notification.
NotificationManager notificationManager =
(NotificationManager) ContextUtils.getApplicationContext().getSystemService(
Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
notificationManager.notify(0, buildSimpleNotification(TEST_NOTIFICATION_TITLE));
// Click notification body.
clickNotification(TEST_NOTIFICATION_TITLE);
// Wait for another tab to load.
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
return mActivityTestRule.tabsCount(false) > 1;
}
});
notificationManager.cancelAll();
}
}
......@@ -43,11 +43,6 @@ public class NotificationTestUtil {
return ((BitmapDrawable) icon.loadDrawable(context)).getBitmap();
}
public static NotificationMetadata getTestNotificationMetadata() {
return new NotificationMetadata(
NotificationUmaTracker.SystemNotificationType.UNKNOWN, null, 0);
}
@SuppressLint("NewApi") // Notification.actions is hidden in Jellybean
static Notification.Action[] getActions(Notification notification) {
return notification.actions;
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.notifications;
import static org.robolectric.Shadows.shadowOf;
import static org.chromium.chrome.browser.notifications.NotificationIntentInterceptor.INTENT_ACTION;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.test.core.app.ApplicationProvider;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLog;
import org.robolectric.shadows.ShadowNotificationManager;
import org.robolectric.shadows.ShadowPendingIntent;
import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.test.ShadowRecordHistogram;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
import org.chromium.testing.local.LocalRobolectricTestRunner;
/**
* Test to verify {@link NotificationIntentInterceptor} can intercept the {@link PendingIntent} and
* track metrics correctly.
*/
@RunWith(LocalRobolectricTestRunner.class)
@Config(shadows = {ShadowNotificationManager.class, ShadowPendingIntent.class,
ShadowRecordHistogram.class})
public class NotificationIntentInterceptorTest {
private static final String TEST_NOTIFICATION_TITLE = "Test notification title";
private static final String TEST_NOTIFICATION_ACTION_TITLE = "Test notification action title";
private static final String EXTRA_INTENT_TYPE =
"NotificationIntentInterceptorTest.EXTRA_INTENT_TYPE";
private Context mContext;
private ShadowNotificationManager mShadowNotificationManager;
private TestReceiver mReceiver;
/**
* When the user clicks the notification, the intent will go through {@link
* NotificationIntentInterceptor.Receiver} to track metrics and then arrive at this {@link
* BroadcastReceiver}.
*/
public static final class TestReceiver extends BroadcastReceiver {
private static final String TEST_ACTION =
"NotificationIntentInterceptorTest.TestReceiver.TEST_ACTION";
private Intent mIntentReceived;
public Intent intentReceived() {
return mIntentReceived;
}
@Override
public void onReceive(Context context, Intent intent) {
mIntentReceived = intent;
}
}
@Before
public void setUp() throws Exception {
ShadowLog.stream = System.out;
ShadowRecordHistogram.reset();
mContext = ApplicationProvider.getApplicationContext();
ContextUtils.initApplicationContextForTests(mContext);
mShadowNotificationManager = shadowOf(
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE));
mContext.registerReceiver(
new NotificationIntentInterceptor.Receiver(), new IntentFilter(INTENT_ACTION));
mReceiver = new TestReceiver();
mContext.registerReceiver(mReceiver, new IntentFilter(TestReceiver.TEST_ACTION));
}
@After
public void tearDown() {
ShadowRecordHistogram.reset();
}
// Builds a simple notification used in tests.
private ChromeNotification buildSimpleNotification(String title) {
NotificationMetadata metaData = new NotificationMetadata(
NotificationUmaTracker.SystemNotificationType.DOWNLOAD_FILES, null, 0);
ChromeNotificationBuilder builder =
NotificationBuilderFactory.createChromeNotificationBuilder(true /* preferCompat */,
ChannelDefinitions.ChannelId.DOWNLOADS, null /* remoteAppPackageName */,
metaData);
// Set content intent.
Intent contentIntent = new Intent(TestReceiver.TEST_ACTION);
contentIntent.putExtra(
EXTRA_INTENT_TYPE, NotificationIntentInterceptor.IntentType.CONTENT_INTENT);
PendingIntentProvider contentPendingIntent =
PendingIntentProvider.getBroadcast(mContext, 0, contentIntent, 0);
builder.setContentIntent(contentPendingIntent);
builder.setContentTitle(title);
builder.setSmallIcon(R.drawable.offline_pin);
// Add a button.
Intent actionIntent = new Intent(TestReceiver.TEST_ACTION);
actionIntent.putExtra(
EXTRA_INTENT_TYPE, NotificationIntentInterceptor.IntentType.ACTION_INTENT);
// Need to use a different request code here since content intent and action intent shares
// the same broadcast receiver.
PendingIntentProvider actionPendingIntent =
PendingIntentProvider.getBroadcast(mContext, /*requestCode=*/1, actionIntent, 0);
builder.addAction(0, TEST_NOTIFICATION_ACTION_TITLE, actionPendingIntent,
NotificationUmaTracker.ActionType.DOWNLOAD_PAUSE);
return builder.buildChromeNotification();
}
/**
* Verifies {@link Notification#contentIntent} can be intercepted by {@link
* NotificationIntentInterceptor}.
*/
@Test
public void testContentIntentInterception() throws Exception {
// Send notification.
new NotificationManagerProxyImpl(mContext).notify(
buildSimpleNotification(TEST_NOTIFICATION_TITLE));
// Simulates a notification click.
Notification notification = mShadowNotificationManager.getAllNotifications().get(0);
Assert.assertEquals(TEST_NOTIFICATION_TITLE,
notification.extras.getCharSequence(Notification.EXTRA_TITLE).toString());
notification.contentIntent.send();
Robolectric.flushForegroundThreadScheduler();
// Verify the intent and histograms recorded.
Intent receivedIntent = mReceiver.intentReceived();
Assert.assertEquals(receivedIntent.getExtras().getInt(EXTRA_INTENT_TYPE),
NotificationIntentInterceptor.IntentType.CONTENT_INTENT);
Assert.assertEquals(1,
ShadowRecordHistogram.getHistogramValueCountForTesting(
"Mobile.SystemNotification.Content.Click",
NotificationUmaTracker.SystemNotificationType.DOWNLOAD_FILES));
}
/**
* Verifies {@link Notification#deleteIntent} can be intercepted by {@link
* NotificationIntentInterceptor}.
*/
@Test
public void testDeleteIntentInterception() throws Exception {
// Send notification.
new NotificationManagerProxyImpl(mContext).notify(
buildSimpleNotification(TEST_NOTIFICATION_TITLE));
// Simulates a notification cancel.
Notification notification = mShadowNotificationManager.getAllNotifications().get(0);
Assert.assertEquals(TEST_NOTIFICATION_TITLE,
notification.extras.getCharSequence(Notification.EXTRA_TITLE).toString());
notification.deleteIntent.send();
Robolectric.flushForegroundThreadScheduler();
// Verify the histogram.
Assert.assertEquals(1,
ShadowRecordHistogram.getHistogramValueCountForTesting(
"Mobile.SystemNotification.Dismiss",
NotificationUmaTracker.SystemNotificationType.DOWNLOAD_FILES));
Assert.assertNull(mReceiver.intentReceived());
}
/**
* Verifies button clicks can be intercepted by {@link NotificationIntentInterceptor}.
*/
@Test
public void testActionIntentInterception() throws Exception {
// Send notification.
new NotificationManagerProxyImpl(mContext).notify(
buildSimpleNotification(TEST_NOTIFICATION_TITLE));
// Simulates a button click.
Notification notification = mShadowNotificationManager.getAllNotifications().get(0);
Assert.assertEquals(TEST_NOTIFICATION_TITLE,
notification.extras.getCharSequence(Notification.EXTRA_TITLE).toString());
Assert.assertNotNull(notification.actions);
Assert.assertEquals(1, notification.actions.length);
Notification.Action action = notification.actions[0];
Assert.assertNotNull(action.actionIntent);
action.actionIntent.send();
Robolectric.flushForegroundThreadScheduler();
// Verify the intent and histograms recorded.
Intent receivedIntent = mReceiver.intentReceived();
Assert.assertEquals(NotificationIntentInterceptor.IntentType.ACTION_INTENT,
receivedIntent.getExtras().getInt(EXTRA_INTENT_TYPE));
Assert.assertEquals(1,
ShadowRecordHistogram.getHistogramValueCountForTesting(
"Mobile.SystemNotification.Action.Click",
NotificationUmaTracker.ActionType.DOWNLOAD_PAUSE));
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment