blob: f109e0e3864abf3787ee06b870ee4325cb43170a [file] [log] [blame]
// Copyright 2015 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.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
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.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
import org.chromium.content_public.browser.test.NativeLibraryTestRule;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Instrumentation unit tests for CustomNotificationBuilder.
*/
@RunWith(BaseJUnit4ClassRunner.class)
public class CustomNotificationBuilderTest {
@Rule
public NativeLibraryTestRule mActivityTestRule = new NativeLibraryTestRule();
@Before
public void setUp() throws Exception {
mActivityTestRule.loadNativeLibraryNoBrowserProcess();
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@SuppressLint("NewApi")
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void testSetAll() {
Context context = InstrumentationRegistry.getTargetContext();
PendingIntent contentIntent = createIntent(context, "Content");
PendingIntent deleteIntent = createIntent(context, "Delete");
Bitmap smallIcon =
BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_chrome);
Bitmap largeIcon = createIcon(Color.RED);
Bitmap actionIcon = createIcon(Color.WHITE);
Notification notification = new CustomNotificationBuilder(context)
.setSmallIconId(R.drawable.ic_chrome)
.setLargeIcon(largeIcon)
.setTitle("title")
.setBody("body")
.setOrigin("origin")
.setChannelId(ChannelDefinitions.ChannelId.SITES)
.setTicker("ticker")
.setDefaults(Notification.DEFAULT_ALL)
.setVibrate(new long[] {100L})
.setContentIntent(contentIntent)
.setDeleteIntent(deleteIntent)
.addButtonAction(actionIcon, "button",
createIntent(context, "ActionButtonOne"))
.addButtonAction(actionIcon, "button",
createIntent(context, "ActionButtonTwo"))
.addSettingsAction(0 /* iconId */, "settings",
createIntent(context, "SettingsButton"))
.build();
assertSmallNotificationIconAsExpected(context, notification, smallIcon);
assertLargeNotificationIconAsExpected(context, notification, largeIcon);
View compactView = notification.contentView.apply(context, new LinearLayout(context));
View bigView = notification.bigContentView.apply(context, new LinearLayout(context));
Assert.assertEquals("title", getIdenticalText(R.id.title, compactView, bigView));
Assert.assertEquals("body", getIdenticalText(R.id.body, compactView, bigView));
Assert.assertEquals("origin", getIdenticalText(R.id.origin, compactView, bigView));
Assert.assertEquals("title", NotificationTestUtil.getExtraTitle(notification));
Assert.assertEquals("body", NotificationTestUtil.getExtraText(notification));
Assert.assertEquals("origin", NotificationTestUtil.getExtraSubText(notification));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
Assert.assertEquals(
NotificationConstants.GROUP_WEB_PREFIX + "origin", notification.getGroup());
}
Assert.assertEquals("ticker", notification.tickerText.toString());
Assert.assertEquals(Notification.DEFAULT_ALL, notification.defaults);
Assert.assertEquals(1, notification.vibrate.length);
Assert.assertEquals(100L, notification.vibrate[0]);
Assert.assertSame(contentIntent, notification.contentIntent);
Assert.assertSame(deleteIntent, notification.deleteIntent);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// Notification.publicVersion was added in Android L.
Assert.assertNotNull(notification.publicVersion);
Assert.assertEquals("origin",
Build.VERSION.SDK_INT <= Build.VERSION_CODES.M
? NotificationTestUtil.getExtraTitle(notification.publicVersion)
: NotificationTestUtil.getExtraSubText(notification.publicVersion));
}
// The regular actions and the settings action are added together in the notification
// actions array, so they can be exposed on e.g. Wear and custom lockscreens.
Assert.assertEquals(3, NotificationTestUtil.getActions(notification).length);
ArrayList<View> buttons = new ArrayList<>();
bigView.findViewsWithText(buttons, "button", View.FIND_VIEWS_WITH_TEXT);
Assert.assertEquals(2, buttons.size());
Assert.assertEquals(
View.VISIBLE, bigView.findViewById(R.id.button_divider).getVisibility());
Assert.assertEquals(View.VISIBLE, bigView.findViewById(R.id.buttons).getVisibility());
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void testZeroActionButtons() {
Context context = InstrumentationRegistry.getTargetContext();
Notification notification = new CustomNotificationBuilder(context)
.setChannelId(ChannelDefinitions.ChannelId.SITES)
.build();
View bigView = notification.bigContentView.apply(context, new LinearLayout(context));
ArrayList<View> buttons = new ArrayList<>();
bigView.findViewsWithText(buttons, "button", View.FIND_VIEWS_WITH_TEXT);
// When there are no buttons the container and divider must not be shown.
Assert.assertTrue(buttons.isEmpty());
Assert.assertEquals(View.GONE, bigView.findViewById(R.id.button_divider).getVisibility());
Assert.assertEquals(View.GONE, bigView.findViewById(R.id.buttons).getVisibility());
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void testMaxActionButtons() {
Context context = InstrumentationRegistry.getTargetContext();
NotificationBuilderBase builder = new CustomNotificationBuilder(context)
.setChannelId(ChannelDefinitions.ChannelId.SITES)
.addButtonAction(null /* iconBitmap */, "button",
createIntent(context, "ActionButtonOne"))
.addButtonAction(null /* iconBitmap */, "button",
createIntent(context, "ActionButtonTwo"));
try {
builder.addButtonAction(
null /* iconBitmap */, "button", createIntent(context, "ActionButtonThree"));
Assert.fail(
"This statement should not be reached as the previous statement should throw.");
} catch (IllegalStateException e) {
Assert.assertEquals("Cannot add more than 2 actions.", e.getMessage());
}
Notification notification = builder.build();
View bigView = notification.bigContentView.apply(context, new LinearLayout(context));
ArrayList<View> buttons = new ArrayList<>();
bigView.findViewsWithText(buttons, "button", View.FIND_VIEWS_WITH_TEXT);
Assert.assertEquals("There is a maximum of 2 buttons", 2, buttons.size());
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void largeIconShouldBePaintedWithoutChange() {
Context context = InstrumentationRegistry.getTargetContext();
Bitmap largeIcon = createIcon(Color.RED);
Notification notification = new CustomNotificationBuilder(context)
.setChannelId(ChannelDefinitions.ChannelId.SITES)
.setLargeIcon(largeIcon)
.setSmallIconId(R.drawable.ic_chrome)
.build();
assertLargeNotificationIconAsExpected(context, notification, largeIcon);
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
public void smallIconsArePaintedWhite() {
Context context = InstrumentationRegistry.getTargetContext();
Bitmap smallIcon = createIcon(Color.RED);
Notification notification = new CustomNotificationBuilder(context)
.setChannelId(ChannelDefinitions.ChannelId.SITES)
.setSmallIconForContent(smallIcon)
.setStatusBarIcon(smallIcon)
.build();
// Note that small icon as a Bitmap should be present on pre-M, even though it can't
// be shown in the status bar
assertSmallNotificationIconAsExpected(context, notification, createIcon(Color.WHITE));
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
public void actionIconsArePaintedWhite() {
Context context = InstrumentationRegistry.getTargetContext();
Bitmap actionIcon = createIcon(Color.RED);
Notification notification = new CustomNotificationBuilder(context)
.setChannelId(ChannelDefinitions.ChannelId.SITES)
.setSmallIconId(R.drawable.ic_chrome)
.addButtonAction(actionIcon, "button",
createIntent(context, "ActionButton"))
.build();
Bitmap whiteIcon = createIcon(Color.WHITE);
Assert.assertEquals(1, NotificationTestUtil.getActions(notification).length);
View bigView = notification.bigContentView.apply(context, new LinearLayout(context));
ImageView actionIconView = (ImageView) bigView.findViewById(R.id.button_icon);
Bitmap actionIconBitmap = ((BitmapDrawable) actionIconView.getDrawable()).getBitmap();
Assert.assertTrue(whiteIcon.sameAs(actionIconBitmap));
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 22)
public void statusBarIconAsBitmapIsIgnoredIfNotSupported() {
Context context = InstrumentationRegistry.getTargetContext();
NotificationBuilderBase notificationBuilder = new CustomNotificationBuilder(context)
.setStatusBarIcon(createIcon(Color.RED));
Assert.assertFalse(notificationBuilder.hasStatusBarIconBitmap());
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void testCharSequenceLimits() {
Context context = InstrumentationRegistry.getTargetContext();
int maxLength = CustomNotificationBuilder.MAX_CHARSEQUENCE_LENGTH;
Notification notification =
new CustomNotificationBuilder(context)
.setTitle(createString('a', maxLength + 1))
.setBody(createString('b', maxLength + 1))
.setOrigin(createString('c', maxLength + 1))
.setChannelId(ChannelDefinitions.ChannelId.SITES)
.setTicker(createString('d', maxLength + 1))
.addButtonAction(null /* iconBitmap */, createString('e', maxLength + 1),
createIntent(context, "ActionButtonOne"))
.build();
View compactView = notification.contentView.apply(context, new LinearLayout(context));
View bigView = notification.bigContentView.apply(context, new LinearLayout(context));
Assert.assertEquals(maxLength, getIdenticalText(R.id.title, compactView, bigView).length());
Assert.assertEquals(maxLength, getIdenticalText(R.id.body, compactView, bigView).length());
Assert.assertEquals(
maxLength, getIdenticalText(R.id.origin, compactView, bigView).length());
Assert.assertEquals(maxLength, notification.tickerText.length());
ArrayList<View> buttons = new ArrayList<>();
bigView.findViewsWithText(buttons, createString('e', maxLength), View.FIND_VIEWS_WITH_TEXT);
Assert.assertEquals(1, buttons.size());
Assert.assertEquals(maxLength, ((Button) buttons.get(0)).getText().length());
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void testCalculateMaxBodyLines() {
Assert.assertEquals(7, CustomNotificationBuilder.calculateMaxBodyLines(-1000.0f));
Assert.assertEquals(7, CustomNotificationBuilder.calculateMaxBodyLines(0.5f));
Assert.assertEquals(7, CustomNotificationBuilder.calculateMaxBodyLines(1.0f));
Assert.assertEquals(4, CustomNotificationBuilder.calculateMaxBodyLines(2.0f));
Assert.assertEquals(1, CustomNotificationBuilder.calculateMaxBodyLines(1000.0f));
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void testCalculateScaledPadding() {
DisplayMetrics metrics = new DisplayMetrics();
metrics.density = 10.0f;
Assert.assertEquals(
30, CustomNotificationBuilder.calculateScaledPadding(-1000.0f, metrics));
Assert.assertEquals(30, CustomNotificationBuilder.calculateScaledPadding(0.5f, metrics));
Assert.assertEquals(30, CustomNotificationBuilder.calculateScaledPadding(1.0f, metrics));
Assert.assertEquals(20, CustomNotificationBuilder.calculateScaledPadding(1.1f, metrics));
Assert.assertEquals(10, CustomNotificationBuilder.calculateScaledPadding(1.2f, metrics));
Assert.assertEquals(0, CustomNotificationBuilder.calculateScaledPadding(1.3f, metrics));
Assert.assertEquals(0, CustomNotificationBuilder.calculateScaledPadding(1000.0f, metrics));
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void testGeneratesLargeIconFromOriginWhenNoLargeIconProvided() {
Context context = InstrumentationRegistry.getTargetContext();
NotificationBuilderBase notificationBuilder =
new CustomNotificationBuilder(context)
.setOrigin("https://www.google.com")
.setChannelId(ChannelDefinitions.ChannelId.SITES);
Notification notification = notificationBuilder.build();
Bitmap expectedIcon = NotificationBuilderBase.createIconGenerator(context.getResources())
.generateIconForUrl("https://www.google.com");
assertLargeNotificationIconAsExpected(context, notification, expectedIcon);
}
@Test
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void testGeneratesLargeIconFromOriginWhenLargeIconProvidedIsNull() {
Context context = InstrumentationRegistry.getTargetContext();
NotificationBuilderBase notificationBuilder =
new CustomNotificationBuilder(context)
.setOrigin("https://www.chromium.org")
.setChannelId(ChannelDefinitions.ChannelId.SITES)
.setLargeIcon(null);
Notification notification = notificationBuilder.build();
Bitmap expectedIcon = NotificationBuilderBase.createIconGenerator(context.getResources())
.generateIconForUrl("https://www.chromium.org");
assertLargeNotificationIconAsExpected(context, notification, expectedIcon);
}
/**
* Tests that adding a text action results in a notification action with a RemoteInput attached.
*
* Note that the action buttons in custom layouts will not trigger an inline reply, but we can
* still check the notification's action properties since these are used on Android Wear.
*/
@Test
@MinAndroidSdkLevel(Build.VERSION_CODES.KITKAT_WATCH)
@TargetApi(Build.VERSION_CODES.KITKAT_WATCH) // RemoteInputs were only added in KITKAT_WATCH.
@SmallTest
@Feature({"Browser", "Notifications"})
@DisableIf.Build(sdk_is_greater_than = 23, message = "crbug.com/779228")
public void testAddTextActionSetsRemoteInput() {
Context context = InstrumentationRegistry.getTargetContext();
NotificationBuilderBase notificationBuilder =
new CustomNotificationBuilder(context)
.setChannelId(ChannelDefinitions.ChannelId.SITES)
.addTextAction(null, "Action Title", null, "Placeholder");
Notification notification = notificationBuilder.build();
Assert.assertEquals(1, notification.actions.length);
Assert.assertEquals("Action Title", notification.actions[0].title);
Assert.assertNotNull(notification.actions[0].getRemoteInputs());
Assert.assertEquals(1, notification.actions[0].getRemoteInputs().length);
Assert.assertEquals("Placeholder", notification.actions[0].getRemoteInputs()[0].getLabel());
}
private static void assertLargeNotificationIconAsExpected(
Context context, Notification notification, Bitmap expectedIcon) {
// 1. Check large icon property on the notification.
Bitmap icon = NotificationTestUtil.getLargeIconFromNotification(context, notification);
Assert.assertNotNull(icon);
Assert.assertTrue(expectedIcon.sameAs(icon));
// 2. Check the large icon in the custom layouts.
View compactView = notification.contentView.apply(context, new LinearLayout(context));
Drawable compactViewIcon = ((ImageView) compactView.findViewById(R.id.icon)).getDrawable();
Assert.assertNotNull(compactViewIcon);
Assert.assertTrue(expectedIcon.sameAs(((BitmapDrawable) compactViewIcon).getBitmap()));
View bigView = notification.bigContentView.apply(context, new LinearLayout(context));
Drawable bigViewIcon = ((ImageView) bigView.findViewById(R.id.icon)).getDrawable();
Assert.assertNotNull(bigViewIcon);
Assert.assertTrue(expectedIcon.sameAs(((BitmapDrawable) bigViewIcon).getBitmap()));
}
private static void assertSmallNotificationIconAsExpected(
Context context, Notification notification, Bitmap expectedIcon) {
// 1. Check small icon property on the notification, for M+.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Bitmap icon =
NotificationTestUtil.getBitmapFromIcon(context, notification.getSmallIcon());
Assert.assertNotNull(icon);
Assert.assertTrue(expectedIcon.sameAs(icon));
}
// 2. Check the small icon in the custom layouts.
int smallIconId = CustomNotificationBuilder.useMaterial() ? R.id.small_icon_overlay
: R.id.small_icon_footer;
View compactView = notification.contentView.apply(context, new LinearLayout(context));
Drawable compactViewIcon =
((ImageView) compactView.findViewById(smallIconId)).getDrawable();
Assert.assertNotNull(compactViewIcon);
Assert.assertTrue(expectedIcon.sameAs(((BitmapDrawable) compactViewIcon).getBitmap()));
View bigView = notification.bigContentView.apply(context, new LinearLayout(context));
Drawable bigViewIcon = ((ImageView) bigView.findViewById(smallIconId)).getDrawable();
Assert.assertNotNull(bigViewIcon);
Assert.assertTrue(expectedIcon.sameAs(((BitmapDrawable) bigViewIcon).getBitmap()));
}
/**
* Finds a TextView with the given id in each of the given views, and checks that they all
* contain the same text.
* @param id The id to find the TextView instances with.
* @param views The views to find the TextView instances in.
* @return The identical text.
*/
private CharSequence getIdenticalText(int id, View... views) {
CharSequence result = null;
for (View view : views) {
TextView textView = (TextView) view.findViewById(id);
Assert.assertNotNull(textView);
CharSequence text = textView.getText();
if (result == null) {
result = text;
} else {
Assert.assertEquals(result, text);
}
}
return result;
}
private static PendingIntent createIntent(Context context, String action) {
Intent intent = new Intent("CustomNotificationBuilderTest." + action);
return PendingIntent.getBroadcast(
context, 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private static String createString(char character, int length) {
char[] chars = new char[length];
Arrays.fill(chars, character);
return new String(chars);
}
private static Bitmap createIcon(int color) {
Bitmap icon = Bitmap.createBitmap(
new int[] {color}, 1 /* width */, 1 /* height */, Bitmap.Config.ARGB_8888);
return icon.copy(Bitmap.Config.ARGB_8888, true /* isMutable */);
}
}