Commit 5179909c authored by Friedrich Horschig's avatar Friedrich Horschig Committed by Commit Bot

[Android] Wire accessory sheet to keyboard accessory

This CL connects the TabLayout int the keyboard accessory with the
ViewPager in the accessory sheet.
This means the visibility of keyboard and tabs can be controlled by the
ManualFillingCoordinator and is decoupled from the accessory.

Bug: 828832, 811747
Change-Id: I01f225458d068e75fde19398fc352672779d23c0
Reviewed-on: https://chromium-review.googlesource.com/1091190
Commit-Queue: Friedrich Horschig <fhorschig@chromium.org>
Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#567196}
parent 7121bff4
......@@ -9,6 +9,7 @@ import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.ViewStub;
import org.chromium.base.Supplier;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.modelutil.LazyViewBinderAdapter;
import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor;
......@@ -22,18 +23,23 @@ import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
*/
public class AccessorySheetCoordinator {
private final AccessorySheetMediator mMediator;
private final Supplier<ViewPager.OnPageChangeListener> mProvider;
/**
* Creates the sheet component by instantiating Model, View and Controller before wiring these
* parts up.
* @param viewStub The view stub that can be inflated into the accessory layout.
* @param provider The provider of a {@link ViewPager.OnPageChangeListener} used for navigation.
*/
public AccessorySheetCoordinator(ViewStub viewStub) {
public AccessorySheetCoordinator(
ViewStub viewStub, Supplier<ViewPager.OnPageChangeListener> provider) {
mProvider = provider;
LazyViewBinderAdapter.StubHolder<ViewPager> stubHolder =
new LazyViewBinderAdapter.StubHolder<>(viewStub);
AccessorySheetModel model = new AccessorySheetModel();
model.addObserver(new PropertyModelChangeProcessor<>(
model, stubHolder, new LazyViewBinderAdapter<>(new AccessorySheetViewBinder())));
model.addObserver(new PropertyModelChangeProcessor<>(model, stubHolder,
new LazyViewBinderAdapter<>(
new AccessorySheetViewBinder(), this::onViewInflated)));
mMediator = new AccessorySheetMediator(model);
}
......@@ -50,6 +56,15 @@ public class AccessorySheetCoordinator {
return adapter;
}
/**
* Called by the {@link LazyViewBinderAdapter} as soon as the view is inflated so it can be
* initialized. This call happens before the {@link AccessorySheetViewBinder} is called for the
* first time.
*/
private void onViewInflated(ViewPager view) {
view.addOnPageChangeListener(mProvider.get());
}
/**
* Adds the contents of a given {@link KeyboardAccessoryData.Tab} to the accessory sheet. If it
* is the first Tab, it automatically becomes the active Tab.
......@@ -74,8 +89,38 @@ public class AccessorySheetCoordinator {
return mMediator.getTab();
}
/**
* Shows the Accessory Sheet.
*/
public void show() {
mMediator.show();
}
/**
* Hides the Accessory Sheet.
*/
public void hide() {
mMediator.hide();
}
/**
* Returns whether the accessory sheet is currently visible.
* @return True, if the accessory sheet is visible.
*/
public boolean isShown() {
return mMediator.isShown();
}
@VisibleForTesting
AccessorySheetMediator getMediatorForTesting() {
return mMediator;
}
/**
* Calling this function changes the active tab to the tab at the given |position|.
* @param position The index of the tab (starting with 0) that should be set active.
*/
public void setActiveTab(int position) {
mMediator.setActiveTab(position);
}
}
\ No newline at end of file
......@@ -40,6 +40,10 @@ class AccessorySheetMediator {
mModel.setVisible(false);
}
boolean isShown() {
return mModel.isVisible();
}
void addTab(KeyboardAccessoryData.Tab tab) {
mModel.getTabList().add(tab);
if (mModel.getActiveTabIndex() == NO_ACTIVE_TAB) {
......@@ -51,6 +55,13 @@ class AccessorySheetMediator {
assert mModel.getActiveTabIndex() != NO_ACTIVE_TAB;
mModel.setActiveTabIndex(getNextActiveTab(tab));
mModel.getTabList().remove(tab);
if (mModel.getActiveTabIndex() == NO_ACTIVE_TAB) hide();
}
void setActiveTab(int position) {
assert position < mModel.getTabList().getItemCount()
|| position >= 0 : position + " is not a valid tab index!";
mModel.setActiveTabIndex(position);
}
/**
......
......@@ -43,7 +43,6 @@ class AccessorySheetViewBinder
if (propertyKey == PropertyKey.ACTIVE_TAB_INDEX) {
if (model.getActiveTabIndex() != AccessorySheetModel.NO_ACTIVE_TAB) {
inflatedView.setCurrentItem(model.getActiveTabIndex());
// inflatedView.post(() -> inflatedView.setCurrentItem(model.getActiveTabIndex()));
}
return;
}
......
......@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.v4.view.ViewPager;
import android.view.ViewStub;
import org.chromium.base.VisibleForTesting;
......@@ -17,7 +18,6 @@ import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
import org.chromium.chrome.browser.modelutil.RecyclerViewModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;
import org.chromium.ui.UiUtils;
import org.chromium.ui.base.WindowAndroid;
/**
......@@ -29,19 +29,51 @@ public class KeyboardAccessoryCoordinator {
private final KeyboardAccessoryMediator mMediator;
private LazyViewBinderAdapter.StubHolder<KeyboardAccessoryView> mViewHolder;
/**
* The keyboard accessory provides signals when to show or change the accessory sheet below it.
* The actual implementation isn't relevant for this component. Therefore, a class implementing
* this interface takes that responsibility, i.e. {@link ManualFillingCoordinator}.
*/
public interface VisibilityDelegate {
/**
* Is triggered when a tab in the accessory was selected and the sheet needs to change.
* If the sheet needs to also be opened, {@link VisibilityDelegate#onOpenAccessorySheet()}
* is called right after this method.
* @param tabIndex The index of the selected tab in the tab bar.
*/
void onChangeAccessorySheet(int tabIndex);
/**
* Called when the sheet needs to be opened.
*/
void onOpenAccessorySheet();
/**
* Called when the sheet needs to be hidden.
*/
void onCloseAccessorySheet();
/**
* Called when the whole accessory should be hidden (e.g. due to selecting a suggestion).
*/
void onCloseKeyboardAccessory();
}
/**
* Initializes the component as soon as the native library is loaded by e.g. starting to listen
* to keyboard visibility events.
* @param windowAndroid The window connected to the activity this component lives in.
* @param viewStub the stub that will become the accessory.
*/
public KeyboardAccessoryCoordinator(WindowAndroid windowAndroid, ViewStub viewStub) {
public KeyboardAccessoryCoordinator(
WindowAndroid windowAndroid, ViewStub viewStub, VisibilityDelegate visibilityDelegate) {
KeyboardAccessoryModel model = new KeyboardAccessoryModel();
mMediator = new KeyboardAccessoryMediator(model, windowAndroid);
mMediator = new KeyboardAccessoryMediator(model, windowAndroid, visibilityDelegate);
mViewHolder = new LazyViewBinderAdapter.StubHolder<>(viewStub);
model.addObserver(new PropertyModelChangeProcessor<>(model, mViewHolder,
new LazyViewBinderAdapter<>(new KeyboardAccessoryViewBinder())));
new LazyViewBinderAdapter<>(
new KeyboardAccessoryViewBinder(), this::onViewInflated)));
}
/**
......@@ -74,6 +106,16 @@ public class KeyboardAccessoryCoordinator {
return tabViewBinder;
}
/**
* Called by the {@link LazyViewBinderAdapter} as soon as the view is inflated so it can be
* initialized. This call happens before the {@link KeyboardAccessoryViewBinder} is called for
* the first time.
* @param view The view that was inflated from the initially given {@link ViewStub}.
*/
private void onViewInflated(KeyboardAccessoryView view) {
view.setTabSelectionAdapter(mMediator);
}
/**
* A {@link KeyboardAccessoryData.Tab} passed into this function will be represented as item at
* the start of the accessory. It is meant to trigger various bottom sheets.
......@@ -125,12 +167,19 @@ public class KeyboardAccessoryCoordinator {
*/
public void dismiss() {
mMediator.dismiss();
// TODO(fhorschig): Move hiding to upcoming root controller, drop it or reassign to bauerb@.
UiUtils.hideKeyboard(mViewHolder.getView());
}
@VisibleForTesting
KeyboardAccessoryMediator getMediatorForTesting() {
return mMediator;
}
/**
* Provides the PageChangeListener that is needed to wire up the TabLayout with the ViewPager.
* @return Returns a {@link ViewPager.OnPageChangeListener}.
*/
ViewPager.OnPageChangeListener getPageChangeListener() {
assert mViewHolder.getView() != null : "Requested PageChangeListener before inflation.";
return mViewHolder.getView().getPageChangeListener();
}
}
......@@ -5,9 +5,11 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.autofill.AutofillKeyboardSuggestions;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryCoordinator.VisibilityDelegate;
import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.ui.base.WindowAndroid;
......@@ -23,24 +25,29 @@ import org.chromium.ui.base.WindowAndroid;
class KeyboardAccessoryMediator
implements WindowAndroid.KeyboardVisibilityListener, ListObservable.ListObserver<Void>,
PropertyObservable.PropertyObserver<KeyboardAccessoryModel.PropertyKey>,
KeyboardAccessoryData.Observer<KeyboardAccessoryData.Action> {
KeyboardAccessoryData.Observer<KeyboardAccessoryData.Action>,
TabLayout.OnTabSelectedListener {
private final KeyboardAccessoryModel mModel;
private final WindowAndroid mWindowAndroid;
private final VisibilityDelegate mVisibilityDelegate;
// TODO(fhorschig): Look stronger signals than |keyboardVisibilityChanged|.
// TODO(fhorschig): Look for stronger signals than |keyboardVisibilityChanged|.
// This variable remembers the last state of |keyboardVisibilityChanged| which might not be
// sufficient for edge cases like hardware keyboards, floating keyboards, etc.
private boolean mIsKeyboardVisible;
KeyboardAccessoryMediator(KeyboardAccessoryModel model, WindowAndroid windowAndroid) {
KeyboardAccessoryMediator(KeyboardAccessoryModel model, WindowAndroid windowAndroid,
VisibilityDelegate visibilityDelegate) {
mModel = model;
mWindowAndroid = windowAndroid;
mVisibilityDelegate = visibilityDelegate;
windowAndroid.addKeyboardVisibilityListener(this);
// Add mediator as observer so it can use model changes as signal for accessory visibility.
mModel.addObserver(this);
mModel.getTabList().addObserver(this);
mModel.getActionList().addObserver(this);
mModel.setTabSelectionCallbacks(this);
}
void destroy() {
......@@ -71,6 +78,7 @@ class KeyboardAccessoryMediator
}
void dismiss() {
mVisibilityDelegate.onCloseKeyboardAccessory();
if (mModel.getAutofillSuggestions() == null) return; // Nothing to do here.
mModel.getAutofillSuggestions().dismiss();
mModel.setAutofillSuggestions(null);
......@@ -105,7 +113,17 @@ class KeyboardAccessoryMediator
public void onPropertyChanged(PropertyObservable<KeyboardAccessoryModel.PropertyKey> source,
@Nullable KeyboardAccessoryModel.PropertyKey propertyKey) {
// Update the visibility only if we haven't set it just now.
if (propertyKey == KeyboardAccessoryModel.PropertyKey.VISIBLE) return;
if (propertyKey == KeyboardAccessoryModel.PropertyKey.VISIBLE) {
// When the accessory just (dis)appeared, there should be no active tab.
mModel.setActiveTab(null);
return;
}
if (propertyKey == KeyboardAccessoryModel.PropertyKey.ACTIVE_TAB) {
return;
}
if (propertyKey == KeyboardAccessoryModel.PropertyKey.TAB_SELECTION_CALLBACKS) {
return;
}
if (propertyKey == KeyboardAccessoryModel.PropertyKey.SUGGESTIONS) {
updateVisibility();
return;
......@@ -113,8 +131,31 @@ class KeyboardAccessoryMediator
assert false : "Every property update needs to be handled explicitly!";
}
@Override
public void onTabSelected(TabLayout.Tab tab) {
boolean hadNoActiveTab = mModel.activeTab() == null;
mModel.setActiveTab(tab.getPosition());
mVisibilityDelegate.onChangeAccessorySheet(tab.getPosition());
if (hadNoActiveTab) mVisibilityDelegate.onOpenAccessorySheet();
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {
if (mModel.activeTab() == null) {
mModel.setActiveTab(tab.getPosition());
mVisibilityDelegate.onChangeAccessorySheet(tab.getPosition());
mVisibilityDelegate.onOpenAccessorySheet();
} else {
mModel.setActiveTab(null);
mVisibilityDelegate.onCloseAccessorySheet();
}
}
private boolean shouldShowAccessory() {
if (!mIsKeyboardVisible) return false;
if (!mIsKeyboardVisible && mModel.activeTab() == null) return false;
return mModel.getAutofillSuggestions() != null || mModel.getActionList().getItemCount() > 0
|| mModel.getTabList().getItemCount() > 0;
}
......
......@@ -4,6 +4,9 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import org.chromium.chrome.browser.autofill.AutofillKeyboardSuggestions;
import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
......@@ -28,6 +31,8 @@ class KeyboardAccessoryModel extends PropertyObservable<KeyboardAccessoryModel.P
static final PropertyKey VISIBLE = new PropertyKey();
static final PropertyKey SUGGESTIONS = new PropertyKey();
static final PropertyKey ACTIVE_TAB = new PropertyKey();
static final PropertyKey TAB_SELECTION_CALLBACKS = new PropertyKey();
private PropertyKey() {
ALL_PROPERTIES.add(this);
......@@ -37,6 +42,8 @@ class KeyboardAccessoryModel extends PropertyObservable<KeyboardAccessoryModel.P
private SimpleListObservable<KeyboardAccessoryData.Action> mActionListObservable;
private SimpleListObservable<KeyboardAccessoryData.Tab> mTabListObservable;
private boolean mVisible;
private @Nullable Integer mActiveTab;
private TabLayout.OnTabSelectedListener mTabSelectionCallbacks;
// TODO(fhorschig): Ideally, make this a ListObservable populating a RecyclerView.
private AutofillKeyboardSuggestions mAutofillSuggestions;
......@@ -84,6 +91,27 @@ class KeyboardAccessoryModel extends PropertyObservable<KeyboardAccessoryModel.P
return mVisible;
}
void setActiveTab(Integer activeTab) {
if (null != mActiveTab && mActiveTab.equals(activeTab)) return;
mActiveTab = activeTab;
notifyPropertyChanged(PropertyKey.ACTIVE_TAB);
}
@Nullable
Integer activeTab() {
return mActiveTab;
}
TabLayout.OnTabSelectedListener getTabSelectionCallbacks() {
return mTabSelectionCallbacks;
}
void setTabSelectionCallbacks(TabLayout.OnTabSelectedListener tabSelectionCallbacks) {
if (tabSelectionCallbacks == mTabSelectionCallbacks) return; // Nothing to do: same object.
mTabSelectionCallbacks = tabSelectionCallbacks;
notifyPropertyChanged(PropertyKey.TAB_SELECTION_CALLBACKS);
}
AutofillKeyboardSuggestions getAutofillSuggestions() {
return mAutofillSuggestions;
}
......
......@@ -7,9 +7,12 @@ package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static org.chromium.ui.base.LocalizationUtils.isLayoutRtl;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
......@@ -33,6 +36,7 @@ class KeyboardAccessoryView extends LinearLayout {
private HorizontalScrollView mSuggestionsView;
private RecyclerView mActionsView;
private TabLayout mTabLayout;
private TabLayout.TabLayoutOnPageChangeListener mPageChangeListener;
private static class HorizontalDividerItemDecoration extends RecyclerView.ItemDecoration {
private final int mHorizontalMargin;
......@@ -95,21 +99,49 @@ class KeyboardAccessoryView extends LinearLayout {
* @param contentDescription The contentDescription to be used for the tab icon.
*/
void addTabAt(int position, Drawable icon, CharSequence contentDescription) {
TabLayout.Tab tab = mTabLayout.newTab();
tab.setIcon(icon); // TODO(fhorschig): Call .mutate() when changing the active tint.
tab.setContentDescription(contentDescription);
mTabLayout.addTab(tab, position);
this.post(() -> { // Using |post| ensures this is cannot happen before inflation finishes.
TabLayout.Tab tab = mTabLayout.newTab();
tab.setIcon(icon.mutate()); // mutate() needed to change the active tint.
tab.setContentDescription(contentDescription);
mTabLayout.addTab(tab, position, false);
});
}
void removeTabAt(int position) {
mTabLayout.removeTabAt(position);
this.post(() -> mTabLayout.removeTabAt(position));
}
/**
* Removes all tabs.
*/
void clearTabs() {
mTabLayout.removeAllTabs();
this.post(() -> {
if (mTabLayout != null) mTabLayout.removeAllTabs();
});
}
ViewPager.OnPageChangeListener getPageChangeListener() {
if (mPageChangeListener == null) {
mPageChangeListener = new TabLayout.TabLayoutOnPageChangeListener(mTabLayout);
}
return mPageChangeListener;
}
void setTabSelectionAdapter(TabLayout.OnTabSelectedListener tabSelectionCallbacks) {
mTabLayout.clearOnTabSelectedListeners();
mTabLayout.addOnTabSelectedListener(tabSelectionCallbacks);
}
void setActiveTabColor(Integer activeTab) {
for (int i = mTabLayout.getTabCount() - 1; i >= 0; i--) {
TabLayout.Tab t = mTabLayout.getTabAt(i);
if (t == null || t.getIcon() == null) continue;
if (activeTab == null || i != activeTab) {
t.getIcon().clearColorFilter();
} else {
t.getIcon().setColorFilter(Color.BLUE, PorterDuff.Mode.SRC_ATOP);
}
}
}
// TODO(crbug/722897): Check to handle RTL.
......@@ -125,6 +157,7 @@ class KeyboardAccessoryView extends LinearLayout {
}
private void show() {
bringToFront(); // Needs to overlay every component and the bottom sheet - like a keyboard.
setVisibility(View.VISIBLE);
announceForAccessibility(((ViewGroup) getParent()).getContentDescription());
}
......
......@@ -89,7 +89,8 @@ class KeyboardAccessoryViewBinder
view.clearTabs();
for (int i = 0; i < model.getItemCount(); ++i) {
Tab tab = model.get(i);
view.addTabAt(i, tab.getIcon(), tab.getContentDescription());
// Mutate tab icons so we can apply color filters.
view.addTabAt(i, tab.getIcon().mutate(), tab.getContentDescription());
}
}
}
......@@ -123,6 +124,16 @@ class KeyboardAccessoryViewBinder
view.setVisible(model.isVisible());
return;
}
if (propertyKey == PropertyKey.ACTIVE_TAB) {
view.setActiveTabColor(model.activeTab());
return;
}
if (propertyKey == PropertyKey.TAB_SELECTION_CALLBACKS) {
// Don't add null as listener. It's a valid state but an invalid argument.
if (model.getTabSelectionCallbacks() == null) return;
view.setTabSelectionAdapter(model.getTabSelectionCallbacks());
return;
}
if (propertyKey == PropertyKey.SUGGESTIONS) {
view.updateSuggestions(model.getAutofillSuggestions());
return;
......
......@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.view.ViewStub;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.ui.base.WindowAndroid;
/**
......@@ -18,8 +19,9 @@ import org.chromium.ui.base.WindowAndroid;
* fields.
*/
public class ManualFillingCoordinator {
private final KeyboardAccessoryCoordinator mKeyboardAccessory;
private final AccessorySheetCoordinator mAccessorySheet;
private final ManualFillingMediator mMediator = new ManualFillingMediator();
// TODO(fhorschig): Manual filling needs a model to map from tab/URL to sheet state/providers.
// Ideally, it just manages |Provider|s and attaches them to the accessory when tabs change.
/**
* Creates a the manual filling controller.
......@@ -29,39 +31,40 @@ public class ManualFillingCoordinator {
*/
public ManualFillingCoordinator(WindowAndroid windowAndroid, ViewStub keyboardAccessoryStub,
ViewStub accessorySheetStub) {
mKeyboardAccessory = new KeyboardAccessoryCoordinator(windowAndroid, keyboardAccessoryStub);
mAccessorySheet = new AccessorySheetCoordinator(accessorySheetStub);
assert windowAndroid.getActivity().get() != null;
KeyboardAccessoryCoordinator keyboardAccessory =
new KeyboardAccessoryCoordinator(windowAndroid, keyboardAccessoryStub, mMediator);
AccessorySheetCoordinator accessorySheet = new AccessorySheetCoordinator(
accessorySheetStub, keyboardAccessory::getPageChangeListener);
mMediator.initialize(keyboardAccessory, accessorySheet,
(ChromeActivity) windowAndroid.getActivity().get());
}
/**
* Cleans up the manual UI by destroying the accessory bar and its bottom sheet.
*/
public void destroy() {
mKeyboardAccessory.destroy();
mMediator.destroy();
}
/**
* Links a tab to the manual UI by adding it to the held {@link AccessorySheetCoordinator} and
* the {@link KeyboardAccessoryCoordinator}.
* @param tab The tab component to be added.
*/
public void addTab(KeyboardAccessoryData.Tab tab) {
mKeyboardAccessory.addTab(tab);
mAccessorySheet.addTab(tab);
// TODO(fhorschig): Ideally, this isn't exposed. (Apply Hollywood principle to tabs).
void addTab(KeyboardAccessoryData.Tab tab) {
mMediator.addTab(tab);
}
public void removeTab(KeyboardAccessoryData.Tab tab) {
mKeyboardAccessory.removeTab(tab);
mAccessorySheet.removeTab(tab);
// TODO(fhorschig): Ideally, this isn't exposed neither.
void removeTab(KeyboardAccessoryData.Tab tab) {
mMediator.removeTab(tab);
}
// TODO(fhorschig): Should be @VisibleForTesting.
/**
* Allows access to the keyboard accessory. This can be used to explicitly modify the the bar of
* the keyboard accessory (e.g. by providing suggestions or actions).
* @return The coordinator of the Keyboard accessory component.
*/
public KeyboardAccessoryCoordinator getKeyboardAccessory() {
return mKeyboardAccessory;
return mMediator.getKeyboardAccessory();
}
/**
......@@ -69,7 +72,7 @@ public class ManualFillingCoordinator {
* @return The coordinator of the Accessory sheet component.
*/
@VisibleForTesting
public AccessorySheetCoordinator getAccessorySheetForTesting() {
return mAccessorySheet;
AccessorySheetCoordinator getAccessorySheetForTesting() {
return mMediator.getAccessorySheet();
}
}
\ No newline at end of file
// 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.autofill.keyboard_accessory;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.ui.UiUtils;
/**
* This part of the manual filling component manages the state of the manual filling flow depending
* on the currently shown tab.
*/
class ManualFillingMediator implements KeyboardAccessoryCoordinator.VisibilityDelegate {
private KeyboardAccessoryCoordinator mKeyboardAccessory;
private AccessorySheetCoordinator mAccessorySheet;
private ChromeActivity mActivity; // Used to control the keyboard.
void initialize(KeyboardAccessoryCoordinator keyboardAccessory,
AccessorySheetCoordinator accessorySheet, ChromeActivity activity) {
mKeyboardAccessory = keyboardAccessory;
mAccessorySheet = accessorySheet;
mActivity = activity;
}
void destroy() {
// TODO(fhorschig): Remove all tabs. Destroy all providers.
// mPasswordAccessorySheet.destroy();
mKeyboardAccessory.destroy();
}
/**
* Links a tab to the manual UI by adding it to the held {@link AccessorySheetCoordinator} and
* the {@link KeyboardAccessoryCoordinator}.
* @param tab The tab component to be added.
*/
void addTab(KeyboardAccessoryData.Tab tab) {
// TODO(fhorschig): Should add Tabs per URL/chome tab.
mKeyboardAccessory.addTab(tab);
mAccessorySheet.addTab(tab);
}
void removeTab(KeyboardAccessoryData.Tab tab) {
// TODO(fhorschig): Should remove Tabs per URL/chome tab.
mKeyboardAccessory.removeTab(tab);
mAccessorySheet.removeTab(tab);
}
@Override
public void onChangeAccessorySheet(int tabIndex) {
assert mActivity != null : "ManualFillingMediator needs initialization.";
mAccessorySheet.setActiveTab(tabIndex);
}
@Override
public void onOpenAccessorySheet() {
assert mActivity != null : "ManualFillingMediator needs initialization.";
UiUtils.hideKeyboard(mActivity.getCurrentFocus());
mAccessorySheet.show();
}
@Override
public void onCloseAccessorySheet() {
assert mActivity != null : "ManualFillingMediator needs initialization.";
mAccessorySheet.hide();
UiUtils.showKeyboard(mActivity.getCurrentFocus());
}
@Override
public void onCloseKeyboardAccessory() {
assert mActivity != null : "ManualFillingMediator needs initialization.";
mAccessorySheet.hide();
UiUtils.hideKeyboard(mActivity.getCurrentFocus());
}
// TODO(fhorschig): Should be @VisibleForTesting.
AccessorySheetCoordinator getAccessorySheet() {
return mAccessorySheet;
}
// TODO(fhorschig): Should be @VisibleForTesting.
KeyboardAccessoryCoordinator getKeyboardAccessory() {
return mKeyboardAccessory;
}
}
......@@ -25,6 +25,8 @@ class PasswordAccessoryBridge {
PasswordAccessorySheetCoordinator passwordAccessorySheet =
new PasswordAccessorySheetCoordinator(activity);
mTab = passwordAccessorySheet.createTab();
// TODO(fhorschig): This is not correct - the passwords need to be mapped to a tab/URL. The
// provider can be registered to the ManualFillingCoordinator - not directly to the sheet.
passwordAccessorySheet.registerItemProvider(mItemProvider);
mManualFillingCoordinator.addTab(mTab);
}
......@@ -43,7 +45,7 @@ class PasswordAccessoryBridge {
@CalledByNative
private void destroy() {
mNativeView = 0;
mManualFillingCoordinator.removeTab(mTab);
mManualFillingCoordinator.removeTab(mTab); // TODO(fhorschig): Should be "unregister".
}
private Item[] convertToItems(
......
......@@ -7,6 +7,8 @@ package org.chromium.chrome.browser.modelutil;
import android.view.View;
import android.view.ViewStub;
import org.chromium.base.Callback;
import javax.annotation.Nullable;
/**
......@@ -41,7 +43,8 @@ public class LazyViewBinderAdapter<M extends PropertyObservable<K>, V extends Vi
}
@SuppressWarnings("unchecked") // The StubHolder always holds a V!
void inflateView() {
void inflateView(ViewStub.OnInflateListener inflationCallback) {
mViewStub.setOnInflateListener(inflationCallback);
mView = (V) mViewStub.inflate();
}
}
......@@ -78,24 +81,41 @@ public class LazyViewBinderAdapter<M extends PropertyObservable<K>, V extends Vi
}
private final SimpleViewBinder<M, V, K> mBinder;
private final Callback<V> mPostInflationCallback;
/**
* Calls {LazyViewBinderAdapter#LazyViewBinderAdapter(SimpleViewBinder, Callback)} without a
* post-inflation callback
* @param binder The binder to be called with the fully inflated view.
* @see LazyViewBinderAdapter#LazyViewBinderAdapter(SimpleViewBinder, Callback)
*/
public LazyViewBinderAdapter(SimpleViewBinder<M, V, K> binder) {
this(binder, (view) -> {});
}
/**
* Creates the adapter with a {@link SimpleViewBinder} that will be used to determine when to
* inflate the {@link StubHolder} that is passed in by the {@link PropertyModelChangeProcessor}.
* @param binder The binder to be called with the fully inflated view.
* @param postInflationCallback Callback called after inflation (before calling the ViewBinder).
*/
public LazyViewBinderAdapter(SimpleViewBinder<M, V, K> binder) {
public LazyViewBinderAdapter(
SimpleViewBinder<M, V, K> binder, Callback<V> postInflationCallback) {
mBinder = binder;
mPostInflationCallback = postInflationCallback;
}
@SuppressWarnings("unchecked") // The StubHolder always holds a V!
@Override
public void bind(M model, StubHolder<V> stubHolder, K propertyKey) {
if (stubHolder.getView() == null) {
if (propertyKey != mBinder.getVisibilityProperty() || !mBinder.isVisible(model)) {
return; // Ignore model changes before the view is shown for the first time.
}
stubHolder.inflateView();
mBinder.onInitialInflation(model, stubHolder.getView());
stubHolder.inflateView((viewStub, view) -> {
mPostInflationCallback.onResult((V) view);
mBinder.onInitialInflation(model, (V) view);
});
}
mBinder.bind(model, stubHolder.getView(), propertyKey);
}
......
......@@ -112,6 +112,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryView.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewBinder.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingMediator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessoryBridge.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewBinder.java",
......@@ -1621,6 +1622,8 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/autofill/AutofillTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/AutofillTestHelper.java",
"javatests/src/org/chromium/chrome/browser/autofill/PersonalDataManagerTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingTestHelper.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewTest.java",
"javatests/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetViewTest.java",
......
......@@ -61,8 +61,18 @@ public class AccessorySheetViewTest {
mStubHolder = new LazyViewBinderAdapter.StubHolder<>(
mActivityTestRule.getActivity().findViewById(R.id.keyboard_accessory_sheet_stub));
mModel = new AccessorySheetModel();
mModel.addObserver(new PropertyModelChangeProcessor<>(
mModel, mStubHolder, new LazyViewBinderAdapter<>(new AccessorySheetViewBinder())));
mModel.addObserver(new PropertyModelChangeProcessor<>(mModel, mStubHolder,
new LazyViewBinderAdapter<>(new AccessorySheetViewBinder(),
view -> view.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int i, float v, int i1) {}
@Override
public void onPageSelected(int i) {}
@Override
public void onPageScrollStateChanged(int i) {}
}))));
}
@Test
......
// 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.autofill.keyboard_accessory;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.ManualFillingTestHelper.selectTabAtPosition;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.ManualFillingTestHelper.whenDisplayed;
import android.support.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import java.util.concurrent.TimeoutException;
/**
* Integration tests for password accessory views. This integration test currently stops testing at
* the bridge - ideally, there should be an easy way to add a temporary account with temporary
* passwords.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class ManualFillingIntegrationTest {
@Rule
public final ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
new ChromeActivityTestRule<>(ChromeActivity.class);
private final ManualFillingTestHelper mHelper = new ManualFillingTestHelper(mActivityTestRule);
@Test
@SmallTest
public void testAccessoryIsAvailable() throws InterruptedException {
mHelper.loadTestPage(false);
mHelper.createTestTab();
Assert.assertNotNull("Controller for Manual filling should be available.",
mActivityTestRule.getActivity().getManualFillingController());
Assert.assertNotNull("Keyboard accessory should have an instance.",
mActivityTestRule.getActivity()
.getManualFillingController()
.getKeyboardAccessory());
Assert.assertNotNull("Accessory Sheet should have an instance.",
mActivityTestRule.getActivity()
.getManualFillingController()
.getAccessorySheetForTesting());
}
@Test
@SmallTest
public void testKeyboardAccessoryHiddenUntilKeyboardShows()
throws InterruptedException, TimeoutException {
mHelper.loadTestPage(false);
mHelper.createTestTab();
// Focus the field to bring up the accessory.
mHelper.clickPasswordField();
onView(withId(R.id.keyboard_accessory)).check(doesNotExist());
mHelper.waitForKeyboard();
// Check that ONLY the accessory is there but the sheet is still hidden.
whenDisplayed(withId(R.id.keyboard_accessory));
onView(withId(R.id.keyboard_accessory_sheet)).check(doesNotExist());
}
@Test
@SmallTest
public void testKeyboardAccessoryDisappearsWithKeyboard()
throws InterruptedException, TimeoutException {
mHelper.loadTestPage(false);
mHelper.createTestTab();
// Focus the field to bring up the accessory.
mHelper.clickPasswordField();
mHelper.waitForKeyboard();
whenDisplayed(withId(R.id.keyboard_accessory));
// Dismiss the keyboard to hide the accessory again.
mHelper.clickSubmit();
mHelper.waitForKeyboardToDisappear();
}
@Test
@SmallTest
public void testAccessorySheetHiddenUntilManuallyTriggered()
throws InterruptedException, TimeoutException {
mHelper.loadTestPage(false);
mHelper.createTestTab();
// Focus the field to bring up the accessory.
mHelper.clickPasswordField();
mHelper.waitForKeyboard();
// Check that ONLY the accessory is there but the sheet is still hidden.
whenDisplayed(withId(R.id.keyboard_accessory));
onView(withId(R.id.keyboard_accessory_sheet)).check(doesNotExist());
// Trigger the sheet and wait for it to open and the keyboard to disappear.
onView(withId(R.id.tabs)).perform(selectTabAtPosition(0));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withId(R.id.keyboard_accessory_sheet));
}
@Test
@SmallTest
public void testHidingSheetBringsBackKeyboard() throws InterruptedException, TimeoutException {
mHelper.loadTestPage(false);
mHelper.createTestTab();
// Focus the field to bring up the accessory.
mHelper.clickPasswordField();
mHelper.waitForKeyboard();
// Click the tab to show the sheet and hide the keyboard.
whenDisplayed(withId(R.id.tabs)).perform(selectTabAtPosition(0));
mHelper.waitForKeyboardToDisappear();
whenDisplayed(withId(R.id.keyboard_accessory_sheet));
// Click the tab again to hide the sheet and show the keyboard.
onView(withId(R.id.tabs)).perform(selectTabAtPosition(0));
mHelper.waitForKeyboard();
onView(withId(R.id.keyboard_accessory)).check(matches(isDisplayed()));
mHelper.waitToBeHidden(withId(R.id.keyboard_accessory_sheet));
}
// TODO(fhorschig): Check that it overlays info bars.
}
// 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.autofill.keyboard_accessory;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
import static org.hamcrest.core.AllOf.allOf;
import static org.chromium.chrome.test.util.ViewUtils.VIEW_GONE;
import static org.chromium.chrome.test.util.ViewUtils.VIEW_INVISIBLE;
import static org.chromium.chrome.test.util.ViewUtils.VIEW_NULL;
import static org.chromium.chrome.test.util.ViewUtils.waitForView;
import static org.chromium.ui.base.LocalizationUtils.setRtlForTesting;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.PerformException;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.ViewInteraction;
import android.view.View;
import android.view.ViewGroup;
import org.hamcrest.Matcher;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.content.browser.test.util.CriteriaHelper;
import org.chromium.content.browser.test.util.DOMUtils;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.UiUtils;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/**
* Helpers in this class simplify interactions with the Keyboard Accessory and the sheet below it.
*/
public class ManualFillingTestHelper {
private final ChromeActivityTestRule<ChromeActivity> mActivityTestRule;
private final AtomicReference<WebContents> mWebContentsRef = new AtomicReference<>();
ManualFillingTestHelper(ChromeActivityTestRule<ChromeActivity> activityTestRule) {
mActivityTestRule = activityTestRule;
}
public void loadTestPage(boolean isRtl) throws InterruptedException {
mActivityTestRule.startMainActivityWithURL(UrlUtils.encodeHtmlDataUri("<html"
+ (isRtl ? " dir=\"rtl\"" : "") + "><head>"
+ "<meta name=\"viewport\""
+ "content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" /></head>"
+ "<body><form method=\"POST\">"
+ "<input type=\"password\" id=\"password\"/><br>"
+ "<input type=\"text\" id=\"email\" autocomplete=\"email\" /><br>"
+ "<input type=\"submit\" id=\"submit\" />"
+ "</form></body></html>"));
setRtlForTesting(isRtl);
ThreadUtils.runOnUiThreadBlocking(
()
-> mWebContentsRef.set(
mActivityTestRule.getActivity().getActivityTab().getWebContents()));
DOMUtils.waitForNonZeroNodeBounds(mWebContentsRef.get(), "password");
}
public void waitForKeyboard() {
CriteriaHelper.pollUiThread(
()
-> UiUtils.isKeyboardShowing(InstrumentationRegistry.getContext(),
mActivityTestRule.getActivity().getCurrentFocus()));
}
public void waitForKeyboardToDisappear() {
CriteriaHelper.pollUiThread(
()
-> !UiUtils.isKeyboardShowing(InstrumentationRegistry.getContext(),
mActivityTestRule.getActivity().getCurrentFocus()));
}
public void clickPasswordField() throws TimeoutException, InterruptedException {
DOMUtils.clickNode(mWebContentsRef.get(), "password");
}
/**
* Although the submit button has no effect, it takes the focus from the input field and should
* hide the keyboard.
*/
public void clickSubmit() throws TimeoutException, InterruptedException {
DOMUtils.clickNode(mWebContentsRef.get(), "submit");
}
/**
* Creates and adds an empty tab without listener to keyboard accessory and sheet.
*/
public void createTestTab() {
mActivityTestRule.getActivity().getManualFillingController().addTab(
new KeyboardAccessoryData.Tab() {
@Override
public Drawable getIcon() {
return InstrumentationRegistry.getContext().getResources().getDrawable(
android.R.drawable.ic_lock_lock);
}
@Override
public String getContentDescription() {
return "TestTabDescription";
}
@Override
public int getTabLayout() {
return R.layout.empty_accessory_sheet;
}
@Nullable
@Override
public Listener getListener() {
return null;
}
});
}
/**
* Use in a |onView().perform| action to select the tab at |tabIndex| for the found tab layout.
* @param tabIndex The index to be selected.
* @return The action executed by |perform|.
*/
static public ViewAction selectTabAtPosition(int tabIndex) {
return new ViewAction() {
@Override
public Matcher<View> getConstraints() {
return allOf(isDisplayed(), isAssignableFrom(TabLayout.class));
}
@Override
public String getDescription() {
return "with tab at index " + tabIndex;
}
@Override
public void perform(UiController uiController, View view) {
TabLayout tabLayout = (TabLayout) view;
if (tabLayout.getTabAt(tabIndex) == null) {
throw new PerformException.Builder()
.withCause(new Throwable("No tab at index " + tabIndex))
.build();
}
tabLayout.getTabAt(tabIndex).select();
}
};
}
/**
* Use like {@link android.support.test.espresso.Espresso#onView}. It waits for a view matching
* the given |matcher| to be displayed and allows to chain checks/performs on the result.
* @param matcher The matcher matching exactly the view that is expected to be displayed.
* @return An interaction on the view matching |matcher.
*/
public static ViewInteraction whenDisplayed(Matcher<View> matcher) {
onView(isRoot()).check((r, e) -> waitForView((ViewGroup) r, allOf(matcher, isDisplayed())));
return onView(matcher);
}
public void waitToBeHidden(Matcher<View> matcher) {
onView(isRoot()).check((r, e) -> {
waitForView((ViewGroup) r, matcher, VIEW_INVISIBLE | VIEW_NULL | VIEW_GONE);
});
}
}
......@@ -11,6 +11,7 @@ import static org.junit.Assert.assertThat;
import android.graphics.drawable.Drawable;
import android.support.annotation.LayoutRes;
import android.support.test.filters.MediumTest;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.RecyclerView;
import android.text.method.PasswordTransformationMethod;
import android.widget.TextView;
......@@ -57,37 +58,35 @@ public class PasswordAccessorySheetViewTest {
*/
private void openLayoutInAccessorySheet(
@LayoutRes int layout, KeyboardAccessoryData.Tab.Listener listener) {
mActivityTestRule.getActivity()
.getManualFillingController()
.getAccessorySheetForTesting()
.addTab(new KeyboardAccessoryData.Tab() {
AccessorySheetCoordinator accessorySheet = new AccessorySheetCoordinator(
mActivityTestRule.getActivity().findViewById(R.id.keyboard_accessory_sheet_stub),
() -> new ViewPager.OnPageChangeListener() {
@Override
public Drawable getIcon() {
return null;
}
@Override
public String getContentDescription() {
return null;
}
public void onPageScrolled(int i, float v, int i1) {}
@Override
public @LayoutRes int getTabLayout() {
return layout;
}
public void onPageSelected(int i) {}
@Override
public Listener getListener() {
return listener;
}
public void onPageScrollStateChanged(int i) {}
});
ThreadUtils.runOnUiThreadBlocking(() -> {
mActivityTestRule.getActivity()
.getManualFillingController()
.getAccessorySheetForTesting()
.getMediatorForTesting()
.show();
accessorySheet.addTab(new KeyboardAccessoryData.Tab() {
@Override
public Drawable getIcon() {
return null;
}
@Override
public String getContentDescription() {
return null;
}
@Override
public @LayoutRes int getTabLayout() {
return layout;
}
@Override
public Listener getListener() {
return listener;
}
});
ThreadUtils.runOnUiThreadBlocking(accessorySheet::show);
}
@Before
......
......@@ -58,7 +58,7 @@ public class AccessorySheetControllerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockViewStub.inflate()).thenReturn(mMockView);
mCoordinator = new AccessorySheetCoordinator(mMockViewStub);
mCoordinator = new AccessorySheetCoordinator(mMockViewStub, /*unused*/ () -> null);
mMediator = mCoordinator.getMediatorForTesting();
mModel = mMediator.getModelForTesting();
}
......
......@@ -15,7 +15,6 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
import android.view.ViewStub;
import org.junit.Before;
......@@ -26,7 +25,6 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.autofill.AutofillKeyboardSuggestions;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Action;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.PropertyProvider;
......@@ -47,6 +45,8 @@ public class KeyboardAccessoryControllerTest {
@Mock
private ListObservable.ListObserver<Void> mMockActionListObserver;
@Mock
private KeyboardAccessoryCoordinator.VisibilityDelegate mMockVisibilityDelegate;
@Mock
private WindowAndroid mMockWindow;
@Mock
private ViewStub mMockViewStub;
......@@ -75,14 +75,13 @@ public class KeyboardAccessoryControllerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockViewStub.inflate()).thenReturn(mMockView);
mCoordinator = new KeyboardAccessoryCoordinator(mMockWindow, mMockViewStub);
mCoordinator = new KeyboardAccessoryCoordinator(
mMockWindow, mMockViewStub, mMockVisibilityDelegate);
mMediator = mCoordinator.getMediatorForTesting();
mModel = mMediator.getModelForTesting();
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testCreatesValidSubComponents() {
assertThat(mCoordinator, is(notNullValue()));
assertThat(mMediator, is(notNullValue()));
......@@ -91,8 +90,6 @@ public class KeyboardAccessoryControllerTest {
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testModelNotifiesVisibilityChangeOnShowAndHide() {
mModel.addObserver(mMockPropertyObserver);
......@@ -110,8 +107,6 @@ public class KeyboardAccessoryControllerTest {
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testChangingTabsNotifiesTabObserver() {
mModel.addTabListObserver(mMockTabListObserver);
......@@ -128,8 +123,6 @@ public class KeyboardAccessoryControllerTest {
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testModelNotifiesAboutActionsChangedByProvider() {
final PropertyProvider<Action> testProvider = new PropertyProvider<>();
final FakeAction testAction = new FakeAction();
......@@ -160,8 +153,6 @@ public class KeyboardAccessoryControllerTest {
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testModelDoesntNotifyUnchangedData() {
mModel.addObserver(mMockPropertyObserver);
......@@ -179,8 +170,6 @@ public class KeyboardAccessoryControllerTest {
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testIsVisibleWithSuggestionsBeforeKeyboardComesUp() {
// Without suggestions, the accessory should remain invisible - even if the keyboard shows.
assertThat(mModel.getAutofillSuggestions(), is(nullValue()));
......@@ -199,8 +188,6 @@ public class KeyboardAccessoryControllerTest {
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testIsVisibleWithSuggestionsAfterKeyboardComesUp() {
// Without any suggestions, the accessory should remain invisible.
assertThat(mModel.getAutofillSuggestions(), is(nullValue()));
......@@ -216,8 +203,6 @@ public class KeyboardAccessoryControllerTest {
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testIsVisibleWithActions() {
// Without any actions, the accessory should remain invisible.
assertThat(mModel.getActionList().getItemCount(), is(0));
......@@ -230,8 +215,6 @@ public class KeyboardAccessoryControllerTest {
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testIsVisibleWithTabs() {
// Without any actions, the accessory should remain invisible.
assertThat(mModel.getActionList().getItemCount(), is(0));
......
......@@ -21,9 +21,12 @@ import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.ui.base.WindowAndroid;
import java.lang.ref.WeakReference;
/**
* Controller tests for the root controller for interactions with the manual filling UI.
*/
......@@ -33,6 +36,8 @@ public class ManualFillingControllerTest {
@Mock
private WindowAndroid mMockWindow;
@Mock
private ChromeActivity mMockActivity;
@Mock
private ViewStub mMockViewStub;
@Mock
private KeyboardAccessoryView mMockView;
......@@ -47,6 +52,7 @@ public class ManualFillingControllerTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockViewStub.inflate()).thenReturn(mMockView);
when(mMockWindow.getActivity()).thenReturn(new WeakReference<>(mMockActivity));
mController = new ManualFillingCoordinator(mMockWindow, mMockViewStub, mMockViewStub);
}
......
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