blob: 746e54f5e833abaa267c4511e37cd13f740e0b33 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// 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.matcher.ViewMatchers.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
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.AccessoryAction.CREDMAN_CONDITIONAL_UI_REENTRY;
import static org.chromium.chrome.browser.keyboard_accessory.AccessoryAction.GENERATE_PASSWORD_AUTOMATIC;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.ANIMATION_LISTENER;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.BAR_ITEMS;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.HAS_SUGGESTIONS;
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.SHOW_SWIPING_IPH;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.SKIP_CLOSING_ANIMATION;
import static org.chromium.chrome.browser.keyboard_accessory.bar_component.KeyboardAccessoryProperties.VISIBLE;
import com.google.android.material.tabs.TabLayout;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.Callback;
import org.chromium.base.FeatureList;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.UmaRecorderHolder;
import org.chromium.base.task.test.CustomShadowAsyncTask;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.keyboard_accessory.AccessoryAction;
import org.chromium.chrome.browser.keyboard_accessory.AccessoryBarContents;
import org.chromium.chrome.browser.keyboard_accessory.ManualFillingMetricsRecorder;
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.data.KeyboardAccessoryData;
import org.chromium.chrome.browser.keyboard_accessory.data.KeyboardAccessoryData.Action;
import org.chromium.chrome.browser.keyboard_accessory.data.PropertyProvider;
import org.chromium.chrome.browser.keyboard_accessory.sheet_component.AccessorySheetCoordinator;
import org.chromium.chrome.browser.keyboard_accessory.tab_layout_component.KeyboardAccessoryTabLayoutCoordinator;
import org.chromium.components.autofill.AutofillDelegate;
import org.chromium.components.autofill.AutofillSuggestion;
import org.chromium.components.autofill.PopupItemId;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.ui.modelutil.ListObservable;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyObservable.PropertyObserver;
import org.chromium.ui.test.util.modelutil.FakeViewProvider;
import java.util.HashMap;
/**
* Controller tests for the keyboard accessory component.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, shadows = {CustomShadowAsyncTask.class})
public class KeyboardAccessoryControllerTest {
@Mock
private PropertyObserver<PropertyKey> mMockPropertyObserver;
@Mock
private ListObservable.ListObserver<Void> mMockActionListObserver;
@Mock
private KeyboardAccessoryCoordinator.BarVisibilityDelegate mMockBarVisibilityDelegate;
@Mock
private AccessorySheetCoordinator.SheetVisibilityDelegate mMockSheetVisibilityDelegate;
@Mock
private KeyboardAccessoryModernView mMockView;
@Mock
private KeyboardAccessoryTabLayoutCoordinator mMockTabLayout;
@Mock
private KeyboardAccessoryCoordinator.TabSwitchingDelegate mMockTabSwitchingDelegate;
@Mock
private AutofillDelegate mMockAutofillDelegate;
private final KeyboardAccessoryData.Tab mTestTab =
new KeyboardAccessoryData.Tab("Passwords", null, null, 0, 0, null);
private KeyboardAccessoryCoordinator mCoordinator;
private PropertyModel mModel;
private KeyboardAccessoryMediator mMediator;
@Before
public void setUp() {
UmaRecorderHolder.resetForTesting();
MockitoAnnotations.initMocks(this);
setAutofillFeature(false);
when(mMockView.getTabLayout()).thenReturn(mock(TabLayout.class));
when(mMockTabLayout.getTabSwitchingDelegate()).thenReturn(mMockTabSwitchingDelegate);
mCoordinator = new KeyboardAccessoryCoordinator(mMockTabLayout, mMockBarVisibilityDelegate,
mMockSheetVisibilityDelegate, new FakeViewProvider<>(mMockView));
mMediator = mCoordinator.getMediatorForTesting();
mModel = mMediator.getModelForTesting();
}
private void setAutofillFeature(boolean enabled) {
HashMap<String, Boolean> features = new HashMap<>();
features.put(ChromeFeatureList.AUTOFILL_KEYBOARD_ACCESSORY, enabled);
FeatureList.setTestFeatures(features);
}
@Test
public void testCreatesValidSubComponents() {
assertThat(mCoordinator, is(notNullValue()));
assertThat(mMediator, is(notNullValue()));
assertThat(mModel, is(notNullValue()));
}
@Test
public void testModelNotifiesVisibilityChangeOnShowAndHide() {
mModel.addObserver(mMockPropertyObserver);
// Setting the visibility on the model should make it propagate that it's visible.
mModel.set(VISIBLE, true);
verify(mMockPropertyObserver).onPropertyChanged(mModel, VISIBLE);
assertThat(mModel.get(VISIBLE), is(true));
// Resetting the visibility on the model to should make it propagate that it's visible.
mModel.set(VISIBLE, false);
verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, VISIBLE);
assertThat(mModel.get(VISIBLE), is(false));
}
@Test
public void testModelNotifiesAboutActionsChangedByProvider() {
// Set a default tab to prevent visibility changes to trigger now:
setTabs(new KeyboardAccessoryData.Tab[] {mTestTab});
mModel.get(BAR_ITEMS).addObserver(mMockActionListObserver);
PropertyProvider<Action[]> testProvider =
new PropertyProvider<>(GENERATE_PASSWORD_AUTOMATIC);
mCoordinator.registerActionProvider(testProvider);
// If the coordinator receives an initial actions, the model should report an insertion.
mCoordinator.show();
Action testAction = new Action(null, 0, null);
testProvider.notifyObservers(new Action[] {testAction});
verify(mMockActionListObserver).onItemRangeInserted(mModel.get(BAR_ITEMS), 0, 1);
assertThat(mModel.get(BAR_ITEMS).size(), is(1));
assertThat(mModel.get(BAR_ITEMS).get(0).getAction(), is(equalTo(testAction)));
// If the coordinator receives a new set of actions, the model should report a change.
testProvider.notifyObservers(new Action[] {testAction});
verify(mMockActionListObserver).onItemRangeChanged(mModel.get(BAR_ITEMS), 0, 1, null);
assertThat(mModel.get(BAR_ITEMS).size(), is(1));
assertThat(mModel.get(BAR_ITEMS).get(0).getAction(), is(equalTo(testAction)));
// If the coordinator receives an empty set of actions, the model should report a deletion.
testProvider.notifyObservers(new Action[] {});
verify(mMockActionListObserver).onItemRangeRemoved(mModel.get(BAR_ITEMS), 0, 1);
assertThat(mModel.get(BAR_ITEMS).size(), is(0));
// There should be no notification if no actions are reported repeatedly.
testProvider.notifyObservers(new Action[] {});
verifyNoMoreInteractions(mMockActionListObserver);
}
@Test
public void testModelNotifiesAboutActionsChangedByProviderForRedesign() {
setAutofillFeature(true);
// Set a default tab to prevent visibility changes to trigger now:
setTabs(new KeyboardAccessoryData.Tab[] {mTestTab});
mModel.get(BAR_ITEMS).addObserver(mMockActionListObserver);
PropertyProvider<Action[]> testProvider =
new PropertyProvider<>(GENERATE_PASSWORD_AUTOMATIC);
mCoordinator.registerActionProvider(testProvider);
// If the coordinator receives an initial action, the model should report an insertion.
mCoordinator.show();
Action testAction = new Action(null, 0, null);
testProvider.notifyObservers(new Action[] {testAction});
verify(mMockActionListObserver).onItemRangeInserted(mModel.get(BAR_ITEMS), 0, 2);
assertThat(mModel.get(BAR_ITEMS).size(), is(2)); // Plus tab switcher.
assertThat(mModel.get(BAR_ITEMS).get(0).getAction(), is(equalTo(testAction)));
// If the coordinator receives a new set of actions, the model should report a change.
testProvider.notifyObservers(new Action[] {testAction});
verify(mMockActionListObserver).onItemRangeChanged(mModel.get(BAR_ITEMS), 0, 2, null);
assertThat(mModel.get(BAR_ITEMS).size(), is(2)); // Plus tab switcher.
assertThat(mModel.get(BAR_ITEMS).get(0).getAction(), is(equalTo(testAction)));
// If the coordinator receives an empty set of actions, the model should report a deletion.
testProvider.notifyObservers(new Action[] {});
// First call of onItemRangeChanged(mModel.get(BAR_ITEMS), 0, 1, null);
verify(mMockActionListObserver).onItemRangeRemoved(mModel.get(BAR_ITEMS), 1, 1);
assertThat(mModel.get(BAR_ITEMS).size(), is(1)); // Only the tab switcher.
// There should be no notification if no actions are reported repeatedly.
testProvider.notifyObservers(new Action[] {});
verify(mMockActionListObserver, times(2))
.onItemRangeChanged(mModel.get(BAR_ITEMS), 0, 1, null);
verifyNoMoreInteractions(mMockActionListObserver);
}
@Test
public void testModelDoesntNotifyUnchangedVisibility() {
mModel.addObserver(mMockPropertyObserver);
// Setting the visibility on the model should make it propagate that it's visible.
mModel.set(VISIBLE, true);
verify(mMockPropertyObserver).onPropertyChanged(mModel, VISIBLE);
assertThat(mModel.get(VISIBLE), is(true));
// Marking it as visible again should not result in a notification.
mModel.set(VISIBLE, true);
verify(mMockPropertyObserver) // Unchanged number of invocations.
.onPropertyChanged(mModel, VISIBLE);
assertThat(mModel.get(VISIBLE), is(true));
}
@Test
public void testTogglesVisibility() {
mCoordinator.show();
assertThat(mModel.get(VISIBLE), is(true));
mCoordinator.dismiss();
assertThat(mModel.get(VISIBLE), is(false));
}
@Test
public void testSortsActionsBasedOnType() {
PropertyProvider<Action[]> generationProvider =
new PropertyProvider<>(GENERATE_PASSWORD_AUTOMATIC);
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
PropertyProvider<Action[]> credManProvider =
new PropertyProvider<>(CREDMAN_CONDITIONAL_UI_REENTRY);
mCoordinator.registerActionProvider(generationProvider);
mCoordinator.registerActionProvider(credManProvider);
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
AutofillSuggestion suggestion1 = new AutofillSuggestion("FirstSuggestion", "",
/* itemTag= */ "", 0, false, 0, false, false, false, /* featureForIPH= */ "");
AutofillSuggestion suggestion2 = new AutofillSuggestion("SecondSuggestion", "",
/* itemTag= */ "", 0, false, 0, false, false, false, /* featureForIPH= */ "");
Action generationAction = new Action("Generate", GENERATE_PASSWORD_AUTOMATIC, (a) -> {});
Action credManAction =
new Action("Show Passkeys", CREDMAN_CONDITIONAL_UI_REENTRY, (a) -> {});
autofillSuggestionProvider.notifyObservers(
new AutofillSuggestion[] {suggestion1, suggestion2});
generationProvider.notifyObservers(new Action[] {generationAction});
credManProvider.notifyObservers(new Action[] {credManAction});
// Autofill suggestions should always come last before mandatory tab switcher.
assertThat(mModel.get(BAR_ITEMS).size(), is(4));
assertThat(mModel.get(BAR_ITEMS).get(0).getAction(), is(credManAction));
assertThat(mModel.get(BAR_ITEMS).get(1).getAction(), is(generationAction));
assertThat(mModel.get(BAR_ITEMS).get(2), instanceOf(AutofillBarItem.class));
AutofillBarItem autofillBarItem1 = (AutofillBarItem) mModel.get(BAR_ITEMS).get(2);
assertThat(autofillBarItem1.getSuggestion(), is(suggestion1));
assertThat(mModel.get(BAR_ITEMS).get(3), instanceOf(AutofillBarItem.class));
AutofillBarItem autofillBarItem2 = (AutofillBarItem) mModel.get(BAR_ITEMS).get(3);
assertThat(autofillBarItem2.getSuggestion(), is(suggestion2));
}
@Test
public void testMovesTabSwitcherToEndForRedesign() {
setAutofillFeature(true);
PropertyProvider<Action[]> generationProvider =
new PropertyProvider<>(GENERATE_PASSWORD_AUTOMATIC);
PropertyProvider<Action[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
mCoordinator.registerActionProvider(generationProvider);
mCoordinator.registerActionProvider(autofillSuggestionProvider);
Action suggestion1 = new Action("FirstSuggestion", AUTOFILL_SUGGESTION, (a) -> {});
Action suggestion2 = new Action("SecondSuggestion", AUTOFILL_SUGGESTION, (a) -> {});
Action generationAction = new Action("Generate", GENERATE_PASSWORD_AUTOMATIC, (a) -> {});
autofillSuggestionProvider.notifyObservers(new Action[] {suggestion1, suggestion2});
generationProvider.notifyObservers(new Action[] {generationAction});
// Autofill suggestions should always come last, independent of when they were added.
assertThat(mModel.get(BAR_ITEMS).size(), is(4)); // Additional tab switcher
assertThat(mModel.get(BAR_ITEMS).get(0).getAction(), is(generationAction));
assertThat(mModel.get(BAR_ITEMS).get(1).getAction(), is(suggestion1));
assertThat(mModel.get(BAR_ITEMS).get(2).getAction(), is(suggestion2));
assertThat(mModel.get(BAR_ITEMS).get(3).getViewType(), is(BarItem.Type.TAB_LAYOUT));
}
@Test
public void testDeletingActionsAffectsOnlyOneType() {
PropertyProvider<Action[]> generationProvider =
new PropertyProvider<>(GENERATE_PASSWORD_AUTOMATIC);
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
mCoordinator.registerActionProvider(generationProvider);
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
AutofillSuggestion suggestion = new AutofillSuggestion("Suggestion", "", /* itemTag= */ "",
0, false, 0, false, false, false, /* featureForIPH= */ "");
Action generationAction = new Action("Generate", GENERATE_PASSWORD_AUTOMATIC, (a) -> {});
autofillSuggestionProvider.notifyObservers(
new AutofillSuggestion[] {suggestion, suggestion});
generationProvider.notifyObservers(new Action[] {generationAction});
assertThat(mModel.get(BAR_ITEMS).size(), is(3));
// Drop all Autofill suggestions. Only the generation action should remain.
autofillSuggestionProvider.notifyObservers(new AutofillSuggestion[0]);
assertThat(mModel.get(BAR_ITEMS).size(), is(1));
assertThat(mModel.get(BAR_ITEMS).get(0).getAction(), is(generationAction));
// Readd an Autofill suggestion and drop the generation. Only the suggestion should remain.
autofillSuggestionProvider.notifyObservers(new AutofillSuggestion[] {suggestion});
generationProvider.notifyObservers(new Action[0]);
assertThat(mModel.get(BAR_ITEMS).size(), is(1));
assertThat(mModel.get(BAR_ITEMS).get(0), instanceOf(AutofillBarItem.class));
AutofillBarItem autofillBarItem = (AutofillBarItem) mModel.get(BAR_ITEMS).get(0);
assertThat(autofillBarItem.getSuggestion(), is(suggestion));
}
@Test
public void testGenerationActionsRemovedWhenNotVisible() {
// Make the accessory visible and add an action to it.
mCoordinator.show();
mModel.get(BAR_ITEMS).add(new BarItem(
BarItem.Type.ACTION_BUTTON, new Action(null, GENERATE_PASSWORD_AUTOMATIC, null)));
// Hiding the accessory should also remove actions.
mCoordinator.dismiss();
assertThat(mModel.get(BAR_ITEMS).size(), is(0));
}
@Test
public void testCreatesAddressItemWithIPH() {
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
int suggestionId = 0x1; // The address ID is located in the least 16 bit.
AutofillSuggestion addressSuggestion =
new AutofillSuggestion("John", "Main Str", /* itemTag= */ "", 0, false,
suggestionId, false, false, false, /* featureForIPH= */ "");
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
autofillSuggestionProvider.notifyObservers(
new AutofillSuggestion[] {addressSuggestion, addressSuggestion, addressSuggestion});
// assertThat(getAutofillItemAt(0).getFeatureForIPH(), is(nullValue()));
// mCoordinator.prepareUserEducation();
assertThat(getAutofillItemAt(0).getFeatureForIPH(),
is(FeatureConstants.KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE));
assertThat(getAutofillItemAt(1).getFeatureForIPH(), is(nullValue()));
assertThat(getAutofillItemAt(2).getFeatureForIPH(), is(nullValue()));
}
@Test
public void testCreatesPaymentItemWithIPH() {
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
int suggestionId = 0x10000; // The payment ID is located in the higher 16 bit.
AutofillSuggestion paymentSuggestion = new AutofillSuggestion("John", "4828 ****",
/* itemTag= */ "", 0, false, suggestionId, false, false, false,
/* featureForIPH= */ "");
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
autofillSuggestionProvider.notifyObservers(
new AutofillSuggestion[] {paymentSuggestion, paymentSuggestion, paymentSuggestion});
// assertThat(getAutofillItemAt(0).getFeatureForIPH(), is(nullValue()));
// mCoordinator.prepareUserEducation();
assertThat(getAutofillItemAt(0).getFeatureForIPH(),
is(FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE));
assertThat(getAutofillItemAt(1).getFeatureForIPH(), is(nullValue()));
assertThat(getAutofillItemAt(2).getFeatureForIPH(), is(nullValue()));
}
@Test
public void testIPHFeatureSetForAutofillSuggestion() {
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
int suggestionId = 0x10000; // The payment ID is located in the higher 16 bit.
AutofillSuggestion paymentSuggestion = new AutofillSuggestion("John", "4828 ****",
/* itemTag= */ "", 0, false, suggestionId, false, false, false,
FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_VIRTUAL_CARD_FEATURE);
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
autofillSuggestionProvider.notifyObservers(
new AutofillSuggestion[] {paymentSuggestion, paymentSuggestion, paymentSuggestion});
// assertThat(getAutofillItemAt(0).getFeatureForIPH(), is(nullValue()));
// mCoordinator.prepareUserEducation();
assertThat(getAutofillItemAt(0).getFeatureForIPH(),
is(FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_VIRTUAL_CARD_FEATURE));
// Other suggestions also have explicit IPH strings, but only the first suggestion's string
// is shown.
assertThat(getAutofillItemAt(1).getFeatureForIPH(), is(nullValue()));
assertThat(getAutofillItemAt(2).getFeatureForIPH(), is(nullValue()));
}
@Test
public void testCreatesIPHForSecondPasswordItem() {
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
AutofillSuggestion passwordSuggestion1 = new AutofillSuggestion("John", "****",
/* itemTg= */ "", 0, false, PopupItemId.ITEM_ID_USERNAME_ENTRY, false, false, false,
/* featureForIPH= */ "");
AutofillSuggestion passwordSuggestion2 = new AutofillSuggestion("Eva", "*******",
/* itemTag= */ "", 0, false, PopupItemId.ITEM_ID_PASSWORD_ENTRY, false, false,
false, /* featureForIPH= */ "");
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
autofillSuggestionProvider.notifyObservers(new AutofillSuggestion[] {
passwordSuggestion1, passwordSuggestion2, passwordSuggestion2});
// assertThat(getAutofillItemAt(0).getFeatureForIPH(), is(nullValue()));
// mCoordinator.prepareUserEducation();
assertThat(getAutofillItemAt(0).getFeatureForIPH(), is(nullValue()));
assertThat(getAutofillItemAt(1).getFeatureForIPH(),
is(FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE));
assertThat(getAutofillItemAt(2).getFeatureForIPH(), is(nullValue()));
}
@Test
public void testCreatesAddressItemWithExternallyProvidedIPH() {
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
int suggestionId = 0x1; // The address ID is located in the least 16 bit.
AutofillSuggestion addressSuggestion = new AutofillSuggestion("John", "Main Str",
/* itemTag= */ "", 0, false, suggestionId, false, false, false,
FeatureConstants.KEYBOARD_ACCESSORY_EXTERNAL_ACCOUNT_PROFILE_FEATURE);
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
autofillSuggestionProvider.notifyObservers(
new AutofillSuggestion[] {addressSuggestion, addressSuggestion, addressSuggestion});
// assertThat(getAutofillItemAt(0).getFeatureForIPH(), is(nullValue()));
// mCoordinator.prepareUserEducation();
assertThat(getAutofillItemAt(0).getFeatureForIPH(),
is(FeatureConstants.KEYBOARD_ACCESSORY_EXTERNAL_ACCOUNT_PROFILE_FEATURE));
assertThat(getAutofillItemAt(1).getFeatureForIPH(), is(nullValue()));
assertThat(getAutofillItemAt(2).getFeatureForIPH(), is(nullValue()));
}
@Test
public void testSkipAnimationsOnlyUntilNextShow() {
assertThat(mModel.get(SKIP_CLOSING_ANIMATION), is(false));
mCoordinator.skipClosingAnimationOnce();
assertThat(mModel.get(SKIP_CLOSING_ANIMATION), is(true));
mCoordinator.show();
assertThat(mModel.get(SKIP_CLOSING_ANIMATION), is(false));
}
@Test
public void testShowSwipingIphUntilVisibilityIsReset() {
// By default, no IPH is shown but the model holds a callback to notify the mediator.
mCoordinator.show();
Callback<Integer> obfuscatedChildAt = mModel.get(OBFUSCATED_CHILD_AT_CALLBACK);
assertThat(obfuscatedChildAt, notNullValue());
assertThat(mModel.get(SHOW_SWIPING_IPH), is(false));
// Notify the mediator to show the IPH because at least one of three items is not visible.
mModel.get(BAR_ITEMS).add(mock(BarItem.class));
mModel.get(BAR_ITEMS).add(mock(BarItem.class));
mModel.get(BAR_ITEMS).add(mock(BarItem.class));
obfuscatedChildAt.onResult(1);
assertThat(mModel.get(SHOW_SWIPING_IPH), is(true));
// Any change that changes the visibility should reset the swiping IPH.
mModel.set(VISIBLE, false);
assertThat(mModel.get(SHOW_SWIPING_IPH), is(false));
}
@Test
public void testRecordsOneImpressionForEveryInitialContentOnVisibilityChange() {
assertThat(RecordHistogram.getHistogramTotalCountForTesting(
KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_BAR_SHOWN),
is(0));
// Adding a tab contributes to the tabs and the total bucket.
setTabs(new KeyboardAccessoryData.Tab[] {mTestTab});
mCoordinator.show();
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(1));
// Adding an action contributes to the actions bucket. Tabs and total are logged again.
mCoordinator.dismiss(); // Hide, so it's brought up again.
mModel.get(BAR_ITEMS).add(new BarItem(
BarItem.Type.ACTION_BUTTON, new Action(null, GENERATE_PASSWORD_AUTOMATIC, null)));
mCoordinator.show();
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(2));
assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(2));
// Adding suggestions adds to the suggestions bucket - and again to tabs and total.
mCoordinator.dismiss(); // Hide, so it's brought up again.
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
AutofillSuggestion suggestion = new AutofillSuggestion("Label", "sublabel",
/* itemTag= */ "", 0, false, 0, false, false, false, /* featureForIPH= */ "");
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
autofillSuggestionProvider.notifyObservers(new AutofillSuggestion[] {suggestion});
mCoordinator.show();
// Hiding the keyboard clears actions, so don't log more actions from here on out.
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_AUTOFILL_SUGGESTIONS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(3));
assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(3));
// Removing suggestions adds to everything but the suggestions bucket. The value remains.
mCoordinator.dismiss(); // Hide, so it's brought up again.
autofillSuggestionProvider.notifyObservers(new AutofillSuggestion[0]);
mCoordinator.show();
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_AUTOFILL_SUGGESTIONS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(4));
assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(4));
}
@Test
public void testRecordsContentBarImpressionOnceAndContentsUpToOnce() {
assertThat(RecordHistogram.getHistogramTotalCountForTesting(
KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_BAR_SHOWN),
is(0));
// First showing contains tabs only.
setTabs(new KeyboardAccessoryData.Tab[] {mTestTab});
mCoordinator.show();
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(1));
// New actions are recorded in specific buckets but don't affect the general ANY_CONTENTS.
mModel.get(BAR_ITEMS).add(new BarItem(
BarItem.Type.ACTION_BUTTON, new Action(null, GENERATE_PASSWORD_AUTOMATIC, null)));
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.ANY_CONTENTS), is(1));
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
AutofillSuggestion suggestion = new AutofillSuggestion("Label", "sublabel",
/* itemTag= */ "", 0, false, 0, false, false, false, /* featureForIPH= */ "");
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
autofillSuggestionProvider.notifyObservers(new AutofillSuggestion[] {suggestion});
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_AUTOFILL_SUGGESTIONS), is(1));
// The other changes were not recorded again - just the changes.
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_TABS), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.NO_CONTENTS), is(0));
}
@Test
public void testRecordsAgainIfExistingItemsChange() {
assertThat(RecordHistogram.getHistogramTotalCountForTesting(
KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_BAR_SHOWN),
is(0));
// Add a tab and show, so the accessory is permanently visible.
setTabs(new KeyboardAccessoryData.Tab[] {mTestTab});
mCoordinator.show();
// Adding an action fills the bar impression bucket and the actions set once.
mModel.get(BAR_ITEMS).set(
new BarItem[] {new BarItem(BarItem.Type.ACTION_BUTTON,
new Action("One", GENERATE_PASSWORD_AUTOMATIC, null)),
new BarItem(BarItem.Type.ACTION_BUTTON,
new Action("Two", GENERATE_PASSWORD_AUTOMATIC, null))});
assertThat(getGenerationImpressionCount(), is(1));
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
// Adding another action leaves bar impressions unchanged but affects the actions bucket.
mModel.get(BAR_ITEMS).set(
new BarItem[] {new BarItem(BarItem.Type.ACTION_BUTTON,
new Action("Uno", GENERATE_PASSWORD_AUTOMATIC, null)),
new BarItem(BarItem.Type.ACTION_BUTTON,
new Action("Dos", GENERATE_PASSWORD_AUTOMATIC, null))});
assertThat(getShownMetricsCount(AccessoryBarContents.WITH_ACTIONS), is(1));
assertThat(getGenerationImpressionCount(), is(2));
}
@Test
public void testModelChangesUpdatesTheContentDescriptionInModernView() {
setAutofillFeature(true);
PropertyProvider<AutofillSuggestion[]> autofillSuggestionProvider =
new PropertyProvider<>(AUTOFILL_SUGGESTION);
mCoordinator.registerAutofillProvider(autofillSuggestionProvider, mMockAutofillDelegate);
autofillSuggestionProvider.notifyObservers(
new AutofillSuggestion[] {mock(AutofillSuggestion.class)});
assertThat(mModel.get(HAS_SUGGESTIONS), is(true));
autofillSuggestionProvider.notifyObservers(new AutofillSuggestion[] {});
assertThat(mModel.get(HAS_SUGGESTIONS), is(false));
}
@Test
public void testFowardsAnimationEventsToVisibilityDelegate() {
mModel.get(ANIMATION_LISTENER).onFadeInEnd();
verify(mMockBarVisibilityDelegate).onBarFadeInAnimationEnd();
}
private int getGenerationImpressionCount() {
return RecordHistogram.getHistogramValueCountForTesting(
ManualFillingMetricsRecorder.UMA_KEYBOARD_ACCESSORY_ACTION_IMPRESSION,
AccessoryAction.GENERATE_PASSWORD_AUTOMATIC);
}
private int getShownMetricsCount(@AccessoryBarContents int bucket) {
return RecordHistogram.getHistogramValueCountForTesting(
KeyboardAccessoryMetricsRecorder.UMA_KEYBOARD_ACCESSORY_BAR_SHOWN, bucket);
}
private void setTabs(KeyboardAccessoryData.Tab[] tabs) {
mCoordinator.setTabs(tabs);
when(mMockTabSwitchingDelegate.hasTabs()).thenReturn(true);
}
private void setActiveTab(KeyboardAccessoryData.Tab tab) {
when(mMockTabSwitchingDelegate.getActiveTab()).thenReturn(tab);
when(mMockTabSwitchingDelegate.hasTabs()).thenReturn(true);
mCoordinator.getMediatorForTesting().onActiveTabChanged(0);
}
private AutofillBarItem getAutofillItemAt(int position) {
return (AutofillBarItem) mModel.get(BAR_ITEMS).get(position);
}
}