Commit e95ccb91 authored by Friedrich Horschig's avatar Friedrich Horschig Committed by Commit Bot

Reland "Render Tabs into Bottom sheet"

This reverts commit 58e3dc5d.

Reason for revert: The failed CrOS test doesn't seem affected
by the newly introduced Android code. 

Original change's description:
> Revert "Render Tabs into Bottom sheet"
> 
> This reverts commit a4ae2e1f.
> 
> Reason for revert:
> All/AutofillInteractiveIsolationTest.SimpleCrossSiteFill/1 started
> to fail on ChromeOS likely after this change.
> https://ci.chromium.org/p/chromium/builders/luci.chromium.ci/linux-chromeos-rel/8133
> https://ci.chromium.org/buildbot/chromium.chromiumos/linux-chromeos-dbg/5701
> 
> Original change's description:
> > Render Tabs into Bottom sheet
> > 
> > This CL shapes the role of a KeyboardAccessoryData.Tab element:
> > It is an element that provides a specific benefit to the Manual UI by...
> > ... providing data that is used by the bar to render a tab (upcoming)
> > ... providing the content for a separate tab in the bottom sheet.
> > 
> > This means, that the bottom sheet provides space for tabs and manages the
> > general visibility and active states of tabs.
> > The exact content of a tab doesn't matter to the AccessorySheetComponent.
> > That way, it should become fairly straight forward to implement a new tab
> > that is rendered into bottom sheet and accessory.
> > 
> > The first concrete instance of a bottom sheet will follow soon: a
> > bottom sheet that contains password related actions and data.
> > (It's important to not mix this specific data into the AccessorySheet
> > component as other upcoming sheets are already planned, like one for
> > payments and one for address data.)
> > 
> > Bug: 811747
> > Change-Id: Id8bd8c389496246166d26a6c298056a142c36566
> > Reviewed-on: https://chromium-review.googlesource.com/1047285
> > Commit-Queue: Friedrich Horschig <fhorschig@chromium.org>
> > Reviewed-by: Bernhard Bauer <bauerb@chromium.org>
> > Cr-Commit-Position: refs/heads/master@{#559180}
> 
> TBR=bauerb@chromium.org,fhorschig@chromium.org
> 
> Change-Id: I879e4e89c674d47562c662b5c2bbac3487546062
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Bug: 811747
> Reviewed-on: https://chromium-review.googlesource.com/1063732
> Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
> Commit-Queue: Kinuko Yasuda <kinuko@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#559463}

TBR=kinuko@chromium.org,bauerb@chromium.org,fhorschig@chromium.org

Change-Id: I17ef937cfed19aa4b3fe9b10220b77ac349ffb31
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 811747
Reviewed-on: https://chromium-review.googlesource.com/1063790Reviewed-by: default avatarFriedrich Horschig <fhorschig@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Friedrich Horschig <fhorschig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#559469}
parent b7bcd807
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<!-- This Layout is mainly used in tests. Therefore use possible UnusedResource warnings. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="UnusedResources"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
...@@ -3,14 +3,11 @@ ...@@ -3,14 +3,11 @@
Use of this source code is governed by a BSD-style license that can be Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. --> found in the LICENSE file. -->
<LinearLayout <android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keyboard_accessory"
android:background="@drawable/keyboard_accessory_background" android:background="@drawable/keyboard_accessory_background"
android:contentDescription="@string/autofill_keyboard_accessory_content_description"
android:fillViewport="true" android:fillViewport="true"
android:layout_gravity="start|bottom" android:layout_gravity="start|bottom"
android:layout_height="@dimen/keyboard_accessory_sheet_height" android:layout_height="@dimen/keyboard_accessory_sheet_height"
android:layout_width="match_parent" android:layout_width="match_parent"
android:orientation="vertical"
android:visibility="gone"/> android:visibility="gone"/>
\ No newline at end of file
...@@ -59,6 +59,7 @@ import org.chromium.chrome.browser.appmenu.AppMenuHandler; ...@@ -59,6 +59,7 @@ import org.chromium.chrome.browser.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.appmenu.AppMenuObserver; import org.chromium.chrome.browser.appmenu.AppMenuObserver;
import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate; import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryCoordinator; import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryCoordinator;
import org.chromium.chrome.browser.autofill.keyboard_accessory.ManualFillingCoordinator;
import org.chromium.chrome.browser.bookmarks.BookmarkModel; import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.BookmarkUtils; import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
import org.chromium.chrome.browser.compositor.CompositorViewHolder; import org.chromium.chrome.browser.compositor.CompositorViewHolder;
...@@ -266,7 +267,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -266,7 +267,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
private BottomSheet mBottomSheet; private BottomSheet mBottomSheet;
private ContextualSuggestionsCoordinator mContextualSuggestionsCoordinator; private ContextualSuggestionsCoordinator mContextualSuggestionsCoordinator;
private FadingBackgroundView mFadingBackgroundView; private FadingBackgroundView mFadingBackgroundView;
private KeyboardAccessoryCoordinator mKeyboardAccessoryCoordinator; private ManualFillingCoordinator mManualFillingController;
// Time in ms that it took took us to inflate the initial layout // Time in ms that it took took us to inflate the initial layout
private long mInflateInitialLayoutDurationMs; private long mInflateInitialLayoutDurationMs;
...@@ -391,8 +392,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -391,8 +392,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
// SurfaceView's 'hole' clipping during animations that are notified to the window. // SurfaceView's 'hole' clipping during animations that are notified to the window.
getWindowAndroid().setAnimationPlaceholderView(mCompositorViewHolder.getCompositorView()); getWindowAndroid().setAnimationPlaceholderView(mCompositorViewHolder.getCompositorView());
mKeyboardAccessoryCoordinator = new KeyboardAccessoryCoordinator( mManualFillingController = new ManualFillingCoordinator(getWindowAndroid(),
getWindowAndroid(), findViewById(R.id.keyboard_accessory_stub)); findViewById(R.id.keyboard_accessory_stub),
findViewById(R.id.keyboard_accessory_sheet_stub));
initializeToolbar(); initializeToolbar();
initializeTabModels(); initializeTabModels();
...@@ -676,7 +678,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -676,7 +678,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
* @return The {@link KeyboardAccessoryCoordinator} that belongs to this activity. * @return The {@link KeyboardAccessoryCoordinator} that belongs to this activity.
*/ */
public KeyboardAccessoryCoordinator getKeyboardAccessory() { public KeyboardAccessoryCoordinator getKeyboardAccessory() {
return mKeyboardAccessoryCoordinator; return mManualFillingController.getKeyboardAccessory();
} }
/** /**
...@@ -1223,9 +1225,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity ...@@ -1223,9 +1225,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
mTabContentManager = null; mTabContentManager = null;
} }
if (mKeyboardAccessoryCoordinator != null) { if (mManualFillingController != null) {
mKeyboardAccessoryCoordinator.destroy(); mManualFillingController.destroy();
mKeyboardAccessoryCoordinator = null; mManualFillingController = null;
} }
AccessibilityManager manager = (AccessibilityManager) AccessibilityManager manager = (AccessibilityManager)
......
// 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 android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;
import java.util.HashMap;
import java.util.Map;
/**
* This {@link PagerAdapter} renders an observable list of {@link KeyboardAccessoryData.Tab}s into a
* {@link ViewPager}. It instantiates the tab views based on the layout they provide.
*/
class AccessoryPagerAdapter extends PagerAdapter {
private final SimpleListObservable<KeyboardAccessoryData.Tab> mTabList;
private final Map<KeyboardAccessoryData.Tab, ViewGroup> mViews;
/**
* Creates the PagerAdapter that populates a ViewPager based on a held list of tabs.
* @param tabList The list that contains the tabs to instantiate.
*/
public AccessoryPagerAdapter(SimpleListObservable<KeyboardAccessoryData.Tab> tabList) {
mTabList = tabList;
mViews = new HashMap<>(mTabList.getItemCount());
}
@NonNull
@Override
public Object instantiateItem(@NonNull ViewGroup container, int position) {
KeyboardAccessoryData.Tab tab = mTabList.get(position);
ViewGroup layout = mViews.get(tab);
if (layout == null) {
layout = (ViewGroup) LayoutInflater.from(container.getContext())
.inflate(tab.getTabLayout(), container, false);
mViews.put(tab, layout);
if (container.indexOfChild(layout) == -1) container.addView(layout);
if (tab.getListener() != null) {
tab.getListener().onTabCreated(layout);
}
}
return layout;
}
@Override
public void destroyItem(@NonNull ViewGroup container, int position, @Nullable Object object) {
if (mViews.get(mTabList.get(position)) == null) return;
ViewGroup layout = mViews.get(mTabList.get(position));
if (container.indexOfChild(layout) != -1) container.removeView(layout);
}
@Override
public int getCount() {
return mTabList.getItemCount();
}
@Override
public boolean isViewFromObject(@NonNull View view, @NonNull Object o) {
return view == o;
}
}
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.view.View; import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.ViewStub; import android.view.ViewStub;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
...@@ -14,7 +16,7 @@ import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor; ...@@ -14,7 +16,7 @@ import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
/** /**
* Creates and owns all elements which are part of the accessory sheet component. * Creates and owns all elements which are part of the accessory sheet component.
* It's part of the controller but will mainly forward events (like showing the sheet) and handle * It's part of the controller but will mainly forward events (like showing the sheet) and handle
* communication with the {@link ManualFillingController} (e.g. add a tab to trigger the sheet). * communication with the {@link ManualFillingCoordinator} (e.g. add a tab to trigger the sheet).
* to the {@link AccessorySheetMediator}. * to the {@link AccessorySheetMediator}.
*/ */
public class AccessorySheetCoordinator { public class AccessorySheetCoordinator {
...@@ -26,7 +28,7 @@ public class AccessorySheetCoordinator { ...@@ -26,7 +28,7 @@ public class AccessorySheetCoordinator {
* @param viewStub The view stub that can be inflated into the accessory layout. * @param viewStub The view stub that can be inflated into the accessory layout.
*/ */
public AccessorySheetCoordinator(ViewStub viewStub) { public AccessorySheetCoordinator(ViewStub viewStub) {
LazyViewBinderAdapter.StubHolder<View> stubHolder = LazyViewBinderAdapter.StubHolder<ViewPager> stubHolder =
new LazyViewBinderAdapter.StubHolder<>(viewStub); new LazyViewBinderAdapter.StubHolder<>(viewStub);
AccessorySheetModel model = new AccessorySheetModel(); AccessorySheetModel model = new AccessorySheetModel();
model.addObserver(new PropertyModelChangeProcessor<>( model.addObserver(new PropertyModelChangeProcessor<>(
...@@ -34,10 +36,34 @@ public class AccessorySheetCoordinator { ...@@ -34,10 +36,34 @@ public class AccessorySheetCoordinator {
mMediator = new AccessorySheetMediator(model); mMediator = new AccessorySheetMediator(model);
} }
/**
* Creates the {@link PagerAdapter} for the newly inflated {@link ViewPager}.
* If any ListModelChangeProcessor<> is needed, it would be created here. Currently, connecting
* the model.getTabList() to the tabViewBinder would have no effect as only the change of
* ACTIVE_TAB affects the view.
* @param model The model containing the list of tabs to be displayed.
* @return A fully initialized {@link PagerAdapter}.
*/
static PagerAdapter createTabViewAdapter(AccessorySheetModel model) {
return new AccessoryPagerAdapter(model.getTabList());
}
/**
* 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.
* Careful, if you want to show this tab as icon in the KeyboardAccessory, use the method
* {@link ManualFillingCoordinator#addTab(KeyboardAccessoryData.Tab)} instead.
* @param tab The tab which should be added to the AccessorySheet.
*/
void addTab(KeyboardAccessoryData.Tab tab) {
mMediator.addTab(tab);
}
/** /**
* Returns a {@link KeyboardAccessoryData.Tab} object that is used to display this bottom sheet. * Returns a {@link KeyboardAccessoryData.Tab} object that is used to display this bottom sheet.
* @return Returns a {@link KeyboardAccessoryData.Tab}. * @return Returns a {@link KeyboardAccessoryData.Tab}.
*/ */
@Nullable
public KeyboardAccessoryData.Tab getTab() { public KeyboardAccessoryData.Tab getTab() {
return mMediator.getTab(); return mMediator.getTab();
} }
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.annotation.Nullable;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
/** /**
...@@ -12,14 +14,15 @@ import org.chromium.base.VisibleForTesting; ...@@ -12,14 +14,15 @@ import org.chromium.base.VisibleForTesting;
*/ */
class AccessorySheetMediator { class AccessorySheetMediator {
private final AccessorySheetModel mModel; private final AccessorySheetModel mModel;
private KeyboardAccessoryData.Tab mTab;
AccessorySheetMediator(AccessorySheetModel model) { AccessorySheetMediator(AccessorySheetModel model) {
mModel = model; mModel = model;
} }
@Nullable
KeyboardAccessoryData.Tab getTab() { KeyboardAccessoryData.Tab getTab() {
return null; // TODO(fhorschig): Return the active tab. if (mModel.getActiveTabIndex() == AccessorySheetModel.NO_ACTIVE_TAB) return null;
return mModel.getTabList().get(mModel.getActiveTabIndex());
} }
@VisibleForTesting @VisibleForTesting
...@@ -34,4 +37,11 @@ class AccessorySheetMediator { ...@@ -34,4 +37,11 @@ class AccessorySheetMediator {
public void hide() { public void hide() {
mModel.setVisible(false); mModel.setVisible(false);
} }
public void addTab(KeyboardAccessoryData.Tab tab) {
mModel.getTabList().add(tab);
if (mModel.getActiveTabIndex() == AccessorySheetModel.NO_ACTIVE_TAB) {
mModel.setActiveTabIndex(mModel.getTabList().getItemCount() - 1);
}
}
} }
\ No newline at end of file
...@@ -4,7 +4,9 @@ ...@@ -4,7 +4,9 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab;
import org.chromium.chrome.browser.modelutil.PropertyObservable; import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;
/** /**
* This model holds all view state of the accessory sheet. * This model holds all view state of the accessory sheet.
...@@ -12,10 +14,22 @@ import org.chromium.chrome.browser.modelutil.PropertyObservable; ...@@ -12,10 +14,22 @@ import org.chromium.chrome.browser.modelutil.PropertyObservable;
* like the view binder react. * like the view binder react.
*/ */
class AccessorySheetModel extends PropertyObservable<AccessorySheetModel.PropertyKey> { class AccessorySheetModel extends PropertyObservable<AccessorySheetModel.PropertyKey> {
public static class PropertyKey { public static final PropertyKey VISIBLE = new PropertyKey(); } public static class PropertyKey {
public static final PropertyKey ACTIVE_TAB_INDEX = new PropertyKey();
public static final PropertyKey VISIBLE = new PropertyKey();
}
public static final int NO_ACTIVE_TAB = -1;
private int mActiveTabIndex = NO_ACTIVE_TAB;
private boolean mVisible; private boolean mVisible;
private final SimpleListObservable<Tab> mTabList = new SimpleListObservable<>();
SimpleListObservable<Tab> getTabList() {
return mTabList;
}
public void setVisible(boolean visible) { void setVisible(boolean visible) {
if (mVisible == visible) return; // Nothing to do here: same value. if (mVisible == visible) return; // Nothing to do here: same value.
mVisible = visible; mVisible = visible;
notifyPropertyChanged(PropertyKey.VISIBLE); notifyPropertyChanged(PropertyKey.VISIBLE);
...@@ -24,4 +38,17 @@ class AccessorySheetModel extends PropertyObservable<AccessorySheetModel.Propert ...@@ -24,4 +38,17 @@ class AccessorySheetModel extends PropertyObservable<AccessorySheetModel.Propert
boolean isVisible() { boolean isVisible() {
return mVisible; return mVisible;
} }
int getActiveTabIndex() {
return mActiveTabIndex;
}
void setActiveTabIndex(int activeTabPosition) {
if (mActiveTabIndex == activeTabPosition) return;
assert((activeTabPosition >= 0 && activeTabPosition < mTabList.getItemCount())
|| activeTabPosition == NO_ACTIVE_TAB)
: "Tried to set invalid index '" + activeTabPosition + "' as active tab!";
mActiveTabIndex = activeTabPosition;
notifyPropertyChanged(PropertyKey.ACTIVE_TAB_INDEX);
}
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.v4.view.ViewPager;
import android.view.View; import android.view.View;
import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetModel.PropertyKey; import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetModel.PropertyKey;
...@@ -14,7 +15,8 @@ import org.chromium.chrome.browser.modelutil.LazyViewBinderAdapter; ...@@ -14,7 +15,8 @@ import org.chromium.chrome.browser.modelutil.LazyViewBinderAdapter;
* {@link AccessorySheetViewBinder} which will modify the view accordingly. * {@link AccessorySheetViewBinder} which will modify the view accordingly.
*/ */
class AccessorySheetViewBinder class AccessorySheetViewBinder
implements LazyViewBinderAdapter.SimpleViewBinder<AccessorySheetModel, View, PropertyKey> { implements LazyViewBinderAdapter
.SimpleViewBinder<AccessorySheetModel, ViewPager, PropertyKey> {
@Override @Override
public PropertyKey getVisibilityProperty() { public PropertyKey getVisibilityProperty() {
return PropertyKey.VISIBLE; return PropertyKey.VISIBLE;
...@@ -26,14 +28,23 @@ class AccessorySheetViewBinder ...@@ -26,14 +28,23 @@ class AccessorySheetViewBinder
} }
@Override @Override
public void onInitialInflation(AccessorySheetModel model, View inflatedView) {} public void onInitialInflation(AccessorySheetModel model, ViewPager inflatedView) {
if (model.getActiveTabIndex() != -1) inflatedView.setCurrentItem(model.getActiveTabIndex());
inflatedView.setAdapter(AccessorySheetCoordinator.createTabViewAdapter(model));
}
@Override @Override
public void bind(AccessorySheetModel model, View inflatedView, PropertyKey propertyKey) { public void bind(AccessorySheetModel model, ViewPager inflatedView, PropertyKey propertyKey) {
if (propertyKey == PropertyKey.VISIBLE) { if (propertyKey == PropertyKey.VISIBLE) {
inflatedView.setVisibility(model.isVisible() ? View.VISIBLE : View.GONE); inflatedView.setVisibility(model.isVisible() ? View.VISIBLE : View.GONE);
return; return;
} }
if (propertyKey == PropertyKey.ACTIVE_TAB_INDEX) {
if (model.getActiveTabIndex() != AccessorySheetModel.NO_ACTIVE_TAB) {
inflatedView.setCurrentItem(model.getActiveTabIndex());
}
return;
}
assert false : "Every possible property update needs to be handled!"; assert false : "Every possible property update needs to be handled!";
} }
} }
...@@ -16,6 +16,7 @@ import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor; ...@@ -16,6 +16,7 @@ import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor; import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter; import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
import org.chromium.chrome.browser.modelutil.RecyclerViewModelChangeProcessor; import org.chromium.chrome.browser.modelutil.RecyclerViewModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;
import org.chromium.ui.UiUtils; import org.chromium.ui.UiUtils;
import org.chromium.ui.base.WindowAndroid; import org.chromium.ui.base.WindowAndroid;
...@@ -49,12 +50,11 @@ public class KeyboardAccessoryCoordinator { ...@@ -49,12 +50,11 @@ public class KeyboardAccessoryCoordinator {
* @param model the {@link KeyboardAccessoryModel} the adapter gets its data from. * @param model the {@link KeyboardAccessoryModel} the adapter gets its data from.
* @return Returns a fully initialized and wired adapter to an ActionViewBinder. * @return Returns a fully initialized and wired adapter to an ActionViewBinder.
*/ */
static RecyclerViewAdapter<KeyboardAccessoryModel.SimpleListObservable<Action>, static RecyclerViewAdapter<SimpleListObservable<Action>, ActionViewBinder.ViewHolder>
ActionViewBinder.ViewHolder>
createActionsAdapter(KeyboardAccessoryModel model) { createActionsAdapter(KeyboardAccessoryModel model) {
RecyclerViewAdapter<KeyboardAccessoryModel.SimpleListObservable<Action>, RecyclerViewAdapter<SimpleListObservable<Action>, ActionViewBinder.ViewHolder>
ActionViewBinder.ViewHolder> actionsAdapter = actionsAdapter =
new RecyclerViewAdapter<>(model.getActionList(), new ActionViewBinder()); new RecyclerViewAdapter<>(model.getActionList(), new ActionViewBinder());
model.addActionListObserver(new RecyclerViewModelChangeProcessor<>(actionsAdapter)); model.addActionListObserver(new RecyclerViewModelChangeProcessor<>(actionsAdapter));
return actionsAdapter; return actionsAdapter;
} }
...@@ -79,7 +79,7 @@ public class KeyboardAccessoryCoordinator { ...@@ -79,7 +79,7 @@ public class KeyboardAccessoryCoordinator {
* the start of the accessory. It is meant to trigger various bottom sheets. * the start of the accessory. It is meant to trigger various bottom sheets.
* @param tab The tab which contains representation data and links back to a bottom sheet. * @param tab The tab which contains representation data and links back to a bottom sheet.
*/ */
public void addTab(KeyboardAccessoryData.Tab tab) { void addTab(KeyboardAccessoryData.Tab tab) {
mMediator.addTab(tab); mMediator.addTab(tab);
} }
...@@ -88,7 +88,7 @@ public class KeyboardAccessoryCoordinator { ...@@ -88,7 +88,7 @@ public class KeyboardAccessoryCoordinator {
* from the accessory. * from the accessory.
* @param tab The tab to be removed. * @param tab The tab to be removed.
*/ */
public void removeTab(KeyboardAccessoryData.Tab tab) { void removeTab(KeyboardAccessoryData.Tab tab) {
mMediator.removeTab(tab); mMediator.removeTab(tab);
} }
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.view.ViewGroup;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -46,6 +49,17 @@ public class KeyboardAccessoryData { ...@@ -46,6 +49,17 @@ public class KeyboardAccessoryData {
* accessory. Typically, a tab is responsible to change the bottom sheet below the accessory. * accessory. Typically, a tab is responsible to change the bottom sheet below the accessory.
*/ */
public interface Tab { public interface Tab {
/**
* A Tab's Listener get's notified when e.g. the Tab was assigned a view.
*/
interface Listener {
/**
* Triggered when the tab was successfully created.
* @param view The newly created accessory sheet of the tab.
*/
void onTabCreated(ViewGroup view);
}
/** /**
* Provides the icon that will be displayed in the {@link KeyboardAccessoryCoordinator}. * Provides the icon that will be displayed in the {@link KeyboardAccessoryCoordinator}.
* @return The small icon that identifies this tab uniquely. * @return The small icon that identifies this tab uniquely.
...@@ -57,6 +71,20 @@ public class KeyboardAccessoryData { ...@@ -57,6 +71,20 @@ public class KeyboardAccessoryData {
* @return A short string describing the task of this tab. * @return A short string describing the task of this tab.
*/ */
String getContentDescription(); String getContentDescription();
/**
* Returns the tab layout which allows to create the tab's view on demand.
* @return The layout resource that allows to create the view necessary for this tab.
*/
@LayoutRes
int getTabLayout();
/**
* Returns the listener which might need to react on changes to this tab.
* @return A {@link Listener} to be called, e.g. when the tab is created.
*/
@Nullable
Listener getListener();
} }
/** /**
......
...@@ -7,9 +7,9 @@ package org.chromium.chrome.browser.autofill.keyboard_accessory; ...@@ -7,9 +7,9 @@ package org.chromium.chrome.browser.autofill.keyboard_accessory;
import org.chromium.chrome.browser.autofill.AutofillKeyboardSuggestions; import org.chromium.chrome.browser.autofill.AutofillKeyboardSuggestions;
import org.chromium.chrome.browser.modelutil.ListObservable; import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyObservable; import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
/** /**
...@@ -34,53 +34,6 @@ class KeyboardAccessoryModel extends PropertyObservable<KeyboardAccessoryModel.P ...@@ -34,53 +34,6 @@ class KeyboardAccessoryModel extends PropertyObservable<KeyboardAccessoryModel.P
} }
} }
/** A {@link ListObservable} containing an {@link ArrayList} of Tabs or Actions. */
class SimpleListObservable<T> extends ListObservable {
private final List<T> mItems = new ArrayList<>();
public T get(int index) {
return mItems.get(index);
}
@Override
public int getItemCount() {
return mItems.size();
}
void add(T item) {
mItems.add(item);
notifyItemRangeInserted(mItems.size() - 1, 1);
}
void remove(T item) {
int position = mItems.indexOf(item);
if (position == -1) {
return;
}
mItems.remove(position);
notifyItemRangeRemoved(position, 1);
}
void set(T[] newItems) {
if (mItems.isEmpty()) {
if (newItems.length == 0) {
return; // Nothing to do, nothing changes.
}
mItems.addAll(Arrays.asList(newItems));
notifyItemRangeInserted(0, mItems.size());
return;
}
int oldSize = mItems.size();
mItems.clear();
if (newItems.length == 0) {
notifyItemRangeRemoved(0, oldSize);
return;
}
mItems.addAll(Arrays.asList(newItems));
notifyItemRangeChanged(0, Math.max(oldSize, mItems.size()), this);
}
}
private SimpleListObservable<KeyboardAccessoryData.Action> mActionListObservable; private SimpleListObservable<KeyboardAccessoryData.Action> mActionListObservable;
private SimpleListObservable<KeyboardAccessoryData.Tab> mTabListObservable; private SimpleListObservable<KeyboardAccessoryData.Tab> mTabListObservable;
private boolean mVisible; private boolean mVisible;
......
...@@ -15,6 +15,7 @@ import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessory ...@@ -15,6 +15,7 @@ import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessory
import org.chromium.chrome.browser.modelutil.LazyViewBinderAdapter; import org.chromium.chrome.browser.modelutil.LazyViewBinderAdapter;
import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor; import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter; import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;
import org.chromium.ui.widget.ButtonCompat; import org.chromium.ui.widget.ButtonCompat;
/** /**
...@@ -24,8 +25,9 @@ import org.chromium.ui.widget.ButtonCompat; ...@@ -24,8 +25,9 @@ import org.chromium.ui.widget.ButtonCompat;
class KeyboardAccessoryViewBinder class KeyboardAccessoryViewBinder
implements LazyViewBinderAdapter.SimpleViewBinder<KeyboardAccessoryModel, implements LazyViewBinderAdapter.SimpleViewBinder<KeyboardAccessoryModel,
KeyboardAccessoryView, PropertyKey> { KeyboardAccessoryView, PropertyKey> {
static class ActionViewBinder implements RecyclerViewAdapter.ViewBinder< static class ActionViewBinder
KeyboardAccessoryModel.SimpleListObservable<Action>, ActionViewBinder.ViewHolder> { implements RecyclerViewAdapter.ViewBinder<SimpleListObservable<Action>,
ActionViewBinder.ViewHolder> {
static class ViewHolder extends RecyclerView.ViewHolder { static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(ButtonCompat actionView) { public ViewHolder(ButtonCompat actionView) {
super(actionView); super(actionView);
...@@ -44,8 +46,8 @@ class KeyboardAccessoryViewBinder ...@@ -44,8 +46,8 @@ class KeyboardAccessoryViewBinder
} }
@Override @Override
public void onBindViewHolder(KeyboardAccessoryModel.SimpleListObservable<Action> actions, public void onBindViewHolder(
ViewHolder holder, int position) { SimpleListObservable<Action> actions, ViewHolder holder, int position) {
final Action action = actions.get(position); final Action action = actions.get(position);
holder.getActionView().setText(action.getCaption()); holder.getActionView().setText(action.getCaption());
holder.getActionView().setOnClickListener( holder.getActionView().setOnClickListener(
...@@ -53,11 +55,12 @@ class KeyboardAccessoryViewBinder ...@@ -53,11 +55,12 @@ class KeyboardAccessoryViewBinder
} }
} }
static class TabViewBinder implements ListModelChangeProcessor.ViewBinder< static class TabViewBinder
KeyboardAccessoryModel.SimpleListObservable<Tab>, KeyboardAccessoryView> { implements ListModelChangeProcessor
.ViewBinder<SimpleListObservable<Tab>, KeyboardAccessoryView> {
@Override @Override
public void onItemsInserted(KeyboardAccessoryModel.SimpleListObservable<Tab> model, public void onItemsInserted(
KeyboardAccessoryView view, int index, int count) { SimpleListObservable<Tab> model, KeyboardAccessoryView view, int index, int count) {
assert count > 0 : "Tried to insert invalid amount of tabs - must be at least one."; assert count > 0 : "Tried to insert invalid amount of tabs - must be at least one.";
while (count-- > 0) { while (count-- > 0) {
Tab tab = model.get(index); Tab tab = model.get(index);
...@@ -67,8 +70,8 @@ class KeyboardAccessoryViewBinder ...@@ -67,8 +70,8 @@ class KeyboardAccessoryViewBinder
} }
@Override @Override
public void onItemsRemoved(KeyboardAccessoryModel.SimpleListObservable<Tab> model, public void onItemsRemoved(
KeyboardAccessoryView view, int index, int count) { SimpleListObservable<Tab> model, KeyboardAccessoryView view, int index, int count) {
assert count > 0 : "Tried to remove invalid amount of tabs - must be at least one."; assert count > 0 : "Tried to remove invalid amount of tabs - must be at least one.";
while (count-- > 0) { while (count-- > 0) {
view.removeTabAt(index++); view.removeTabAt(index++);
...@@ -76,14 +79,13 @@ class KeyboardAccessoryViewBinder ...@@ -76,14 +79,13 @@ class KeyboardAccessoryViewBinder
} }
@Override @Override
public void onItemsChanged(KeyboardAccessoryModel.SimpleListObservable<Tab> model, public void onItemsChanged(
KeyboardAccessoryView view, int index, int count) { SimpleListObservable<Tab> model, KeyboardAccessoryView view, int index, int count) {
// TODO(fhorschig): Implement fine-grained, ranged changes should the need arise. // TODO(fhorschig): Implement fine-grained, ranged changes should the need arise.
updateAllTabs(view, model); updateAllTabs(view, model);
} }
void updateAllTabs(KeyboardAccessoryView view, void updateAllTabs(KeyboardAccessoryView view, SimpleListObservable<Tab> model) {
KeyboardAccessoryModel.SimpleListObservable<Tab> model) {
view.clearTabs(); view.clearTabs();
for (int i = 0; i < model.getItemCount(); ++i) { for (int i = 0; i < model.getItemCount(); ++i) {
Tab tab = model.get(i); Tab tab = model.get(i);
......
// 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;
// TODO(fhorschig): Inline this class if the setting up/destroying is less overhead than expected.
// TODO(fhorschig): Otherwise, replace the KeyboardAccessorySheet in the ChromeActivity.
/**
* Handles requests to the manual UI for filling passwords, payments and other user data. Ideally,
* the caller has no access to Keyboard accessory or sheet and is only interacting with this
* component.
* For that, it facilitates the communication between {@link KeyboardAccessoryCoordinator} and
* {@link AccessorySheetCoordinator} to add and trigger surfaces that may assist users while filling
* fields.
*/
public class ManualFillingController {
private final KeyboardAccessoryCoordinator mKeyboardAccessoryCoordinator;
/**
* Creates a the manual filling controller.
* @param keyboardAccessoryCoordinator The keybaord
*/
public ManualFillingController(KeyboardAccessoryCoordinator keyboardAccessoryCoordinator) {
mKeyboardAccessoryCoordinator = keyboardAccessoryCoordinator;
}
/**
* Links an {@link AccessorySheetCoordinator} to the {@link KeyboardAccessoryCoordinator}.
* @param accessorySheetCoordinator The sheet component to be linked.
*/
public void addAccessorySheet(AccessorySheetCoordinator accessorySheetCoordinator) {
mKeyboardAccessoryCoordinator.addTab(accessorySheetCoordinator.getTab());
}
}
\ 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 android.view.ViewStub;
import org.chromium.base.VisibleForTesting;
import org.chromium.ui.base.WindowAndroid;
/**
* Handles requests to the manual UI for filling passwords, payments and other user data. Ideally,
* the caller has no access to Keyboard accessory or sheet and is only interacting with this
* component.
* For that, it facilitates the communication between {@link KeyboardAccessoryCoordinator} and
* {@link AccessorySheetCoordinator} to add and trigger surfaces that may assist users while filling
* fields.
*/
public class ManualFillingCoordinator {
private final KeyboardAccessoryCoordinator mKeyboardAccessory;
private final AccessorySheetCoordinator mAccessorySheet;
/**
* Creates a the manual filling controller.
* @param windowAndroid The window needed to set up the sub components.
* @param keyboardAccessoryStub The view stub for keyboard accessory bar.
* @param accessorySheetStub The view stub for the keyboard accessory bottom sheet.
*/
public ManualFillingCoordinator(WindowAndroid windowAndroid, ViewStub keyboardAccessoryStub,
ViewStub accessorySheetStub) {
mKeyboardAccessory = new KeyboardAccessoryCoordinator(windowAndroid, keyboardAccessoryStub);
mAccessorySheet = new AccessorySheetCoordinator(accessorySheetStub);
}
/**
* Cleans up the manual UI by destroying the accessory bar and its bottom sheet.
*/
public void 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.
*/
public void addTab(KeyboardAccessoryData.Tab tab) {
mKeyboardAccessory.addTab(tab);
mAccessorySheet.addTab(tab);
}
/**
* 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;
}
/**
* Allows access to the accessory sheet.
* @return The coordinator of the Accessory sheet component.
*/
@VisibleForTesting
public AccessorySheetCoordinator getAccessorySheetForTesting() {
return mAccessorySheet;
}
}
\ 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.modelutil;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A {@link ListObservable} containing an {@link ArrayList} of Tabs or Actions.
* It allows models to compose different ListObservables.
* @param <T> The object type that this class manages in a list.
*/
public class SimpleListObservable<T> extends ListObservable {
private final List<T> mItems = new ArrayList<>();
/**
* Returns the item at the given position.
* @param index The position to get the item from.
* @return Returns the found item.
*/
public T get(int index) {
return mItems.get(index);
}
@Override
public int getItemCount() {
return mItems.size();
}
/**
* Appends a given item to the last position of the held {@link ArrayList}. Notifies observers
* about the inserted item.
* @param item The item to be stored.
*/
public void add(T item) {
mItems.add(item);
notifyItemRangeInserted(mItems.size() - 1, 1);
}
/**
* Removes a given item from the held {@link ArrayList}. Notifies observers about the removal.
* @param item The item to be removed.
*/
public void remove(T item) {
int position = mItems.indexOf(item);
mItems.remove(position);
notifyItemRangeRemoved(position, 1);
}
/**
* Replaces all held items with the given array of items.
* If the held list was empty, notify observers about inserted elements.
* If the held list isn't empty but the new list is, notify observer about the removal.
* If the new and old list aren't empty, notify observer about the complete exchange.
* @param newItems The set of items that should replace all held items.
*/
public void set(T[] newItems) {
if (mItems.isEmpty()) {
if (newItems.length == 0) {
return; // Nothing to do, nothing changes.
}
mItems.addAll(Arrays.asList(newItems));
notifyItemRangeInserted(0, mItems.size());
return;
}
int oldSize = mItems.size();
mItems.clear();
if (newItems.length == 0) {
notifyItemRangeRemoved(0, oldSize);
return;
}
mItems.addAll(Arrays.asList(newItems));
notifyItemRangeChanged(0, Math.max(oldSize, mItems.size()), this);
}
}
\ No newline at end of file
...@@ -97,6 +97,7 @@ chrome_java_sources = [ ...@@ -97,6 +97,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/autofill/PasswordGenerationPopupBridge.java", "java/src/org/chromium/chrome/browser/autofill/PasswordGenerationPopupBridge.java",
"java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java", "java/src/org/chromium/chrome/browser/autofill/PersonalDataManager.java",
"java/src/org/chromium/chrome/browser/autofill/PhoneNumberUtil.java", "java/src/org/chromium/chrome/browser/autofill/PhoneNumberUtil.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessoryPagerAdapter.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetCoordinator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetMediator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetMediator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetModel.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetModel.java",
...@@ -107,7 +108,7 @@ chrome_java_sources = [ ...@@ -107,7 +108,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryModel.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryModel.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryView.java", "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/KeyboardAccessoryViewBinder.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingController.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingCoordinator.java",
"java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java", "java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java",
"java/src/org/chromium/chrome/browser/banners/AppBannerManager.java", "java/src/org/chromium/chrome/browser/banners/AppBannerManager.java",
"java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java", "java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
...@@ -710,6 +711,7 @@ chrome_java_sources = [ ...@@ -710,6 +711,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/modelutil/PropertyObservable.java", "java/src/org/chromium/chrome/browser/modelutil/PropertyObservable.java",
"java/src/org/chromium/chrome/browser/modelutil/RecyclerViewModelChangeProcessor.java", "java/src/org/chromium/chrome/browser/modelutil/RecyclerViewModelChangeProcessor.java",
"java/src/org/chromium/chrome/browser/modelutil/RecyclerViewAdapter.java", "java/src/org/chromium/chrome/browser/modelutil/RecyclerViewAdapter.java",
"java/src/org/chromium/chrome/browser/modelutil/SimpleListObservable.java",
"java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java", "java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java",
"java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceChromeTabbedActivity.java", "java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceChromeTabbedActivity.java",
"java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java", "java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java",
...@@ -2038,6 +2040,7 @@ chrome_junit_test_java_sources = [ ...@@ -2038,6 +2040,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java", "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTestTabHolder.java",
"junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTitleUpdatedTest.java", "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTitleUpdatedTest.java",
"junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java", "junit/src/org/chromium/chrome/browser/metrics/VariationsSessionTest.java",
"junit/src/org/chromium/chrome/browser/modelutil/SimpleListObservableTest.java",
"junit/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeUnitTest.java", "junit/src/org/chromium/chrome/browser/notifications/NotificationPlatformBridgeUnitTest.java",
"junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java", "junit/src/org/chromium/chrome/browser/notifications/channels/ChannelDefinitionsTest.java",
"junit/src/org/chromium/chrome/browser/ntp/NativePageFactoryTest.java", "junit/src/org/chromium/chrome/browser/ntp/NativePageFactoryTest.java",
......
...@@ -4,13 +4,23 @@ ...@@ -4,13 +4,23 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static android.support.test.espresso.Espresso.onView;
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.withText;
import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import android.graphics.drawable.Drawable;
import android.support.annotation.LayoutRes;
import android.support.test.filters.MediumTest; import android.support.test.filters.MediumTest;
import android.support.v4.view.ViewPager;
import android.view.View; import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
...@@ -34,7 +44,7 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner; ...@@ -34,7 +44,7 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class AccessorySheetViewTest { public class AccessorySheetViewTest {
private AccessorySheetModel mModel; private AccessorySheetModel mModel;
private LazyViewBinderAdapter.StubHolder<View> mStubHolder; private LazyViewBinderAdapter.StubHolder<ViewPager> mStubHolder;
@Rule @Rule
public ChromeActivityTestRule<ChromeTabbedActivity> mActivityTestRule = public ChromeActivityTestRule<ChromeTabbedActivity> mActivityTestRule =
...@@ -66,4 +76,51 @@ public class AccessorySheetViewTest { ...@@ -66,4 +76,51 @@ public class AccessorySheetViewTest {
assertNotNull(mStubHolder.getView()); assertNotNull(mStubHolder.getView());
assertTrue(mStubHolder.getView().getVisibility() != View.VISIBLE); assertTrue(mStubHolder.getView().getVisibility() != View.VISIBLE);
} }
@Test
@MediumTest
public void testAddingTabToModelRendersTabsView() {
final String kSampleAction = "Some Action";
mModel.getTabList().add(createTestTab(view -> {
assertNotNull("The tab must have been created!", view);
assertTrue("Empty tab is a layout.", view instanceof LinearLayout);
LinearLayout baseLayout = (LinearLayout) view;
TextView sampleTextView = new TextView(mActivityTestRule.getActivity());
sampleTextView.setText(kSampleAction);
baseLayout.addView(sampleTextView);
}));
mModel.setActiveTabIndex(0);
// Shouldn't cause the view to be inflated.
assertNull(mStubHolder.getView());
// Setting visibility should cause the Tab to be rendered.
ThreadUtils.runOnUiThreadBlocking(() -> mModel.setVisible(true));
assertNotNull(mStubHolder.getView());
onView(withText(kSampleAction)).check(matches(isDisplayed()));
}
private KeyboardAccessoryData.Tab createTestTab(KeyboardAccessoryData.Tab.Listener listener) {
return new KeyboardAccessoryData.Tab() {
@Override
public Drawable getIcon() {
return null; // Unused.
}
@Override
public String getContentDescription() {
return null; // Unused.
}
@Override
public @LayoutRes int getTabLayout() {
return R.layout.empty_accessory_sheet;
}
@Override
public Listener getListener() {
return listener;
}
};
}
} }
\ No newline at end of file
...@@ -90,6 +90,16 @@ public class KeyboardAccessoryViewTest { ...@@ -90,6 +90,16 @@ public class KeyboardAccessoryViewTest {
public String getContentDescription() { public String getContentDescription() {
return contentDescription; return contentDescription;
} }
@Override
public int getTabLayout() {
return R.layout.empty_accessory_sheet; // Unused.
}
@Override
public Listener getListener() {
return null; // Unused.
}
}; };
} }
......
...@@ -6,14 +6,15 @@ package org.chromium.chrome.browser.autofill.keyboard_accessory; ...@@ -6,14 +6,15 @@ package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.v4.view.ViewPager;
import android.view.ViewStub; import android.view.ViewStub;
import android.widget.LinearLayout;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
...@@ -24,6 +25,8 @@ import org.robolectric.annotation.Config; ...@@ -24,6 +25,8 @@ import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature; import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetModel.PropertyKey;
import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyObservable; import org.chromium.chrome.browser.modelutil.PropertyObservable;
/** /**
...@@ -33,12 +36,17 @@ import org.chromium.chrome.browser.modelutil.PropertyObservable; ...@@ -33,12 +36,17 @@ import org.chromium.chrome.browser.modelutil.PropertyObservable;
@Config(manifest = Config.NONE) @Config(manifest = Config.NONE)
public class AccessorySheetControllerTest { public class AccessorySheetControllerTest {
@Mock @Mock
private PropertyObservable private PropertyObservable.PropertyObserver<PropertyKey> mMockPropertyObserver;
.PropertyObserver<AccessorySheetModel.PropertyKey> mMockPropertyObserver; @Mock
private ListObservable.ListObserver mTabListObserver;
@Mock @Mock
private ViewStub mMockViewStub; private ViewStub mMockViewStub;
@Mock @Mock
private LinearLayout mMockView; private ViewPager mMockView;
@Mock
private KeyboardAccessoryData.Tab mMockTab;
@Mock
private KeyboardAccessoryData.Tab mSecondMockTab;
private AccessorySheetCoordinator mCoordinator; private AccessorySheetCoordinator mCoordinator;
private AccessorySheetMediator mMediator; private AccessorySheetMediator mMediator;
...@@ -69,19 +77,54 @@ public class AccessorySheetControllerTest { ...@@ -69,19 +77,54 @@ public class AccessorySheetControllerTest {
// Calling show on the mediator should make model propagate that it's visible. // Calling show on the mediator should make model propagate that it's visible.
mMediator.show(); mMediator.show();
verify(mMockPropertyObserver) verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.VISIBLE);
.onPropertyChanged(mModel, AccessorySheetModel.PropertyKey.VISIBLE);
assertThat(mModel.isVisible(), is(true)); assertThat(mModel.isVisible(), is(true));
// Calling show again does nothing. // Calling show again does nothing.
mMediator.show(); mMediator.show();
verify(mMockPropertyObserver) // Still the same call and no new one added. verify(mMockPropertyObserver) // Still the same call and no new one added.
.onPropertyChanged(mModel, AccessorySheetModel.PropertyKey.VISIBLE); .onPropertyChanged(mModel, PropertyKey.VISIBLE);
// Calling hide on the mediator should make model propagate that it's invisible. // Calling hide on the mediator should make model propagate that it's invisible.
mMediator.hide(); mMediator.hide();
verify(mMockPropertyObserver, times(2)) verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, PropertyKey.VISIBLE);
.onPropertyChanged(mModel, AccessorySheetModel.PropertyKey.VISIBLE);
assertThat(mModel.isVisible(), is(false)); assertThat(mModel.isVisible(), is(false));
} }
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testModelNotifiesChangesForNewSheet() {
mModel.addObserver(mMockPropertyObserver);
mModel.getTabList().addObserver(mTabListObserver);
assertThat(mModel.getTabList().getItemCount(), is(0));
mCoordinator.addTab(mMockTab);
verify(mTabListObserver).onItemRangeInserted(mModel.getTabList(), 0, 1);
assertThat(mModel.getTabList().getItemCount(), is(1));
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testFirstAddedTabBecomesActiveTab() {
mModel.addObserver(mMockPropertyObserver);
// Initially, there is no active Tab.
assertThat(mModel.getTabList().getItemCount(), is(0));
assertThat(mCoordinator.getTab(), is(nullValue()));
// The first tab becomes the active Tab.
mCoordinator.addTab(mMockTab);
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.ACTIVE_TAB_INDEX);
assertThat(mModel.getTabList().getItemCount(), is(1));
assertThat(mModel.getActiveTabIndex(), is(0));
assertThat(mCoordinator.getTab(), is(mMockTab));
// A second tab is added but doesn't become automatically active.
mCoordinator.addTab(mSecondMockTab);
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.ACTIVE_TAB_INDEX);
assertThat(mModel.getTabList().getItemCount(), is(2));
assertThat(mModel.getActiveTabIndex(), is(0));
}
} }
\ No newline at end of file
...@@ -119,7 +119,7 @@ public class KeyboardAccessoryControllerTest { ...@@ -119,7 +119,7 @@ public class KeyboardAccessoryControllerTest {
mCoordinator.addTab(mMockTab); mCoordinator.addTab(mMockTab);
verify(mMockTabListObserver).onItemRangeInserted(mModel.getTabList(), 0, 1); verify(mMockTabListObserver).onItemRangeInserted(mModel.getTabList(), 0, 1);
assertThat(mModel.getTabList().getItemCount(), is(1)); assertThat(mModel.getTabList().getItemCount(), is(1));
assertThat(mModel.getTabList().get(0), is(equalTo(mMockTab))); assertThat(mModel.getTabList().get(0), is(mMockTab));
// Calling hide on the coordinator should make model propagate that it's invisible. // Calling hide on the coordinator should make model propagate that it's invisible.
mCoordinator.removeTab(mMockTab); mCoordinator.removeTab(mMockTab);
......
...@@ -38,36 +38,43 @@ public class ManualFillingControllerTest { ...@@ -38,36 +38,43 @@ public class ManualFillingControllerTest {
private KeyboardAccessoryView mMockView; private KeyboardAccessoryView mMockView;
@Mock @Mock
private ListObservable.ListObserver mMockTabListObserver; private ListObservable.ListObserver mMockTabListObserver;
@Mock
private KeyboardAccessoryData.Tab mMockTab;
private KeyboardAccessoryCoordinator mKeyboardAccessory; private ManualFillingCoordinator mController;
private ManualFillingController mController;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
when(mMockViewStub.inflate()).thenReturn(mMockView); when(mMockViewStub.inflate()).thenReturn(mMockView);
mKeyboardAccessory = new KeyboardAccessoryCoordinator(mMockWindow, mMockViewStub); mController = new ManualFillingCoordinator(mMockWindow, mMockViewStub, mMockViewStub);
mController = new ManualFillingController(mKeyboardAccessory);
} }
@Test @Test
@SmallTest @SmallTest
public void testCreatesValidSubComponents() { public void testCreatesValidSubComponents() {
assertThat(mKeyboardAccessory, is(notNullValue()));
assertThat(mController, is(notNullValue())); assertThat(mController, is(notNullValue()));
assertThat(mController.getKeyboardAccessory(), is(notNullValue()));
assertThat(mController.getAccessorySheetForTesting(), is(notNullValue()));
} }
@Test @Test
@SmallTest @SmallTest
public void testAddingBottomSheetsAddsTabsToAccessory() { public void testAddingNewTabIsAddedToAccessoryAndSheet() {
KeyboardAccessoryModel keyboardAccessoryModel = KeyboardAccessoryModel keyboardAccessoryModel =
mKeyboardAccessory.getMediatorForTesting().getModelForTesting(); mController.getKeyboardAccessory().getMediatorForTesting().getModelForTesting();
keyboardAccessoryModel.addTabListObserver(mMockTabListObserver); keyboardAccessoryModel.addTabListObserver(mMockTabListObserver);
AccessorySheetModel accessorySheetModel = mController.getAccessorySheetForTesting()
.getMediatorForTesting()
.getModelForTesting();
accessorySheetModel.getTabList().addObserver(mMockTabListObserver);
assertThat(keyboardAccessoryModel.getTabList().getItemCount(), is(0)); assertThat(keyboardAccessoryModel.getTabList().getItemCount(), is(0));
mController.addAccessorySheet(new AccessorySheetCoordinator(mMockViewStub)); mController.addTab(mMockTab);
verify(mMockTabListObserver).onItemRangeInserted(keyboardAccessoryModel.getTabList(), 0, 1); verify(mMockTabListObserver).onItemRangeInserted(keyboardAccessoryModel.getTabList(), 0, 1);
verify(mMockTabListObserver).onItemRangeInserted(accessorySheetModel.getTabList(), 0, 1);
assertThat(keyboardAccessoryModel.getTabList().getItemCount(), is(1)); assertThat(keyboardAccessoryModel.getTabList().getItemCount(), is(1));
assertThat(accessorySheetModel.getTabList().getItemCount(), is(1));
} }
} }
\ No newline at end of file
file://chrome/android/java/src/org/chromium/chrome/browser/modelutil/OWNERS
// 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.modelutil;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.verify;
import android.support.test.filters.SmallTest;
import org.junit.After;
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.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
/**
* Basic test ensuring the {@link SimpleListObservable} notifies listeners properly.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class SimpleListObservableTest {
@Mock
private ListObservable.ListObserver mObserver;
private SimpleListObservable<Integer> mIntegerList = new SimpleListObservable<>();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mIntegerList.addObserver(mObserver);
}
@After
public void tearDown() {
mIntegerList.removeObserver(mObserver);
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testNotifiesSuccessfulInsertions() {
// Setting an empty list ist always an insertion.
assertThat(mIntegerList.getItemCount(), is(0));
mIntegerList.set(new Integer[] {333, 88888888, 22});
verify(mObserver).onItemRangeInserted(mIntegerList, 0, 3);
assertThat(mIntegerList.getItemCount(), is(3));
assertThat(mIntegerList.get(1), is(88888888));
// Adding Items is always an insertion.
mIntegerList.add(55555);
verify(mObserver).onItemRangeInserted(mIntegerList, 3, 1);
assertThat(mIntegerList.getItemCount(), is(4));
assertThat(mIntegerList.get(3), is(55555));
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testModelNotifiesSuccessfulRemoval() {
Integer eightEights = 88888888;
mIntegerList.set(new Integer[] {333, eightEights, 22});
assertThat(mIntegerList.getItemCount(), is(3));
// Removing any item by instance is always a removal.
mIntegerList.remove(eightEights);
verify(mObserver).onItemRangeRemoved(mIntegerList, 1, 1);
// Setting an empty list is a removal of all items.
mIntegerList.set(new Integer[] {});
verify(mObserver).onItemRangeRemoved(mIntegerList, 0, 2);
}
@Test
@SmallTest
@Feature({"keyboard-accessory"})
public void testModelNotifiesReplacedData() {
// The initial setting is an insertion.
mIntegerList.set(new Integer[] {333, 88888888, 22});
verify(mObserver).onItemRangeInserted(mIntegerList, 0, 3);
// Setting non-empty data is a change of all elements.
mIntegerList.set(new Integer[] {4444, 22});
verify(mObserver).onItemRangeChanged(mIntegerList, 0, 3, mIntegerList);
// Setting data of same values is a change.
mIntegerList.set(new Integer[] {4444, 22, 1, 666666});
verify(mObserver).onItemRangeChanged(mIntegerList, 0, 4, mIntegerList);
// Setting empty data is a removal.
mIntegerList.set(new Integer[] {});
verify(mObserver).onItemRangeRemoved(mIntegerList, 0, 4);
}
}
\ No newline at end of file
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