Commit 58e3dc5d authored by Kinuko Yasuda's avatar Kinuko Yasuda Committed by Commit Bot

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/1063732Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Kinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#559463}
parent ecf635df
<?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,11 +3,14 @@
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file. -->
<android.support.v4.view.ViewPager
<LinearLayout
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,7 +59,6 @@ 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;
......@@ -267,7 +266,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
private BottomSheet mBottomSheet;
private ContextualSuggestionsCoordinator mContextualSuggestionsCoordinator;
private FadingBackgroundView mFadingBackgroundView;
private ManualFillingCoordinator mManualFillingController;
private KeyboardAccessoryCoordinator mKeyboardAccessoryCoordinator;
// Time in ms that it took took us to inflate the initial layout
private long mInflateInitialLayoutDurationMs;
......@@ -392,9 +391,8 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
// SurfaceView's 'hole' clipping during animations that are notified to the window.
getWindowAndroid().setAnimationPlaceholderView(mCompositorViewHolder.getCompositorView());
mManualFillingController = new ManualFillingCoordinator(getWindowAndroid(),
findViewById(R.id.keyboard_accessory_stub),
findViewById(R.id.keyboard_accessory_sheet_stub));
mKeyboardAccessoryCoordinator = new KeyboardAccessoryCoordinator(
getWindowAndroid(), findViewById(R.id.keyboard_accessory_stub));
initializeToolbar();
initializeTabModels();
......@@ -678,7 +676,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
* @return The {@link KeyboardAccessoryCoordinator} that belongs to this activity.
*/
public KeyboardAccessoryCoordinator getKeyboardAccessory() {
return mManualFillingController.getKeyboardAccessory();
return mKeyboardAccessoryCoordinator;
}
/**
......@@ -1225,9 +1223,9 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
mTabContentManager = null;
}
if (mManualFillingController != null) {
mManualFillingController.destroy();
mManualFillingController = null;
if (mKeyboardAccessoryCoordinator != null) {
mKeyboardAccessoryCoordinator.destroy();
mKeyboardAccessoryCoordinator = 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,9 +4,7 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.view.ViewStub;
import org.chromium.base.VisibleForTesting;
......@@ -16,7 +14,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 ManualFillingCoordinator} (e.g. add a tab to trigger the sheet).
* communication with the {@link ManualFillingController} (e.g. add a tab to trigger the sheet).
* to the {@link AccessorySheetMediator}.
*/
public class AccessorySheetCoordinator {
......@@ -28,7 +26,7 @@ public class AccessorySheetCoordinator {
* @param viewStub The view stub that can be inflated into the accessory layout.
*/
public AccessorySheetCoordinator(ViewStub viewStub) {
LazyViewBinderAdapter.StubHolder<ViewPager> stubHolder =
LazyViewBinderAdapter.StubHolder<View> stubHolder =
new LazyViewBinderAdapter.StubHolder<>(viewStub);
AccessorySheetModel model = new AccessorySheetModel();
model.addObserver(new PropertyModelChangeProcessor<>(
......@@ -36,34 +34,10 @@ 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,8 +4,6 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import android.support.annotation.Nullable;
import org.chromium.base.VisibleForTesting;
/**
......@@ -14,15 +12,14 @@ import org.chromium.base.VisibleForTesting;
*/
class AccessorySheetMediator {
private final AccessorySheetModel mModel;
private KeyboardAccessoryData.Tab mTab;
AccessorySheetMediator(AccessorySheetModel model) {
mModel = model;
}
@Nullable
KeyboardAccessoryData.Tab getTab() {
if (mModel.getActiveTabIndex() == AccessorySheetModel.NO_ACTIVE_TAB) return null;
return mModel.getTabList().get(mModel.getActiveTabIndex());
return null; // TODO(fhorschig): Return the active tab.
}
@VisibleForTesting
......@@ -37,11 +34,4 @@ 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,9 +4,7 @@
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.
......@@ -14,22 +12,10 @@ import org.chromium.chrome.browser.modelutil.SimpleListObservable;
* like the view binder react.
*/
class AccessorySheetModel extends PropertyObservable<AccessorySheetModel.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;
public static class PropertyKey { public static final PropertyKey VISIBLE = new PropertyKey(); }
private boolean mVisible;
private final SimpleListObservable<Tab> mTabList = new SimpleListObservable<>();
SimpleListObservable<Tab> getTabList() {
return mTabList;
}
void setVisible(boolean visible) {
public void setVisible(boolean visible) {
if (mVisible == visible) return; // Nothing to do here: same value.
mVisible = visible;
notifyPropertyChanged(PropertyKey.VISIBLE);
......@@ -38,17 +24,4 @@ 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,7 +4,6 @@
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;
......@@ -15,8 +14,7 @@ import org.chromium.chrome.browser.modelutil.LazyViewBinderAdapter;
* {@link AccessorySheetViewBinder} which will modify the view accordingly.
*/
class AccessorySheetViewBinder
implements LazyViewBinderAdapter
.SimpleViewBinder<AccessorySheetModel, ViewPager, PropertyKey> {
implements LazyViewBinderAdapter.SimpleViewBinder<AccessorySheetModel, View, PropertyKey> {
@Override
public PropertyKey getVisibilityProperty() {
return PropertyKey.VISIBLE;
......@@ -28,23 +26,14 @@ class AccessorySheetViewBinder
}
@Override
public void onInitialInflation(AccessorySheetModel model, ViewPager inflatedView) {
if (model.getActiveTabIndex() != -1) inflatedView.setCurrentItem(model.getActiveTabIndex());
inflatedView.setAdapter(AccessorySheetCoordinator.createTabViewAdapter(model));
}
public void onInitialInflation(AccessorySheetModel model, View inflatedView) {}
@Override
public void bind(AccessorySheetModel model, ViewPager inflatedView, PropertyKey propertyKey) {
public void bind(AccessorySheetModel model, View 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,7 +16,6 @@ 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;
......@@ -50,11 +49,12 @@ 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<SimpleListObservable<Action>, ActionViewBinder.ViewHolder>
static RecyclerViewAdapter<KeyboardAccessoryModel.SimpleListObservable<Action>,
ActionViewBinder.ViewHolder>
createActionsAdapter(KeyboardAccessoryModel model) {
RecyclerViewAdapter<SimpleListObservable<Action>, ActionViewBinder.ViewHolder>
actionsAdapter =
new RecyclerViewAdapter<>(model.getActionList(), new ActionViewBinder());
RecyclerViewAdapter<KeyboardAccessoryModel.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.
*/
void addTab(KeyboardAccessoryData.Tab tab) {
public 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.
*/
void removeTab(KeyboardAccessoryData.Tab tab) {
public void removeTab(KeyboardAccessoryData.Tab tab) {
mMediator.removeTab(tab);
}
......
......@@ -5,9 +5,6 @@
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;
......@@ -49,17 +46,6 @@ 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.
......@@ -71,20 +57,6 @@ 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,6 +34,53 @@ 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,7 +15,6 @@ 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;
/**
......@@ -25,9 +24,8 @@ import org.chromium.ui.widget.ButtonCompat;
class KeyboardAccessoryViewBinder
implements LazyViewBinderAdapter.SimpleViewBinder<KeyboardAccessoryModel,
KeyboardAccessoryView, PropertyKey> {
static class ActionViewBinder
implements RecyclerViewAdapter.ViewBinder<SimpleListObservable<Action>,
ActionViewBinder.ViewHolder> {
static class ActionViewBinder implements RecyclerViewAdapter.ViewBinder<
KeyboardAccessoryModel.SimpleListObservable<Action>, ActionViewBinder.ViewHolder> {
static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(ButtonCompat actionView) {
super(actionView);
......@@ -46,8 +44,8 @@ class KeyboardAccessoryViewBinder
}
@Override
public void onBindViewHolder(
SimpleListObservable<Action> actions, ViewHolder holder, int position) {
public void onBindViewHolder(KeyboardAccessoryModel.SimpleListObservable<Action> actions,
ViewHolder holder, int position) {
final Action action = actions.get(position);
holder.getActionView().setText(action.getCaption());
holder.getActionView().setOnClickListener(
......@@ -55,12 +53,11 @@ class KeyboardAccessoryViewBinder
}
}
static class TabViewBinder
implements ListModelChangeProcessor
.ViewBinder<SimpleListObservable<Tab>, KeyboardAccessoryView> {
static class TabViewBinder implements ListModelChangeProcessor.ViewBinder<
KeyboardAccessoryModel.SimpleListObservable<Tab>, KeyboardAccessoryView> {
@Override
public void onItemsInserted(
SimpleListObservable<Tab> model, KeyboardAccessoryView view, int index, int count) {
public void onItemsInserted(KeyboardAccessoryModel.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);
......@@ -70,8 +67,8 @@ class KeyboardAccessoryViewBinder
}
@Override
public void onItemsRemoved(
SimpleListObservable<Tab> model, KeyboardAccessoryView view, int index, int count) {
public void onItemsRemoved(KeyboardAccessoryModel.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++);
......@@ -79,13 +76,14 @@ class KeyboardAccessoryViewBinder
}
@Override
public void onItemsChanged(
SimpleListObservable<Tab> model, KeyboardAccessoryView view, int index, int count) {
public void onItemsChanged(KeyboardAccessoryModel.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, SimpleListObservable<Tab> model) {
void updateAllTabs(KeyboardAccessoryView view,
KeyboardAccessoryModel.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,7 +97,6 @@ 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",
......@@ -108,7 +107,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/ManualFillingCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingController.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",
......@@ -711,7 +710,6 @@ 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",
......@@ -2040,7 +2038,6 @@ 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,23 +4,13 @@
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;
......@@ -44,7 +34,7 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class AccessorySheetViewTest {
private AccessorySheetModel mModel;
private LazyViewBinderAdapter.StubHolder<ViewPager> mStubHolder;
private LazyViewBinderAdapter.StubHolder<View> mStubHolder;
@Rule
public ChromeActivityTestRule<ChromeTabbedActivity> mActivityTestRule =
......@@ -76,51 +66,4 @@ 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,16 +90,6 @@ 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,15 +6,14 @@ 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;
......@@ -25,8 +24,6 @@ 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;
/**
......@@ -36,17 +33,12 @@ import org.chromium.chrome.browser.modelutil.PropertyObservable;
@Config(manifest = Config.NONE)
public class AccessorySheetControllerTest {
@Mock
private PropertyObservable.PropertyObserver<PropertyKey> mMockPropertyObserver;
@Mock
private ListObservable.ListObserver mTabListObserver;
private PropertyObservable
.PropertyObserver<AccessorySheetModel.PropertyKey> mMockPropertyObserver;
@Mock
private ViewStub mMockViewStub;
@Mock
private ViewPager mMockView;
@Mock
private KeyboardAccessoryData.Tab mMockTab;
@Mock
private KeyboardAccessoryData.Tab mSecondMockTab;
private LinearLayout mMockView;
private AccessorySheetCoordinator mCoordinator;
private AccessorySheetMediator mMediator;
......@@ -77,54 +69,19 @@ public class AccessorySheetControllerTest {
// Calling show on the mediator should make model propagate that it's visible.
mMediator.show();
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.VISIBLE);
verify(mMockPropertyObserver)
.onPropertyChanged(mModel, AccessorySheetModel.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, PropertyKey.VISIBLE);
.onPropertyChanged(mModel, AccessorySheetModel.PropertyKey.VISIBLE);
// Calling hide on the mediator should make model propagate that it's invisible.
mMediator.hide();
verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, PropertyKey.VISIBLE);
verify(mMockPropertyObserver, times(2))
.onPropertyChanged(mModel, AccessorySheetModel.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(mMockTab));
assertThat(mModel.getTabList().get(0), is(equalTo(mMockTab)));
// Calling hide on the coordinator should make model propagate that it's invisible.
mCoordinator.removeTab(mMockTab);
......
......@@ -38,43 +38,36 @@ public class ManualFillingControllerTest {
private KeyboardAccessoryView mMockView;
@Mock
private ListObservable.ListObserver mMockTabListObserver;
@Mock
private KeyboardAccessoryData.Tab mMockTab;
private ManualFillingCoordinator mController;
private KeyboardAccessoryCoordinator mKeyboardAccessory;
private ManualFillingController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mMockViewStub.inflate()).thenReturn(mMockView);
mController = new ManualFillingCoordinator(mMockWindow, mMockViewStub, mMockViewStub);
mKeyboardAccessory = new KeyboardAccessoryCoordinator(mMockWindow, mMockViewStub);
mController = new ManualFillingController(mKeyboardAccessory);
}
@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 testAddingNewTabIsAddedToAccessoryAndSheet() {
public void testAddingBottomSheetsAddsTabsToAccessory() {
KeyboardAccessoryModel keyboardAccessoryModel =
mController.getKeyboardAccessory().getMediatorForTesting().getModelForTesting();
mKeyboardAccessory.getMediatorForTesting().getModelForTesting();
keyboardAccessoryModel.addTabListObserver(mMockTabListObserver);
AccessorySheetModel accessorySheetModel = mController.getAccessorySheetForTesting()
.getMediatorForTesting()
.getModelForTesting();
accessorySheetModel.getTabList().addObserver(mMockTabListObserver);
assertThat(keyboardAccessoryModel.getTabList().getItemCount(), is(0));
mController.addTab(mMockTab);
mController.addAccessorySheet(new AccessorySheetCoordinator(mMockViewStub));
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