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 @@
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<LinearLayout
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keyboard_accessory"
android:background="@drawable/keyboard_accessory_background"
android:contentDescription="@string/autofill_keyboard_accessory_content_description"
android:fillViewport="true"
android:layout_gravity="start|bottom"
android:layout_height="@dimen/keyboard_accessory_sheet_height"
android:layout_width="match_parent"
android:orientation="vertical"
android:visibility="gone"/>
\ No newline at end of file
......@@ -59,6 +59,7 @@ import org.chromium.chrome.browser.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.appmenu.AppMenuObserver;
import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
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.BookmarkUtils;
import org.chromium.chrome.browser.compositor.CompositorViewHolder;
......@@ -266,7 +267,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
private BottomSheet mBottomSheet;
private ContextualSuggestionsCoordinator mContextualSuggestionsCoordinator;
private FadingBackgroundView mFadingBackgroundView;
private KeyboardAccessoryCoordinator mKeyboardAccessoryCoordinator;
private ManualFillingCoordinator mManualFillingController;
// Time in ms that it took took us to inflate the initial layout
private long mInflateInitialLayoutDurationMs;
......@@ -391,8 +392,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
// SurfaceView's 'hole' clipping during animations that are notified to the window.
getWindowAndroid().setAnimationPlaceholderView(mCompositorViewHolder.getCompositorView());
mKeyboardAccessoryCoordinator = new KeyboardAccessoryCoordinator(
getWindowAndroid(), findViewById(R.id.keyboard_accessory_stub));
mManualFillingController = new ManualFillingCoordinator(getWindowAndroid(),
findViewById(R.id.keyboard_accessory_stub),
findViewById(R.id.keyboard_accessory_sheet_stub));
initializeToolbar();
initializeTabModels();
......@@ -676,7 +678,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
* @return The {@link KeyboardAccessoryCoordinator} that belongs to this activity.
*/
public KeyboardAccessoryCoordinator getKeyboardAccessory() {
return mKeyboardAccessoryCoordinator;
return mManualFillingController.getKeyboardAccessory();
}
/**
......@@ -1223,9 +1225,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
mTabContentManager = null;
}
if (mKeyboardAccessoryCoordinator != null) {
mKeyboardAccessoryCoordinator.destroy();
mKeyboardAccessoryCoordinator = null;
if (mManualFillingController != null) {
mManualFillingController.destroy();
mManualFillingController = null;
}
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 @@
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 org.chromium.base.VisibleForTesting;
......@@ -14,7 +16,7 @@ import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
/**
* 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
* 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}.
*/
public class AccessorySheetCoordinator {
......@@ -26,7 +28,7 @@ public class AccessorySheetCoordinator {
* @param viewStub The view stub that can be inflated into the accessory layout.
*/
public AccessorySheetCoordinator(ViewStub viewStub) {
LazyViewBinderAdapter.StubHolder<View> stubHolder =
LazyViewBinderAdapter.StubHolder<ViewPager> stubHolder =
new LazyViewBinderAdapter.StubHolder<>(viewStub);
AccessorySheetModel model = new AccessorySheetModel();
model.addObserver(new PropertyModelChangeProcessor<>(
......@@ -34,10 +36,34 @@ public class AccessorySheetCoordinator {
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.
* @return Returns a {@link KeyboardAccessoryData.Tab}.
*/
@Nullable
public KeyboardAccessoryData.Tab getTab() {
return mMediator.getTab();
}
......
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.annotation.Nullable;
import org.chromium.base.VisibleForTesting;
/**
......@@ -12,14 +14,15 @@ import org.chromium.base.VisibleForTesting;
*/
class AccessorySheetMediator {
private final AccessorySheetModel mModel;
private KeyboardAccessoryData.Tab mTab;
AccessorySheetMediator(AccessorySheetModel model) {
mModel = model;
}
@Nullable
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
......@@ -34,4 +37,11 @@ class AccessorySheetMediator {
public void hide() {
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 @@
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.SimpleListObservable;
/**
* This model holds all view state of the accessory sheet.
......@@ -12,10 +14,22 @@ import org.chromium.chrome.browser.modelutil.PropertyObservable;
* like the view binder react.
*/
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 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.
mVisible = visible;
notifyPropertyChanged(PropertyKey.VISIBLE);
......@@ -24,4 +38,17 @@ class AccessorySheetModel extends PropertyObservable<AccessorySheetModel.Propert
boolean isVisible() {
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 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.v4.view.ViewPager;
import android.view.View;
import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetModel.PropertyKey;
......@@ -14,7 +15,8 @@ import org.chromium.chrome.browser.modelutil.LazyViewBinderAdapter;
* {@link AccessorySheetViewBinder} which will modify the view accordingly.
*/
class AccessorySheetViewBinder
implements LazyViewBinderAdapter.SimpleViewBinder<AccessorySheetModel, View, PropertyKey> {
implements LazyViewBinderAdapter
.SimpleViewBinder<AccessorySheetModel, ViewPager, PropertyKey> {
@Override
public PropertyKey getVisibilityProperty() {
return PropertyKey.VISIBLE;
......@@ -26,14 +28,23 @@ class AccessorySheetViewBinder
}
@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
public void bind(AccessorySheetModel model, View inflatedView, PropertyKey propertyKey) {
public void bind(AccessorySheetModel model, ViewPager inflatedView, PropertyKey propertyKey) {
if (propertyKey == PropertyKey.VISIBLE) {
inflatedView.setVisibility(model.isVisible() ? View.VISIBLE : View.GONE);
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!";
}
}
......@@ -16,6 +16,7 @@ import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor;
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;
......@@ -49,12 +50,11 @@ public class KeyboardAccessoryCoordinator {
* @param model the {@link KeyboardAccessoryModel} the adapter gets its data from.
* @return Returns a fully initialized and wired adapter to an ActionViewBinder.
*/
static RecyclerViewAdapter<KeyboardAccessoryModel.SimpleListObservable<Action>,
ActionViewBinder.ViewHolder>
static RecyclerViewAdapter<SimpleListObservable<Action>, ActionViewBinder.ViewHolder>
createActionsAdapter(KeyboardAccessoryModel model) {
RecyclerViewAdapter<KeyboardAccessoryModel.SimpleListObservable<Action>,
ActionViewBinder.ViewHolder> actionsAdapter =
new RecyclerViewAdapter<>(model.getActionList(), new ActionViewBinder());
RecyclerViewAdapter<SimpleListObservable<Action>, ActionViewBinder.ViewHolder>
actionsAdapter =
new RecyclerViewAdapter<>(model.getActionList(), new ActionViewBinder());
model.addActionListObserver(new RecyclerViewModelChangeProcessor<>(actionsAdapter));
return actionsAdapter;
}
......@@ -79,7 +79,7 @@ public class KeyboardAccessoryCoordinator {
* 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.
*/
public void addTab(KeyboardAccessoryData.Tab tab) {
void addTab(KeyboardAccessoryData.Tab tab) {
mMediator.addTab(tab);
}
......@@ -88,7 +88,7 @@ public class KeyboardAccessoryCoordinator {
* from the accessory.
* @param tab The tab to be removed.
*/
public void removeTab(KeyboardAccessoryData.Tab tab) {
void removeTab(KeyboardAccessoryData.Tab tab) {
mMediator.removeTab(tab);
}
......
......@@ -5,6 +5,9 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory;
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.List;
......@@ -46,6 +49,17 @@ public class KeyboardAccessoryData {
* accessory. Typically, a tab is responsible to change the bottom sheet below the accessory.
*/
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}.
* @return The small icon that identifies this tab uniquely.
......@@ -57,6 +71,20 @@ public class KeyboardAccessoryData {
* @return A short string describing the task of this tab.
*/
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;
import org.chromium.chrome.browser.autofill.AutofillKeyboardSuggestions;
import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
......@@ -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.Tab> mTabListObservable;
private boolean mVisible;
......
......@@ -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.ListModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
import org.chromium.chrome.browser.modelutil.SimpleListObservable;
import org.chromium.ui.widget.ButtonCompat;
/**
......@@ -24,8 +25,9 @@ import org.chromium.ui.widget.ButtonCompat;
class KeyboardAccessoryViewBinder
implements LazyViewBinderAdapter.SimpleViewBinder<KeyboardAccessoryModel,
KeyboardAccessoryView, PropertyKey> {
static class ActionViewBinder implements RecyclerViewAdapter.ViewBinder<
KeyboardAccessoryModel.SimpleListObservable<Action>, ActionViewBinder.ViewHolder> {
static class ActionViewBinder
implements RecyclerViewAdapter.ViewBinder<SimpleListObservable<Action>,
ActionViewBinder.ViewHolder> {
static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(ButtonCompat actionView) {
super(actionView);
......@@ -44,8 +46,8 @@ class KeyboardAccessoryViewBinder
}
@Override
public void onBindViewHolder(KeyboardAccessoryModel.SimpleListObservable<Action> actions,
ViewHolder holder, int position) {
public void onBindViewHolder(
SimpleListObservable<Action> actions, ViewHolder holder, int position) {
final Action action = actions.get(position);
holder.getActionView().setText(action.getCaption());
holder.getActionView().setOnClickListener(
......@@ -53,11 +55,12 @@ class KeyboardAccessoryViewBinder
}
}
static class TabViewBinder implements ListModelChangeProcessor.ViewBinder<
KeyboardAccessoryModel.SimpleListObservable<Tab>, KeyboardAccessoryView> {
static class TabViewBinder
implements ListModelChangeProcessor
.ViewBinder<SimpleListObservable<Tab>, KeyboardAccessoryView> {
@Override
public void onItemsInserted(KeyboardAccessoryModel.SimpleListObservable<Tab> model,
KeyboardAccessoryView view, int index, int count) {
public void onItemsInserted(
SimpleListObservable<Tab> model, KeyboardAccessoryView view, int index, int count) {
assert count > 0 : "Tried to insert invalid amount of tabs - must be at least one.";
while (count-- > 0) {
Tab tab = model.get(index);
......@@ -67,8 +70,8 @@ class KeyboardAccessoryViewBinder
}
@Override
public void onItemsRemoved(KeyboardAccessoryModel.SimpleListObservable<Tab> model,
KeyboardAccessoryView view, int index, int count) {
public void onItemsRemoved(
SimpleListObservable<Tab> model, KeyboardAccessoryView view, int index, int count) {
assert count > 0 : "Tried to remove invalid amount of tabs - must be at least one.";
while (count-- > 0) {
view.removeTabAt(index++);
......@@ -76,14 +79,13 @@ class KeyboardAccessoryViewBinder
}
@Override
public void onItemsChanged(KeyboardAccessoryModel.SimpleListObservable<Tab> model,
KeyboardAccessoryView view, int index, int count) {
public void onItemsChanged(
SimpleListObservable<Tab> model, KeyboardAccessoryView view, int index, int count) {
// TODO(fhorschig): Implement fine-grained, ranged changes should the need arise.
updateAllTabs(view, model);
}
void updateAllTabs(KeyboardAccessoryView view,
KeyboardAccessoryModel.SimpleListObservable<Tab> model) {
void updateAllTabs(KeyboardAccessoryView view, SimpleListObservable<Tab> model) {
view.clearTabs();
for (int i = 0; i < model.getItemCount(); ++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 = [
"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/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/AccessorySheetMediator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetModel.java",
......@@ -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/KeyboardAccessoryView.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/banners/AppBannerManager.java",
"java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
......@@ -710,6 +711,7 @@ chrome_java_sources = [
"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/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/multiwindow/MultiInstanceChromeTabbedActivity.java",
"java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java",
......@@ -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/MediaNotificationTitleUpdatedTest.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/channels/ChannelDefinitionsTest.java",
"junit/src/org/chromium/chrome/browser/ntp/NativePageFactoryTest.java",
......
......@@ -4,13 +4,23 @@
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.assertNull;
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.v4.view.ViewPager;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.junit.Before;
import org.junit.Rule;
......@@ -34,7 +44,7 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class AccessorySheetViewTest {
private AccessorySheetModel mModel;
private LazyViewBinderAdapter.StubHolder<View> mStubHolder;
private LazyViewBinderAdapter.StubHolder<ViewPager> mStubHolder;
@Rule
public ChromeActivityTestRule<ChromeTabbedActivity> mActivityTestRule =
......@@ -66,4 +76,51 @@ public class AccessorySheetViewTest {
assertNotNull(mStubHolder.getView());
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 {
public String getContentDescription() {
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;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.support.test.filters.SmallTest;
import android.support.v4.view.ViewPager;
import android.view.ViewStub;
import android.widget.LinearLayout;
import org.junit.Before;
import org.junit.Test;
......@@ -24,6 +25,8 @@ import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
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;
/**
......@@ -33,12 +36,17 @@ import org.chromium.chrome.browser.modelutil.PropertyObservable;
@Config(manifest = Config.NONE)
public class AccessorySheetControllerTest {
@Mock
private PropertyObservable
.PropertyObserver<AccessorySheetModel.PropertyKey> mMockPropertyObserver;
private PropertyObservable.PropertyObserver<PropertyKey> mMockPropertyObserver;
@Mock
private ListObservable.ListObserver mTabListObserver;
@Mock
private ViewStub mMockViewStub;
@Mock
private LinearLayout mMockView;
private ViewPager mMockView;
@Mock
private KeyboardAccessoryData.Tab mMockTab;
@Mock
private KeyboardAccessoryData.Tab mSecondMockTab;
private AccessorySheetCoordinator mCoordinator;
private AccessorySheetMediator mMediator;
......@@ -69,19 +77,54 @@ public class AccessorySheetControllerTest {
// Calling show on the mediator should make model propagate that it's visible.
mMediator.show();
verify(mMockPropertyObserver)
.onPropertyChanged(mModel, AccessorySheetModel.PropertyKey.VISIBLE);
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.VISIBLE);
assertThat(mModel.isVisible(), is(true));
// Calling show again does nothing.
mMediator.show();
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.
mMediator.hide();
verify(mMockPropertyObserver, times(2))
.onPropertyChanged(mModel, AccessorySheetModel.PropertyKey.VISIBLE);
verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, PropertyKey.VISIBLE);
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 {
mCoordinator.addTab(mMockTab);
verify(mMockTabListObserver).onItemRangeInserted(mModel.getTabList(), 0, 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.
mCoordinator.removeTab(mMockTab);
......
......@@ -38,36 +38,43 @@ public class ManualFillingControllerTest {
private KeyboardAccessoryView mMockView;
@Mock
private ListObservable.ListObserver mMockTabListObserver;
@Mock
private KeyboardAccessoryData.Tab mMockTab;
private KeyboardAccessoryCoordinator mKeyboardAccessory;
private ManualFillingController mController;
private ManualFillingCoordinator mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockViewStub.inflate()).thenReturn(mMockView);
mKeyboardAccessory = new KeyboardAccessoryCoordinator(mMockWindow, mMockViewStub);
mController = new ManualFillingController(mKeyboardAccessory);
mController = new ManualFillingCoordinator(mMockWindow, mMockViewStub, mMockViewStub);
}
@Test
@SmallTest
public void testCreatesValidSubComponents() {
assertThat(mKeyboardAccessory, is(notNullValue()));
assertThat(mController, is(notNullValue()));
assertThat(mController.getKeyboardAccessory(), is(notNullValue()));
assertThat(mController.getAccessorySheetForTesting(), is(notNullValue()));
}
@Test
@SmallTest
public void testAddingBottomSheetsAddsTabsToAccessory() {
public void testAddingNewTabIsAddedToAccessoryAndSheet() {
KeyboardAccessoryModel keyboardAccessoryModel =
mKeyboardAccessory.getMediatorForTesting().getModelForTesting();
mController.getKeyboardAccessory().getMediatorForTesting().getModelForTesting();
keyboardAccessoryModel.addTabListObserver(mMockTabListObserver);
AccessorySheetModel accessorySheetModel = mController.getAccessorySheetForTesting()
.getMediatorForTesting()
.getModelForTesting();
accessorySheetModel.getTabList().addObserver(mMockTabListObserver);
assertThat(keyboardAccessoryModel.getTabList().getItemCount(), is(0));
mController.addAccessorySheet(new AccessorySheetCoordinator(mMockViewStub));
mController.addTab(mMockTab);
verify(mMockTabListObserver).onItemRangeInserted(keyboardAccessoryModel.getTabList(), 0, 1);
verify(mMockTabListObserver).onItemRangeInserted(accessorySheetModel.getTabList(), 0, 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