blob: 1cee1210f1cced101b2e55cfb8de1e66061f0708 [file] [log] [blame]
// 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.keyboard_accessory.bar_component;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.longClick;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.matcher.ViewMatchers.isSelected;
import static androidx.test.espresso.matcher.ViewMatchers.withChild;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.chromium.chrome.browser.keyboard_accessory.AccessoryAction.AUTOFILL_SUGGESTION;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.BAR_ITEMS;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.DISABLE_ANIMATIONS_FOR_TESTING;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.KEYBOARD_TOGGLE_VISIBLE;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.OBFUSCATED_CHILD_AT_CALLBACK;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHEET_TITLE;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SHOW_SWIPING_IPH;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.TAB_LAYOUT_ITEM;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.VISIBLE;
import static org.chromium.ui.test.util.ViewUtils.onViewWaiting;
import static org.chromium.ui.test.util.ViewUtils.waitForView;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewStub;
import androidx.annotation.Nullable;
import androidx.test.espresso.ViewInteraction;
import androidx.test.espresso.matcher.RootMatchers;
import androidx.test.filters.MediumTest;
import com.google.android.material.tabs.TabLayout;
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.Callback;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.CriteriaNotSatisfiedException;
import org.chromium.chrome.browser.autofill.PersonalDataManager;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.keyboard_accessory.R;
import org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.AutofillBarItem;
import org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.BarItem;
import org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.TabLayoutBarItem;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.Action;
import org.chromium.chrome.browser.keyboard_accessory.tab_layout_component.KeyboardAccessoryTabLayoutCoordinator;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.autofill.AutofillSuggestion;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.feature_engagement.TriggerDetails;
import org.chromium.components.feature_engagement.TriggerState;
import org.chromium.content_public.browser.test.util.JavaScriptUtils;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.DeferredViewStubInflationProvider;
import org.chromium.ui.DropdownItem;
import org.chromium.ui.ViewProvider;
import org.chromium.ui.modelutil.LazyConstructionPropertyMcp;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.widget.ChipView;
import org.chromium.ui.widget.ChromeImageView;
import org.chromium.url.GURL;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/**
* View tests for the keyboard accessory component.
*
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@EnableFeatures(ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY)
public class KeyboardAccessoryModernViewTest {
private static final String CUSTOM_ICON_URL = "https://www.example.com/image.png";
private static final Bitmap TEST_CARD_ART_IMAGE =
Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
private PropertyModel mModel;
private BlockingQueue<KeyboardAccessoryModernView> mKeyboardAccessoryView;
@Rule
public ChromeTabbedActivityTestRule mActivityTestRule = new ChromeTabbedActivityTestRule();
@Mock
PersonalDataManager mMockPersonalDataManager;
private static class TestTracker implements Tracker {
private boolean mWasDismissed;
private @Nullable String mEmittedEvent;
@Override
public void notifyEvent(String event) {
mEmittedEvent = event;
}
public @Nullable String getLastEmittedEvent() {
return mEmittedEvent;
}
@Override
public boolean shouldTriggerHelpUI(String feature) {
return true;
}
@Override
public TriggerDetails shouldTriggerHelpUIWithSnooze(String feature) {
return null;
}
@Override
public boolean wouldTriggerHelpUI(String feature) {
return true;
}
@Override
public boolean hasEverTriggered(String feature, boolean fromWindow) {
return true;
}
@Override
public int getTriggerState(String feature) {
return TriggerState.HAS_NOT_BEEN_DISPLAYED;
}
@Override
public void dismissed(String feature) {
mWasDismissed = true;
}
@Override
public void dismissedWithSnooze(String feature, int snoozeAction) {
mWasDismissed = true;
}
public boolean wasDismissed() {
return mWasDismissed;
}
@Nullable
@Override
public DisplayLockHandle acquireDisplayLock() {
return () -> {};
}
@Override
public boolean isInitialized() {
return true;
}
@Override
public void addOnInitializedCallback(Callback<Boolean> callback) {
assert false : "Implement addOnInitializedCallback if you need it.";
}
}
@Before
public void setUp() throws InterruptedException {
MockitoAnnotations.initMocks(this);
mActivityTestRule.startMainActivityOnBlankPage();
PersonalDataManager.setInstanceForTesting(mMockPersonalDataManager);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel =
KeyboardAccessoryProperties.defaultModelBuilder()
.with(TAB_LAYOUT_ITEM,
new TabLayoutBarItem(new KeyboardAccessoryTabLayoutCoordinator
.TabLayoutCallbacks() {
@Override
public void onTabLayoutBound(
TabLayout tabs) {}
@Override
public void onTabLayoutUnbound(
TabLayout tabs) {}
}))
.with(DISABLE_ANIMATIONS_FOR_TESTING, true)
.with(OBFUSCATED_CHILD_AT_CALLBACK, unused -> {})
.with(SHOW_SWIPING_IPH, false)
.build();
ViewStub viewStub =
mActivityTestRule.getActivity().findViewById(R.id.keyboard_accessory_stub);
mKeyboardAccessoryView = new ArrayBlockingQueue<>(1);
ViewProvider<KeyboardAccessoryModernView> provider =
new DeferredViewStubInflationProvider<>(viewStub);
LazyConstructionPropertyMcp.create(
mModel, VISIBLE, provider, KeyboardAccessoryModernViewBinder::bind);
provider.whenLoaded(mKeyboardAccessoryView::add);
});
}
@Test
@MediumTest
public void testAccessoryVisibilityChangedByModel() throws InterruptedException {
// Initially, there shouldn't be a view yet.
assertNull(mKeyboardAccessoryView.poll());
// After setting the visibility to true, the view should exist and be visible.
TestThreadUtils.runOnUiThreadBlocking(() -> { mModel.set(VISIBLE, true); });
KeyboardAccessoryModernView view = mKeyboardAccessoryView.take();
assertEquals(view.getVisibility(), View.VISIBLE);
// After hiding the view, the view should still exist but be invisible.
TestThreadUtils.runOnUiThreadBlocking(() -> { mModel.set(VISIBLE, false); });
assertNotEquals(view.getVisibility(), View.VISIBLE);
}
@Test
@MediumTest
public void testAddsClickableAutofillSuggestions() {
AtomicReference<Boolean> clickRecorded = new AtomicReference<>();
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(
createAutofillChipAndTab("Johnathan", result -> clickRecorded.set(true)));
});
onViewWaiting(withText("Johnathan")).perform(click());
assertTrue(clickRecorded.get());
}
@Test
@MediumTest
public void testAddsLongClickableAutofillSuggestions() {
AtomicReference<Boolean> clickRecorded = new AtomicReference<>();
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(new BarItem[] {
new AutofillBarItem(
new AutofillSuggestion("Johnathan", "Smith", "", DropdownItem.NO_ICON,
false, 1, false, false, false, /* featureForIPH= */ ""),
new Action("Unused", AUTOFILL_SUGGESTION,
result -> {}, result -> clickRecorded.set(true))),
createTabs()});
});
onViewWaiting(withText("Johnathan")).perform(longClick());
assertTrue(clickRecorded.get());
}
@Test
@MediumTest
public void testUpdatesKeyPaddingAfterRotation() throws InterruptedException {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.set(KEYBOARD_TOGGLE_VISIBLE, false);
mModel.set(SHEET_TITLE, "Sheet title");
mModel.get(BAR_ITEMS).set(createAutofillChipAndTab("John", null));
});
KeyboardAccessoryModernView view = mKeyboardAccessoryView.take();
CriteriaHelper.pollUiThread(view.mBarItemsView::isShown);
CriteriaHelper.pollUiThread(viewsAreRightAligned(view, view.mBarItemsView.getChildAt(1)));
TestThreadUtils.runOnUiThreadBlocking(() -> mModel.set(KEYBOARD_TOGGLE_VISIBLE, true));
CriteriaHelper.pollUiThread(() -> !view.mBarItemsView.isShown());
rotateActivityToLandscape();
TestThreadUtils.runOnUiThreadBlocking(() -> mModel.set(KEYBOARD_TOGGLE_VISIBLE, false));
CriteriaHelper.pollUiThread(view.mBarItemsView::isShown);
CriteriaHelper.pollUiThread(viewsAreRightAligned(view, view.mBarItemsView.getChildAt(1)));
// Reset device orientation.
mActivityTestRule.getActivity().setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
@Test
@MediumTest
public void testDismissesPasswordEducationBubbleOnFilling() {
AutofillBarItem itemWithIPH = new AutofillBarItem(
new AutofillSuggestion("Johnathan", "Smith", /*itemTag=*/"", DropdownItem.NO_ICON,
false, -2, false, false, false, /* featureForIPH= */ ""),
new KeyboardAccessoryData.Action("", AUTOFILL_SUGGESTION, unused -> {}));
itemWithIPH.setFeatureForIPH(FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE);
TestTracker tracker = new TestTracker();
TrackerFactory.setTrackerForTests(tracker);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(new BarItem[] {itemWithIPH, createTabs()});
});
onViewWaiting(withText("Johnathan"));
waitForHelpBubble(withText(R.string.iph_keyboard_accessory_fill_with_chrome));
onView(withChild(withText("Johnathan"))).check(matches(isSelected()));
onView(withText("Johnathan")).perform(click());
assertThat(tracker.wasDismissed(), is(true));
assertThat(tracker.getLastEmittedEvent(),
is(EventConstants.KEYBOARD_ACCESSORY_PASSWORD_AUTOFILLED));
onView(withChild(withText("Johnathan"))).check(matches(not(isSelected())));
}
@Test
@MediumTest
public void testDismissesAddressEducationBubbleOnFilling() {
AutofillBarItem itemWithIPH = new AutofillBarItem(
new AutofillSuggestion("Johnathan", "Smith", /*itemTag=*/"", DropdownItem.NO_ICON,
false, 1, false, false, false, /* featureForIPH= */ ""),
new KeyboardAccessoryData.Action("", AUTOFILL_SUGGESTION, unused -> {}));
itemWithIPH.setFeatureForIPH(FeatureConstants.KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE);
TestTracker tracker = new TestTracker();
TrackerFactory.setTrackerForTests(tracker);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(new BarItem[] {itemWithIPH, createTabs()});
});
onViewWaiting(withText("Johnathan"));
waitForHelpBubble(withText(R.string.iph_keyboard_accessory_fill_with_chrome));
onView(withText("Johnathan")).perform(click());
assertThat(tracker.wasDismissed(), is(true));
assertThat(tracker.getLastEmittedEvent(),
is(EventConstants.KEYBOARD_ACCESSORY_ADDRESS_AUTOFILLED));
}
@Test
@MediumTest
public void testDismissesPaymentEducationBubbleOnFilling() {
AutofillBarItem itemWithIPH = new AutofillBarItem(
new AutofillSuggestion("Johnathan", "Smith", /*itemTag=*/"", DropdownItem.NO_ICON,
false, 70000, false, false, false, /* featureForIPH= */ ""),
new KeyboardAccessoryData.Action("", AUTOFILL_SUGGESTION, unused -> {}));
itemWithIPH.setFeatureForIPH(FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE);
TestTracker tracker = new TestTracker();
TrackerFactory.setTrackerForTests(tracker);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(new BarItem[] {itemWithIPH, createTabs()});
});
onViewWaiting(withText("Johnathan"));
waitForHelpBubble(withText(R.string.iph_keyboard_accessory_fill_with_chrome));
onView(withText("Johnathan")).perform(click());
assertThat(tracker.wasDismissed(), is(true));
assertThat(tracker.getLastEmittedEvent(),
is(EventConstants.KEYBOARD_ACCESSORY_PAYMENT_AUTOFILLED));
}
@Test
@MediumTest
public void testDismissesSwipingEducationBubbleOnTap() {
TestTracker tracker = new TestTracker() {
@Override
public int getTriggerState(String feature) {
// Pretend that an autofill IPH was shown already.
return feature.equals(FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE)
? TriggerState.HAS_BEEN_DISPLAYED
: TriggerState.HAS_NOT_BEEN_DISPLAYED;
}
};
TrackerFactory.setTrackerForTests(tracker);
// Render a keyboard accessory bar and wait for completion.
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(createAutofillChipAndTab("Johnathan", null));
});
onViewWaiting(withText("Johnathan"));
// Pretend an item is offscreen, so swiping is possible and an IPH could be shown.
TestThreadUtils.runOnUiThreadBlocking(() -> mModel.set(SHOW_SWIPING_IPH, true));
// Wait until the bubble appears, then dismiss is by tapping it.
waitForHelpBubble(withText(R.string.iph_keyboard_accessory_swipe_for_more))
.perform(click());
assertThat(tracker.wasDismissed(), is(true));
}
@Test
@MediumTest
public void testDismissesPaymentOfferEducationBubbleOnFilling() {
String itemTag = "Cashback linked";
AutofillBarItem itemWithIPH = new AutofillBarItem(
new AutofillSuggestion("Johnathan", "Smith", itemTag, R.drawable.ic_offer_tag,
false, 70000, false, false, false, /* featureForIPH= */ ""),
new KeyboardAccessoryData.Action("", AUTOFILL_SUGGESTION, unused -> {}));
itemWithIPH.setFeatureForIPH(FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_OFFER_FEATURE);
TestTracker tracker = new TestTracker();
TrackerFactory.setTrackerForTests(tracker);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(new BarItem[] {itemWithIPH, createTabs()});
});
onViewWaiting(withText("Johnathan"));
waitForHelpBubble(withText(itemTag));
onView(withText("Johnathan")).perform(click());
assertThat(tracker.wasDismissed(), is(true));
assertThat(tracker.getLastEmittedEvent(),
is(EventConstants.KEYBOARD_ACCESSORY_PAYMENT_AUTOFILLED));
}
@Test
@MediumTest
public void testNotifiesAboutPartiallyVisibleSuggestions() throws InterruptedException {
// Ensure that the callback isn't triggered while all items are visible:
AtomicInteger obfuscatedChildAt = new AtomicInteger(-1);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(OBFUSCATED_CHILD_AT_CALLBACK, obfuscatedChildAt::set);
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(createAutofillChipAndTab("John", null));
});
KeyboardAccessoryModernView view = mKeyboardAccessoryView.take();
CriteriaHelper.pollUiThread(() -> view.mBarItemsView.getChildCount() > 0);
assertThat(obfuscatedChildAt.get(), is(-1));
// As soon as at least one item can't be displayed in full, trigger the swiping callback.
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.get(BAR_ITEMS).set(new BarItem[] {createAutofillBarItem("JohnathanSmith", null),
createAutofillBarItem("TroyMcSpartanGregor", null),
createAutofillBarItem("SomeOtherRandomLongName", null),
createAutofillBarItem("ToddTester", null),
createAutofillBarItem("MayaPark", null),
createAutofillBarItem("ThisChipIsProbablyHiddenNow", null), createTabs()});
});
onViewWaiting(withText("JohnathanSmith"));
CriteriaHelper.pollUiThread(() -> obfuscatedChildAt.get() > -1);
}
@Test
@MediumTest
public void testCustomIconUrlSet_imageReturnedByPersonalDataManager_customIconSetOnChipView()
throws InterruptedException {
GURL customIconUrl = mock(GURL.class);
when(customIconUrl.isValid()).thenReturn(true);
when(customIconUrl.getSpec()).thenReturn(CUSTOM_ICON_URL);
// Return the cached image when
// PersonalDataManager.getCustomImageForAutofillSuggestionIfAvailable is called for the
// above url.
when(mMockPersonalDataManager.getCustomImageForAutofillSuggestionIfAvailable(any()))
.thenReturn(TEST_CARD_ART_IMAGE);
// Create an autofill suggestion and set the `customIconUrl`.
AutofillBarItem customIconItem = new AutofillBarItem(
getDefaultAutofillSuggestionBuilder().setCustomIconUrl(customIconUrl).build(),
new KeyboardAccessoryData.Action("", AUTOFILL_SUGGESTION, unused -> {}));
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(new BarItem[] {customIconItem, createTabs()});
});
KeyboardAccessoryModernView view = mKeyboardAccessoryView.take();
CriteriaHelper.pollUiThread(() -> view.mBarItemsView.getChildCount() > 0);
CriteriaHelper.pollUiThread(() -> {
ChipView chipView = (ChipView) view.mBarItemsView.getChildAt(0);
ChromeImageView iconImageView = (ChromeImageView) chipView.getChildAt(0);
return ((BitmapDrawable) iconImageView.getDrawable())
.getBitmap()
.equals(TEST_CARD_ART_IMAGE);
});
}
@Test
@MediumTest
public void testCustomIconUrlSet_imageNotCachedInPersonalDataManager_defaultIconSetOnChipView()
throws InterruptedException {
GURL customIconUrl = mock(GURL.class);
when(customIconUrl.isValid()).thenReturn(true);
when(customIconUrl.getSpec()).thenReturn(CUSTOM_ICON_URL);
// Return the response of PersonalDataManager.getCustomImageForAutofillSuggestionIfAvailable
// to null to indicate that the image is not present in the cache.
when(mMockPersonalDataManager.getCustomImageForAutofillSuggestionIfAvailable(any()))
.thenReturn(null);
AutofillBarItem customIconItem = new AutofillBarItem(
getDefaultAutofillSuggestionBuilder().setCustomIconUrl(customIconUrl).build(),
new KeyboardAccessoryData.Action("", AUTOFILL_SUGGESTION, unused -> {}));
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(new BarItem[] {customIconItem, createTabs()});
});
KeyboardAccessoryModernView view = mKeyboardAccessoryView.take();
CriteriaHelper.pollUiThread(() -> view.mBarItemsView.getChildCount() > 0);
CriteriaHelper.pollUiThread(() -> {
ChipView chipView = (ChipView) view.mBarItemsView.getChildAt(0);
ChromeImageView iconImageView = (ChromeImageView) chipView.getChildAt(0);
Drawable expectedIcon = mActivityTestRule.getActivity().getResources().getDrawable(
R.drawable.visa_card);
return getBitmap(expectedIcon).sameAs(getBitmap(iconImageView.getDrawable()));
});
}
@Test
@MediumTest
public void testCustomIconUrlNotSet_defaultIconSetOnChipView() throws InterruptedException {
// Create an autofill suggestion without setting the `customIconUrl`.
AutofillBarItem itemWithoutCustomIconUrl =
new AutofillBarItem(getDefaultAutofillSuggestionBuilder().build(),
new KeyboardAccessoryData.Action("", AUTOFILL_SUGGESTION, unused -> {}));
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(new BarItem[] {itemWithoutCustomIconUrl, createTabs()});
});
KeyboardAccessoryModernView view = mKeyboardAccessoryView.take();
CriteriaHelper.pollUiThread(() -> view.mBarItemsView.getChildCount() > 0);
CriteriaHelper.pollUiThread(() -> {
ChipView chipView = (ChipView) view.mBarItemsView.getChildAt(0);
ChromeImageView iconImageView = (ChromeImageView) chipView.getChildAt(0);
Drawable expectedIcon = mActivityTestRule.getActivity().getResources().getDrawable(
R.drawable.visa_card);
return getBitmap(expectedIcon).sameAs(getBitmap(iconImageView.getDrawable()));
});
}
private static AutofillSuggestion.Builder getDefaultAutofillSuggestionBuilder() {
return new AutofillSuggestion.Builder()
.setLabel("Johnathan")
.setSubLabel("Smith")
.setIconId(R.drawable.visa_card)
.setSuggestionId(70000);
}
// Convert a drawable to a Bitmap for comparison.
private static Bitmap getBitmap(Drawable drawable) {
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
private ViewInteraction waitForHelpBubble(Matcher<View> matcher) {
View mainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
return onView(isRoot())
.inRoot(RootMatchers.withDecorView(not(is(mainDecorView))))
.check(waitForView(matcher));
}
private void rotateActivityToLandscape() {
mActivityTestRule.getActivity().setRequestedOrientation(
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
CriteriaHelper.pollInstrumentationThread(() -> {
try {
String result = JavaScriptUtils.executeJavaScriptAndWaitForResult(
mActivityTestRule.getWebContents(),
"screen.orientation.type.split('-')[0]");
Criteria.checkThat(result, is("\"landscape\""));
} catch (TimeoutException ex) {
throw new CriteriaNotSatisfiedException(ex);
}
});
}
private Runnable viewsAreRightAligned(View staticView, View changingView) {
Rect accessoryViewRect = new Rect();
staticView.getGlobalVisibleRect(accessoryViewRect);
return () -> {
Rect keyItemRect = new Rect();
changingView.getGlobalVisibleRect(keyItemRect);
Criteria.checkThat(keyItemRect.right, is(accessoryViewRect.right));
};
}
private BarItem[] createAutofillChipAndTab(String label, Callback<Action> chipCallback) {
return new BarItem[] {createAutofillBarItem(label, chipCallback), createTabs()};
}
private AutofillBarItem createAutofillBarItem(String label, Callback<Action> chipCallback) {
return new AutofillBarItem(
new AutofillSuggestion(label, "Smith", /*itemTag=*/"", DropdownItem.NO_ICON, false,
1, false, false, false, /* featureForIPH= */ ""),
new KeyboardAccessoryData.Action("Unused", AUTOFILL_SUGGESTION, chipCallback));
}
private TabLayoutBarItem createTabs() {
return new TabLayoutBarItem(new KeyboardAccessoryTabLayoutCoordinator.TabLayoutCallbacks() {
@Override
public void onTabLayoutBound(TabLayout tabs) {
if (tabs.getTabCount() > 0) return;
tabs.addTab(tabs.newTab()
.setIcon(R.drawable.ic_vpn_key_grey)
.setContentDescription("Key Icon"));
}
@Override
public void onTabLayoutUnbound(TabLayout tabs) {}
});
}
}