Commit 77624f00 authored by Bernhard Bauer's avatar Bernhard Bauer Committed by Commit Bot

Add support for read-only properties in PropertyModel

Read-only properties can be initialized with a Builder.

Migrate KeyboardAccessoryModel and AccessorySheetModel to be based on
PropertyModel.

Change-Id: I22f461f904ccefe1ecdde12d7ead62bacb0acf29
Reviewed-on: https://chromium-review.googlesource.com/1179885
Commit-Queue: Bernhard Bauer <bauerb@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avatarFriedrich Horschig [CEST] <fhorschig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589881}
parent d38023cc
...@@ -4,6 +4,12 @@ ...@@ -4,6 +4,12 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.ACTIVE_TAB_INDEX;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.HEIGHT;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.NO_ACTIVE_TAB;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.TABS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.VISIBLE;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.Px; import android.support.annotation.Px;
import android.support.v4.view.PagerAdapter; import android.support.v4.view.PagerAdapter;
...@@ -13,6 +19,7 @@ import org.chromium.base.VisibleForTesting; ...@@ -13,6 +19,7 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.modelutil.LazyConstructionPropertyMcp; import org.chromium.chrome.browser.modelutil.LazyConstructionPropertyMcp;
import org.chromium.chrome.browser.modelutil.ListModel; import org.chromium.chrome.browser.modelutil.ListModel;
import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor; import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.ui.ViewProvider; import org.chromium.ui.ViewProvider;
/** /**
...@@ -30,12 +37,16 @@ public class AccessorySheetCoordinator { ...@@ -30,12 +37,16 @@ public class AccessorySheetCoordinator {
* @param viewProvider A provider for the accessory layout. * @param viewProvider A provider for the accessory layout.
*/ */
public AccessorySheetCoordinator(ViewProvider<ViewPager> viewProvider) { public AccessorySheetCoordinator(ViewProvider<ViewPager> viewProvider) {
AccessorySheetModel model = new AccessorySheetModel(); PropertyModel model = new PropertyModel.Builder(TABS, ACTIVE_TAB_INDEX, VISIBLE, HEIGHT)
.with(TABS, new ListModel<>())
.with(ACTIVE_TAB_INDEX, NO_ACTIVE_TAB)
.with(VISIBLE, false)
.build();
new LazyConstructionPropertyMcp<>(model, AccessorySheetModel.PropertyKey.VISIBLE, LazyConstructionPropertyMcp.create(
AccessorySheetModel::isVisible, viewProvider, AccessorySheetViewBinder::bind); model, VISIBLE, viewProvider, AccessorySheetViewBinder::bind);
KeyboardAccessoryMetricsRecorder.registerMetricsObserver(model); KeyboardAccessoryMetricsRecorder.registerAccessorySheetModelMetricsObserver(model);
mMediator = new AccessorySheetMediator(model); mMediator = new AccessorySheetMediator(model);
} }
......
...@@ -4,94 +4,100 @@ ...@@ -4,94 +4,100 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetModel.NO_ACTIVE_TAB; import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.ACTIVE_TAB_INDEX;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.NO_ACTIVE_TAB;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.TABS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.VISIBLE;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.Px; import android.support.annotation.Px;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.chrome.browser.modelutil.PropertyObservable; import org.chromium.chrome.browser.modelutil.PropertyObservable;
/** /**
* Contains the controller logic of the AccessorySheet component. * Contains the controller logic of the AccessorySheet component.
* It communicates with data providers and native backends to update a {@link AccessorySheetModel}. * It communicates with data providers and native backends to update a model based on {@link
* AccessorySheetProperties}.
*/ */
class AccessorySheetMediator class AccessorySheetMediator implements PropertyObservable.PropertyObserver<PropertyKey> {
implements PropertyObservable.PropertyObserver<AccessorySheetModel.PropertyKey> { private final PropertyModel mModel;
private final AccessorySheetModel mModel;
AccessorySheetMediator(AccessorySheetModel model) { AccessorySheetMediator(PropertyModel model) {
mModel = model; mModel = model;
mModel.addObserver(this); mModel.addObserver(this);
} }
@Nullable @Nullable
KeyboardAccessoryData.Tab getTab() { KeyboardAccessoryData.Tab getTab() {
if (mModel.getActiveTabIndex() == NO_ACTIVE_TAB) return null; if (mModel.get(ACTIVE_TAB_INDEX) == NO_ACTIVE_TAB) return null;
return mModel.getTabList().get(mModel.getActiveTabIndex()); return mModel.get(TABS).get(mModel.get(ACTIVE_TAB_INDEX));
} }
@VisibleForTesting @VisibleForTesting
AccessorySheetModel getModelForTesting() { PropertyModel getModelForTesting() {
return mModel; return mModel;
} }
void show() { void show() {
mModel.setVisible(true); mModel.set(VISIBLE, true);
} }
void setHeight(int height) { void setHeight(int height) {
mModel.setHeight(height); mModel.set(AccessorySheetProperties.HEIGHT, height);
} }
public @Px int getHeight() { public @Px int getHeight() {
return mModel.getHeight(); return mModel.get(AccessorySheetProperties.HEIGHT);
} }
void hide() { void hide() {
mModel.setVisible(false); mModel.set(VISIBLE, false);
} }
boolean isShown() { boolean isShown() {
return mModel.isVisible(); return mModel.get(VISIBLE);
} }
void addTab(KeyboardAccessoryData.Tab tab) { void addTab(KeyboardAccessoryData.Tab tab) {
mModel.getTabList().add(tab); mModel.get(TABS).add(tab);
if (mModel.getActiveTabIndex() == NO_ACTIVE_TAB) { if (mModel.get(ACTIVE_TAB_INDEX) == NO_ACTIVE_TAB) {
mModel.setActiveTabIndex(mModel.getTabList().size() - 1); mModel.set(ACTIVE_TAB_INDEX, mModel.get(TABS).size() - 1);
} }
} }
void removeTab(KeyboardAccessoryData.Tab tab) { void removeTab(KeyboardAccessoryData.Tab tab) {
assert mModel.getActiveTabIndex() != NO_ACTIVE_TAB; assert mModel.get(ACTIVE_TAB_INDEX) != NO_ACTIVE_TAB;
mModel.setActiveTabIndex(getNextActiveTab(tab)); mModel.set(ACTIVE_TAB_INDEX, getNextActiveTab(tab));
mModel.getTabList().remove(tab); mModel.get(TABS).remove(tab);
if (mModel.getActiveTabIndex() == NO_ACTIVE_TAB) hide(); if (mModel.get(ACTIVE_TAB_INDEX) == NO_ACTIVE_TAB) hide();
} }
void setTabs(KeyboardAccessoryData.Tab[] tabs) { void setTabs(KeyboardAccessoryData.Tab[] tabs) {
mModel.getTabList().set(tabs); mModel.get(TABS).set(tabs);
mModel.setActiveTabIndex(mModel.getTabList().size() - 1); mModel.set(ACTIVE_TAB_INDEX, mModel.get(TABS).size() - 1);
} }
void setActiveTab(int position) { void setActiveTab(int position) {
assert position < mModel.getTabList().size() assert position < mModel.get(TABS).size()
|| position >= 0 : position + " is not a valid tab index!"; || position >= 0 : position + " is not a valid tab index!";
mModel.setActiveTabIndex(position); mModel.set(ACTIVE_TAB_INDEX, position);
} }
/** /**
* Returns the position of a tab which needs to become the active tab. If the tab to be deleted * Returns the position of a tab which needs to become the active tab. If the tab to be deleted
* is the active tab, return the item on its left. If it was the first item in the list, return * is the active tab, return the item on its left. If it was the first item in the list, return
* the new first item. If no items remain, return {@link AccessorySheetModel#NO_ACTIVE_TAB}. * the new first item. If no items remain, return {@link
* AccessorySheetProperties#NO_ACTIVE_TAB}.
* @param tabToBeDeleted The tab to be removed from the list. * @param tabToBeDeleted The tab to be removed from the list.
* @return The position of the tab which should become active. * @return The position of the tab which should become active.
*/ */
private int getNextActiveTab(KeyboardAccessoryData.Tab tabToBeDeleted) { private int getNextActiveTab(KeyboardAccessoryData.Tab tabToBeDeleted) {
int activeTab = mModel.getActiveTabIndex(); int activeTab = mModel.get(ACTIVE_TAB_INDEX);
for (int i = 0; i <= activeTab; i++) { for (int i = 0; i <= activeTab; i++) {
KeyboardAccessoryData.Tab tabLeftToActiveTab = mModel.getTabList().get(i); KeyboardAccessoryData.Tab tabLeftToActiveTab = mModel.get(TABS).get(i);
// If we delete the active tab or a tab left to it, the new active tab moves left. // If we delete the active tab or a tab left to it, the new active tab moves left.
if (tabLeftToActiveTab == tabToBeDeleted) { if (tabLeftToActiveTab == tabToBeDeleted) {
--activeTab; --activeTab;
...@@ -100,21 +106,19 @@ class AccessorySheetMediator ...@@ -100,21 +106,19 @@ class AccessorySheetMediator
} }
if (activeTab >= 0) return activeTab; // The new active tab is valid. if (activeTab >= 0) return activeTab; // The new active tab is valid.
// If there are items left, take the first one. // If there are items left, take the first one.
int itemCountAfterDeletion = mModel.getTabList().size() - 1; int itemCountAfterDeletion = mModel.get(TABS).size() - 1;
return itemCountAfterDeletion > 0 ? 0 : NO_ACTIVE_TAB; return itemCountAfterDeletion > 0 ? 0 : NO_ACTIVE_TAB;
} }
@Override @Override
public void onPropertyChanged(PropertyObservable<AccessorySheetModel.PropertyKey> source, public void onPropertyChanged(PropertyObservable<PropertyKey> source, PropertyKey propertyKey) {
@Nullable AccessorySheetModel.PropertyKey propertyKey) { if (propertyKey == VISIBLE) {
if (propertyKey == AccessorySheetModel.PropertyKey.VISIBLE) { if (mModel.get(VISIBLE) && getTab() != null && getTab().getListener() != null) {
if (mModel.isVisible() && getTab() != null && getTab().getListener() != null) {
getTab().getListener().onTabShown(); getTab().getListener().onTabShown();
} }
return; return;
} }
if (propertyKey == AccessorySheetModel.PropertyKey.ACTIVE_TAB_INDEX if (propertyKey == ACTIVE_TAB_INDEX || propertyKey == AccessorySheetProperties.HEIGHT) {
|| propertyKey == AccessorySheetModel.PropertyKey.HEIGHT) {
return; return;
} }
assert false : "Every property update needs to be handled explicitly!"; assert false : "Every property update needs to be handled explicitly!";
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab;
import org.chromium.chrome.browser.modelutil.ListModel;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* This model holds all view state of the accessory sheet.
* It is updated by the {@link AccessorySheetMediator} and emits notification on which observers
* like the view binder react.
*/
class AccessorySheetModel extends PropertyObservable<AccessorySheetModel.PropertyKey> {
public static class PropertyKey {
public static final List<PropertyKey> ALL_PROPERTIES = new ArrayList<>();
private PropertyKey() {
ALL_PROPERTIES.add(this);
}
public static final PropertyKey TAB_LIST = new PropertyKey();
public static final PropertyKey ACTIVE_TAB_INDEX = new PropertyKey();
public static final PropertyKey VISIBLE = new PropertyKey();
public static final PropertyKey HEIGHT = new PropertyKey();
}
public static final int NO_ACTIVE_TAB = -1;
private int mActiveTabIndex = NO_ACTIVE_TAB;
private boolean mVisible;
private int mHeight;
private final ListModel<Tab> mTabList = new ListModel<>();
@Override
public Collection<PropertyKey> getAllSetProperties() {
return PropertyKey.ALL_PROPERTIES;
}
ListModel<Tab> getTabList() {
return mTabList;
}
void setVisible(boolean visible) {
if (mVisible == visible) return; // Nothing to do here: same value.
mVisible = visible;
notifyPropertyChanged(PropertyKey.VISIBLE);
}
boolean isVisible() {
return mVisible;
}
void setHeight(int height) {
if (height == mHeight) return; // Same value, nothing to do here.
mHeight = height;
notifyPropertyChanged(PropertyKey.HEIGHT);
}
int getHeight() {
return mHeight;
}
int getActiveTabIndex() {
return mActiveTabIndex;
}
void setActiveTabIndex(int activeTabPosition) {
if (mActiveTabIndex == activeTabPosition) return;
assert((activeTabPosition >= 0 && activeTabPosition < mTabList.size())
|| 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
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.autofill.keyboard_accessory;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab;
import org.chromium.chrome.browser.modelutil.ListModel;
import org.chromium.chrome.browser.modelutil.PropertyModel.ReadableObjectPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.WritableIntPropertyKey;
/**
* This model holds all view state of the accessory sheet.
* It is updated by the {@link AccessorySheetMediator} and emits notification on which observers
* like the view binder react.
*/
class AccessorySheetProperties {
public static final ReadableObjectPropertyKey<ListModel<Tab>> TABS =
new ReadableObjectPropertyKey<>();
public static final WritableIntPropertyKey ACTIVE_TAB_INDEX = new WritableIntPropertyKey();
public static final WritableBooleanPropertyKey VISIBLE = new WritableBooleanPropertyKey();
public static final WritableIntPropertyKey HEIGHT = new WritableIntPropertyKey();
public static final int NO_ACTIVE_TAB = -1;
private AccessorySheetProperties() {}
}
\ No newline at end of file
...@@ -4,36 +4,41 @@ ...@@ -4,36 +4,41 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.ACTIVE_TAB_INDEX;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.HEIGHT;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.NO_ACTIVE_TAB;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.TABS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.VISIBLE;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetModel.PropertyKey; import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel;
/** /**
* Observes {@link AccessorySheetModel} changes (like a newly available tab) and triggers the * Observes {@link AccessorySheetProperties} changes (like a newly available tab) and triggers the
* {@link AccessorySheetViewBinder} which will modify the view accordingly. * {@link AccessorySheetViewBinder} which will modify the view accordingly.
*/ */
class AccessorySheetViewBinder { class AccessorySheetViewBinder {
public static void bind( public static void bind(PropertyModel model, ViewPager viewPager, PropertyKey propertyKey) {
AccessorySheetModel model, ViewPager viewPager, PropertyKey propertyKey) { if (propertyKey == TABS) {
if (propertyKey == PropertyKey.TAB_LIST) {
viewPager.setAdapter( viewPager.setAdapter(
AccessorySheetCoordinator.createTabViewAdapter(model.getTabList(), viewPager)); AccessorySheetCoordinator.createTabViewAdapter(model.get(TABS), viewPager));
} else if (propertyKey == PropertyKey.VISIBLE) { } else if (propertyKey == VISIBLE) {
viewPager.bringToFront(); // Ensure toolbars and other containers are overlaid. viewPager.bringToFront(); // Ensure toolbars and other containers are overlaid.
viewPager.setVisibility(model.isVisible() ? View.VISIBLE : View.GONE); viewPager.setVisibility(model.get(VISIBLE) ? View.VISIBLE : View.GONE);
if (model.isVisible() if (model.get(VISIBLE) && model.get(ACTIVE_TAB_INDEX) != NO_ACTIVE_TAB) {
&& model.getActiveTabIndex() != AccessorySheetModel.NO_ACTIVE_TAB) { announceOpenedTab(viewPager, model.get(TABS).get(model.get(ACTIVE_TAB_INDEX)));
announceOpenedTab(viewPager, model.getTabList().get(model.getActiveTabIndex()));
} }
} else if (propertyKey == PropertyKey.HEIGHT) { } else if (propertyKey == HEIGHT) {
ViewGroup.LayoutParams p = viewPager.getLayoutParams(); ViewGroup.LayoutParams p = viewPager.getLayoutParams();
p.height = model.getHeight(); p.height = model.get(HEIGHT);
viewPager.setLayoutParams(p); viewPager.setLayoutParams(p);
} else if (propertyKey == PropertyKey.ACTIVE_TAB_INDEX) { } else if (propertyKey == ACTIVE_TAB_INDEX) {
if (model.getActiveTabIndex() != AccessorySheetModel.NO_ACTIVE_TAB) { if (model.get(ACTIVE_TAB_INDEX) != NO_ACTIVE_TAB) {
viewPager.setCurrentItem(model.getActiveTabIndex()); viewPager.setCurrentItem(model.get(ACTIVE_TAB_INDEX));
} }
} else { } else {
assert false : "Every possible property update needs to be handled!"; assert false : "Every possible property update needs to be handled!";
......
...@@ -4,6 +4,13 @@ ...@@ -4,6 +4,13 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.ACTIONS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.ACTIVE_TAB;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.BOTTOM_OFFSET_PX;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.TABS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.TAB_SELECTION_CALLBACKS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.VISIBLE;
import android.support.annotation.Px; import android.support.annotation.Px;
import org.chromium.base.VisibleForTesting; import org.chromium.base.VisibleForTesting;
...@@ -12,6 +19,7 @@ import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessory ...@@ -12,6 +19,7 @@ import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessory
import org.chromium.chrome.browser.modelutil.LazyConstructionPropertyMcp; import org.chromium.chrome.browser.modelutil.LazyConstructionPropertyMcp;
import org.chromium.chrome.browser.modelutil.ListModel; import org.chromium.chrome.browser.modelutil.ListModel;
import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor; import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter; import org.chromium.chrome.browser.modelutil.RecyclerViewAdapter;
import org.chromium.chrome.browser.modelutil.SimpleRecyclerViewMcp; import org.chromium.chrome.browser.modelutil.SimpleRecyclerViewMcp;
import org.chromium.ui.ViewProvider; import org.chromium.ui.ViewProvider;
...@@ -60,18 +68,25 @@ public class KeyboardAccessoryCoordinator { ...@@ -60,18 +68,25 @@ public class KeyboardAccessoryCoordinator {
*/ */
public KeyboardAccessoryCoordinator(VisibilityDelegate visibilityDelegate, public KeyboardAccessoryCoordinator(VisibilityDelegate visibilityDelegate,
ViewProvider<KeyboardAccessoryView> viewProvider) { ViewProvider<KeyboardAccessoryView> viewProvider) {
KeyboardAccessoryModel model = new KeyboardAccessoryModel(); PropertyModel model = new PropertyModel
.Builder(ACTIONS, TABS, VISIBLE, BOTTOM_OFFSET_PX, ACTIVE_TAB,
TAB_SELECTION_CALLBACKS)
.with(TABS, new ListModel<>())
.with(ACTIONS, new ListModel<>())
.with(ACTIVE_TAB, null)
.with(VISIBLE, false)
.build();
mMediator = new KeyboardAccessoryMediator(model, visibilityDelegate); mMediator = new KeyboardAccessoryMediator(model, visibilityDelegate);
viewProvider.whenLoaded(view -> view.setTabSelectionAdapter(mMediator)); viewProvider.whenLoaded(view -> view.setTabSelectionAdapter(mMediator));
new LazyConstructionPropertyMcp<>(model, KeyboardAccessoryModel.PropertyKey.VISIBLE, LazyConstructionPropertyMcp.create(
KeyboardAccessoryModel::isVisible, viewProvider, KeyboardAccessoryViewBinder::bind); model, VISIBLE, viewProvider, KeyboardAccessoryViewBinder::bind);
KeyboardAccessoryMetricsRecorder.registerMetricsObserver(model); KeyboardAccessoryMetricsRecorder.registerKeyboardAccessoryModelMetricsObserver(model);
} }
/** /**
* Creates an adapter to an {@link ActionViewHolder} that is wired * Creates an adapter to an {@link ActionViewHolder} that is wired
* up to the model change processor which listens to the given {@link KeyboardAccessoryModel}. * up to the model change processor which listens to the given action list.
* @param actions The list of actions shown represented by the adapter. * @param actions The list of actions shown represented by the adapter.
* @return Returns a fully initialized and wired adapter to an ActionViewHolder. * @return Returns a fully initialized and wired adapter to an ActionViewHolder.
*/ */
...@@ -85,16 +100,16 @@ public class KeyboardAccessoryCoordinator { ...@@ -85,16 +100,16 @@ public class KeyboardAccessoryCoordinator {
/** /**
* Creates the {@link TabViewBinder} that is linked to the {@link ListModelChangeProcessor} that * Creates the {@link TabViewBinder} that is linked to the {@link ListModelChangeProcessor} that
* connects the given {@link KeyboardAccessoryView} to the given {@link KeyboardAccessoryModel}. * connects the given {@link KeyboardAccessoryView} to the given action list.
* @param model the {@link KeyboardAccessoryModel} whose data is used by the TabViewBinder. * @param model the {@link KeyboardAccessoryProperties} whose data is used by the TabViewBinder.
* @param inflatedView the {@link KeyboardAccessoryView} to which the TabViewBinder binds data. * @param inflatedView the {@link KeyboardAccessoryView} to which the TabViewBinder binds data.
* @return Returns a fully initialized and wired {@link TabViewBinder}. * @return Returns a fully initialized and wired {@link TabViewBinder}.
*/ */
static TabViewBinder createTabViewBinder( static TabViewBinder createTabViewBinder(
KeyboardAccessoryModel model, KeyboardAccessoryView inflatedView) { PropertyModel model, KeyboardAccessoryView inflatedView) {
TabViewBinder tabViewBinder = new TabViewBinder(); TabViewBinder tabViewBinder = new TabViewBinder();
model.addTabListObserver( model.get(TABS).addObserver(
new ListModelChangeProcessor<>(model.getTabList(), inflatedView, tabViewBinder)); new ListModelChangeProcessor<>(model.get(TABS), inflatedView, tabViewBinder));
return tabViewBinder; return tabViewBinder;
} }
......
...@@ -5,6 +5,12 @@ ...@@ -5,6 +5,12 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetTrigger.MANUAL_CLOSE; import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetTrigger.MANUAL_CLOSE;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.ACTIONS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.ACTIVE_TAB;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.BOTTOM_OFFSET_PX;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.TABS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.TAB_SELECTION_CALLBACKS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.VISIBLE;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.Px; import android.support.annotation.Px;
...@@ -14,6 +20,8 @@ import org.chromium.base.VisibleForTesting; ...@@ -14,6 +20,8 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryCoordinator.VisibilityDelegate; import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryCoordinator.VisibilityDelegate;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Action; import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Action;
import org.chromium.chrome.browser.modelutil.ListObservable; import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.chrome.browser.modelutil.PropertyObservable; import org.chromium.chrome.browser.modelutil.PropertyObservable;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -22,31 +30,30 @@ import java.util.List; ...@@ -22,31 +30,30 @@ import java.util.List;
/** /**
* This is the second part of the controller of the keyboard accessory component. * This is the second part of the controller of the keyboard accessory component.
* It is responsible to update the {@link KeyboardAccessoryModel} based on Backend calls and notify * It is responsible for updating the model based on backend calls and notify the backend if the
* the Backend if the {@link KeyboardAccessoryModel} changes. * model changes. From the backend, it receives all actions that the accessory can perform (most
* From the backend, it receives all actions that the accessory can perform (most prominently * prominently generating passwords) and lets the model know of these actions and which callback to
* generating passwords) and lets the {@link KeyboardAccessoryModel} know of these actions and which * trigger when selecting them.
* callback to trigger when selecting them.
*/ */
class KeyboardAccessoryMediator class KeyboardAccessoryMediator
implements ListObservable.ListObserver<Void>, implements ListObservable.ListObserver<Void>,
PropertyObservable.PropertyObserver<KeyboardAccessoryModel.PropertyKey>, PropertyObservable.PropertyObserver<PropertyKey>,
KeyboardAccessoryData.Observer<KeyboardAccessoryData.Action>, KeyboardAccessoryData.Observer<KeyboardAccessoryData.Action>,
TabLayout.OnTabSelectedListener { TabLayout.OnTabSelectedListener {
private final KeyboardAccessoryModel mModel; private final PropertyModel mModel;
private final VisibilityDelegate mVisibilityDelegate; private final VisibilityDelegate mVisibilityDelegate;
private boolean mShowIfNotEmpty; private boolean mShowIfNotEmpty;
KeyboardAccessoryMediator(KeyboardAccessoryModel model, VisibilityDelegate visibilityDelegate) { KeyboardAccessoryMediator(PropertyModel model, VisibilityDelegate visibilityDelegate) {
mModel = model; mModel = model;
mVisibilityDelegate = visibilityDelegate; mVisibilityDelegate = visibilityDelegate;
// Add mediator as observer so it can use model changes as signal for accessory visibility. // Add mediator as observer so it can use model changes as signal for accessory visibility.
mModel.addObserver(this); mModel.addObserver(this);
mModel.getTabList().addObserver(this); mModel.get(TABS).addObserver(this);
mModel.getActionList().addObserver(this); mModel.get(ACTIONS).addObserver(this);
mModel.setTabSelectionCallbacks(this); mModel.set(TAB_SELECTION_CALLBACKS, this);
} }
@Override @Override
...@@ -55,14 +62,14 @@ class KeyboardAccessoryMediator ...@@ -55,14 +62,14 @@ class KeyboardAccessoryMediator
// If there is a new list, retain all actions that are of a different type than the provided // If there is a new list, retain all actions that are of a different type than the provided
// actions. // actions.
List<Action> retainedActions = new ArrayList<>(); List<Action> retainedActions = new ArrayList<>();
for (Action a : mModel.getActionList()) { for (Action a : mModel.get(ACTIONS)) {
if (a.getActionType() == typeId) continue; if (a.getActionType() == typeId) continue;
retainedActions.add(a); retainedActions.add(a);
} }
// Always append autofill suggestions to the very end. // Always append autofill suggestions to the very end.
int insertPos = typeId == AccessoryAction.AUTOFILL_SUGGESTION ? retainedActions.size() : 0; int insertPos = typeId == AccessoryAction.AUTOFILL_SUGGESTION ? retainedActions.size() : 0;
retainedActions.addAll(insertPos, Arrays.asList(actions)); retainedActions.addAll(insertPos, Arrays.asList(actions));
mModel.setActions(retainedActions.toArray(new Action[retainedActions.size()])); mModel.get(ACTIONS).set(retainedActions);
} }
void requestShowing() { void requestShowing() {
...@@ -76,15 +83,15 @@ class KeyboardAccessoryMediator ...@@ -76,15 +83,15 @@ class KeyboardAccessoryMediator
} }
void addTab(KeyboardAccessoryData.Tab tab) { void addTab(KeyboardAccessoryData.Tab tab) {
mModel.addTab(tab); mModel.get(TABS).add(tab);
} }
void removeTab(KeyboardAccessoryData.Tab tab) { void removeTab(KeyboardAccessoryData.Tab tab) {
mModel.removeTab(tab); mModel.get(TABS).remove(tab);
} }
void setTabs(KeyboardAccessoryData.Tab[] tabs) { void setTabs(KeyboardAccessoryData.Tab[] tabs) {
mModel.getTabList().set(tabs); mModel.get(TABS).set(tabs);
} }
void dismiss() { void dismiss() {
...@@ -93,50 +100,50 @@ class KeyboardAccessoryMediator ...@@ -93,50 +100,50 @@ class KeyboardAccessoryMediator
} }
void closeActiveTab() { void closeActiveTab() {
mModel.setActiveTab(null); mModel.set(ACTIVE_TAB, null);
} }
@VisibleForTesting @VisibleForTesting
KeyboardAccessoryModel getModelForTesting() { PropertyModel getModelForTesting() {
return mModel; return mModel;
} }
@Override @Override
public void onItemRangeInserted(ListObservable source, int index, int count) { public void onItemRangeInserted(ListObservable source, int index, int count) {
assert source == mModel.getActionList() || source == mModel.getTabList(); assert source == mModel.get(ACTIONS) || source == mModel.get(TABS);
updateVisibility(); updateVisibility();
} }
@Override @Override
public void onItemRangeRemoved(ListObservable source, int index, int count) { public void onItemRangeRemoved(ListObservable source, int index, int count) {
assert source == mModel.getActionList() || source == mModel.getTabList(); assert source == mModel.get(ACTIONS) || source == mModel.get(TABS);
updateVisibility(); updateVisibility();
} }
@Override @Override
public void onItemRangeChanged( public void onItemRangeChanged(
ListObservable source, int index, int count, @Nullable Void payload) { ListObservable source, int index, int count, @Nullable Void payload) {
assert source == mModel.getActionList() || source == mModel.getTabList(); assert source == mModel.get(ACTIONS) || source == mModel.get(TABS);
assert payload == null; assert payload == null;
updateVisibility(); updateVisibility();
} }
@Override @Override
public void onPropertyChanged(PropertyObservable<KeyboardAccessoryModel.PropertyKey> source, public void onPropertyChanged(
@Nullable KeyboardAccessoryModel.PropertyKey propertyKey) { PropertyObservable<PropertyKey> source, @Nullable PropertyKey propertyKey) {
// Update the visibility only if we haven't set it just now. // Update the visibility only if we haven't set it just now.
if (propertyKey == KeyboardAccessoryModel.PropertyKey.VISIBLE) { if (propertyKey == VISIBLE) {
// When the accessory just (dis)appeared, there should be no active tab. // When the accessory just (dis)appeared, there should be no active tab.
closeActiveTab(); closeActiveTab();
mVisibilityDelegate.onBottomControlSpaceChanged(); mVisibilityDelegate.onBottomControlSpaceChanged();
if (!mModel.isVisible()) { if (!mModel.get(VISIBLE)) {
// TODO(fhorschig|ioanap): Maybe the generation bridge should take care of that. // TODO(fhorschig|ioanap): Maybe the generation bridge should take care of that.
onItemsAvailable(AccessoryAction.GENERATE_PASSWORD_AUTOMATIC, new Action[0]); onItemsAvailable(AccessoryAction.GENERATE_PASSWORD_AUTOMATIC, new Action[0]);
} }
return; return;
} }
if (propertyKey == KeyboardAccessoryModel.PropertyKey.ACTIVE_TAB) { if (propertyKey == ACTIVE_TAB) {
Integer activeTab = mModel.activeTab(); Integer activeTab = mModel.get(ACTIVE_TAB);
if (activeTab == null) { if (activeTab == null) {
mVisibilityDelegate.onCloseAccessorySheet(); mVisibilityDelegate.onCloseAccessorySheet();
updateVisibility(); updateVisibility();
...@@ -145,8 +152,7 @@ class KeyboardAccessoryMediator ...@@ -145,8 +152,7 @@ class KeyboardAccessoryMediator
mVisibilityDelegate.onChangeAccessorySheet(activeTab); mVisibilityDelegate.onChangeAccessorySheet(activeTab);
return; return;
} }
if (propertyKey == KeyboardAccessoryModel.PropertyKey.BOTTOM_OFFSET if (propertyKey == BOTTOM_OFFSET_PX || propertyKey == TAB_SELECTION_CALLBACKS) {
|| propertyKey == KeyboardAccessoryModel.PropertyKey.TAB_SELECTION_CALLBACKS) {
return; return;
} }
assert false : "Every property update needs to be handled explicitly!"; assert false : "Every property update needs to be handled explicitly!";
...@@ -154,7 +160,7 @@ class KeyboardAccessoryMediator ...@@ -154,7 +160,7 @@ class KeyboardAccessoryMediator
@Override @Override
public void onTabSelected(TabLayout.Tab tab) { public void onTabSelected(TabLayout.Tab tab) {
mModel.setActiveTab(tab.getPosition()); mModel.set(ACTIVE_TAB, tab.getPosition());
} }
@Override @Override
...@@ -162,37 +168,37 @@ class KeyboardAccessoryMediator ...@@ -162,37 +168,37 @@ class KeyboardAccessoryMediator
@Override @Override
public void onTabReselected(TabLayout.Tab tab) { public void onTabReselected(TabLayout.Tab tab) {
if (mModel.activeTab() == null) { if (mModel.get(ACTIVE_TAB) == null) {
mModel.setActiveTab(tab.getPosition()); mModel.set(ACTIVE_TAB, tab.getPosition());
} else { } else {
KeyboardAccessoryMetricsRecorder.recordSheetTrigger( KeyboardAccessoryMetricsRecorder.recordSheetTrigger(
mModel.getTabList().get(mModel.activeTab()).getRecordingType(), MANUAL_CLOSE); mModel.get(TABS).get(mModel.get(ACTIVE_TAB)).getRecordingType(), MANUAL_CLOSE);
mVisibilityDelegate.onOpenKeyboard(); // This will close the active tab gently. mVisibilityDelegate.onOpenKeyboard(); // This will close the active tab gently.
} }
} }
boolean hasContents() { boolean hasContents() {
return mModel.getActionList().size() > 0 || mModel.getTabList().size() > 0; return mModel.get(ACTIONS).size() > 0 || mModel.get(TABS).size() > 0;
} }
private boolean shouldShowAccessory() { private boolean shouldShowAccessory() {
if (!mShowIfNotEmpty && mModel.activeTab() == null) return false; if (!mShowIfNotEmpty && mModel.get(ACTIVE_TAB) == null) return false;
return hasContents(); return hasContents();
} }
private void updateVisibility() { private void updateVisibility() {
mModel.setVisible(shouldShowAccessory()); mModel.set(VISIBLE, shouldShowAccessory());
} }
public void setBottomOffset(@Px int bottomOffset) { public void setBottomOffset(@Px int bottomOffset) {
mModel.setBottomOffset(bottomOffset); mModel.set(BOTTOM_OFFSET_PX, bottomOffset);
} }
public boolean isShown() { public boolean isShown() {
return mModel.isVisible(); return mModel.get(VISIBLE);
} }
public boolean hasActiveTab() { public boolean hasActiveTab() {
return mModel.isVisible() && mModel.activeTab() != null; return mModel.get(VISIBLE) && mModel.get(ACTIVE_TAB) != null;
} }
} }
// 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.Nullable;
import android.support.annotation.Px;
import android.support.design.widget.TabLayout;
import org.chromium.chrome.browser.modelutil.ListModel;
import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyObservable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* As model of the keyboard accessory component, this class holds the data relevant to the visual
* state of the accessory.
* This includes the visibility of the accessory in general, any available tabs and actions.
* Whenever the state changes, it notifies its listeners - like the
* {@link KeyboardAccessoryMediator} or the ModelChangeProcessor.
*/
class KeyboardAccessoryModel extends PropertyObservable<KeyboardAccessoryModel.PropertyKey> {
/** Keys uniquely identifying model properties. */
static class PropertyKey {
// This list contains all properties and is only necessary because the view needs to
// iterate over all properties when it's created to ensure it is in sync with this model.
static final List<PropertyKey> ALL_PROPERTIES = new ArrayList<>();
static final PropertyKey ACTIONS = new PropertyKey();
static final PropertyKey TABS = new PropertyKey();
static final PropertyKey VISIBLE = new PropertyKey();
static final PropertyKey BOTTOM_OFFSET = new PropertyKey();
static final PropertyKey ACTIVE_TAB = new PropertyKey();
static final PropertyKey TAB_SELECTION_CALLBACKS = new PropertyKey();
private PropertyKey() {
ALL_PROPERTIES.add(this);
}
}
private ListModel<KeyboardAccessoryData.Action> mActionListObservable;
private ListModel<KeyboardAccessoryData.Tab> mTabListObservable;
private boolean mVisible;
private @Px int mBottomOffset;
private @Nullable Integer mActiveTab;
private TabLayout.OnTabSelectedListener mTabSelectionCallbacks;
KeyboardAccessoryModel() {
mActionListObservable = new ListModel<>();
mTabListObservable = new ListModel<>();
}
@Override
public Collection<PropertyKey> getAllSetProperties() {
return PropertyKey.ALL_PROPERTIES;
}
void addActionListObserver(ListObservable.ListObserver<Void> observer) {
mActionListObservable.addObserver(observer);
}
void setActions(KeyboardAccessoryData.Action[] actions) {
mActionListObservable.set(actions);
}
ListModel<KeyboardAccessoryData.Action> getActionList() {
return mActionListObservable;
}
void addTabListObserver(ListObservable.ListObserver<Void> observer) {
mTabListObservable.addObserver(observer);
}
void addTab(KeyboardAccessoryData.Tab tab) {
mTabListObservable.add(tab);
}
void removeTab(KeyboardAccessoryData.Tab tab) {
mTabListObservable.remove(tab);
}
ListModel<KeyboardAccessoryData.Tab> getTabList() {
return mTabListObservable;
}
void setVisible(boolean visible) {
if (mVisible == visible) return; // Nothing to do here: same value.
mVisible = visible;
notifyPropertyChanged(PropertyKey.VISIBLE);
}
boolean isVisible() {
return mVisible;
}
void setBottomOffset(@Px int bottomOffset) {
if (mBottomOffset == bottomOffset) return; // Nothing to do here: same value.
mBottomOffset = bottomOffset;
notifyPropertyChanged(PropertyKey.BOTTOM_OFFSET);
}
@Px
int bottomOffset() {
return mBottomOffset;
}
@SuppressWarnings("ReferenceEquality") // No action if both are null or exact same object.
void setActiveTab(@Nullable Integer activeTab) {
if (activeTab == mActiveTab) return;
if (null != mActiveTab && mActiveTab.equals(activeTab)) return;
mActiveTab = activeTab;
notifyPropertyChanged(PropertyKey.ACTIVE_TAB);
}
@Nullable
Integer activeTab() {
return mActiveTab;
}
TabLayout.OnTabSelectedListener getTabSelectionCallbacks() {
return mTabSelectionCallbacks;
}
void setTabSelectionCallbacks(TabLayout.OnTabSelectedListener tabSelectionCallbacks) {
if (tabSelectionCallbacks == mTabSelectionCallbacks) return; // Nothing to do: same object.
mTabSelectionCallbacks = tabSelectionCallbacks;
notifyPropertyChanged(PropertyKey.TAB_SELECTION_CALLBACKS);
}
}
// 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.design.widget.TabLayout;
import org.chromium.chrome.browser.modelutil.ListModel;
import org.chromium.chrome.browser.modelutil.PropertyModel.ReadableObjectPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.WritableIntPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.WritableObjectPropertyKey;
/**
* As model of the keyboard accessory component, this class holds the data relevant to the visual
* state of the accessory.
* This includes the visibility of the accessory in general, any available tabs and actions.
* Whenever the state changes, it notifies its listeners - like the
* {@link KeyboardAccessoryMediator} or the ModelChangeProcessor.
*/
class KeyboardAccessoryProperties {
static final ReadableObjectPropertyKey<ListModel<KeyboardAccessoryData.Action>> ACTIONS =
new ReadableObjectPropertyKey<>();
static final ReadableObjectPropertyKey<ListModel<KeyboardAccessoryData.Tab>> TABS =
new ReadableObjectPropertyKey<>();
static final WritableBooleanPropertyKey VISIBLE = new WritableBooleanPropertyKey();
static final WritableIntPropertyKey BOTTOM_OFFSET_PX = new WritableIntPropertyKey();
static final /* @Nullable */ WritableObjectPropertyKey<Integer> ACTIVE_TAB =
new WritableObjectPropertyKey<>();
static final WritableObjectPropertyKey<TabLayout.OnTabSelectedListener>
TAB_SELECTION_CALLBACKS = new WritableObjectPropertyKey<>();
private KeyboardAccessoryProperties() {}
}
...@@ -4,6 +4,14 @@ ...@@ -4,6 +4,14 @@
package org.chromium.chrome.browser.autofill.keyboard_accessory; package org.chromium.chrome.browser.autofill.keyboard_accessory;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.ACTIONS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.ACTIVE_TAB;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.BOTTOM_OFFSET_PX;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.TABS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.TAB_SELECTION_CALLBACKS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.VISIBLE;
import android.support.design.widget.TabLayout;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
...@@ -13,13 +21,14 @@ import android.widget.TextView; ...@@ -13,13 +21,14 @@ import android.widget.TextView;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Action; import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Action;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab; import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryModel.PropertyKey;
import org.chromium.chrome.browser.modelutil.ListModel; import org.chromium.chrome.browser.modelutil.ListModel;
import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor; import org.chromium.chrome.browser.modelutil.ListModelChangeProcessor;
import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel;
/** /**
* Observes {@link KeyboardAccessoryModel} changes (like a newly available tab) and triggers the * Observes {@link KeyboardAccessoryProperties} changes (like a newly available tab) and triggers
* {@link KeyboardAccessoryViewBinder} which will modify the view accordingly. * the {@link KeyboardAccessoryViewBinder} which will modify the view accordingly.
*/ */
class KeyboardAccessoryViewBinder { class KeyboardAccessoryViewBinder {
static class ActionViewHolder extends RecyclerView.ViewHolder { static class ActionViewHolder extends RecyclerView.ViewHolder {
...@@ -89,39 +98,40 @@ class KeyboardAccessoryViewBinder { ...@@ -89,39 +98,40 @@ class KeyboardAccessoryViewBinder {
} }
} }
public static void bind(KeyboardAccessoryModel model, KeyboardAccessoryView view, public static void bind(
PropertyKey propertyKey) { PropertyModel model, KeyboardAccessoryView view, PropertyKey propertyKey) {
if (propertyKey == PropertyKey.ACTIONS) { if (propertyKey == ACTIONS) {
view.setActionsAdapter( view.setActionsAdapter(
KeyboardAccessoryCoordinator.createActionsAdapter(model.getActionList())); KeyboardAccessoryCoordinator.createActionsAdapter(model.get(ACTIONS)));
} else if (propertyKey == PropertyKey.TABS) { } else if (propertyKey == TABS) {
KeyboardAccessoryCoordinator.createTabViewBinder(model, view) KeyboardAccessoryCoordinator.createTabViewBinder(model, view)
.updateAllTabs(view, model.getTabList()); .updateAllTabs(view, model.get(TABS));
} else if (propertyKey == PropertyKey.VISIBLE) { } else if (propertyKey == VISIBLE) {
view.setActiveTabColor(model.activeTab()); view.setActiveTabColor(model.get(ACTIVE_TAB));
setActiveTabHint(model, view); setActiveTabHint(model, view);
view.setVisible(model.isVisible()); view.setVisible(model.get(VISIBLE));
} else if (propertyKey == PropertyKey.ACTIVE_TAB) { } else if (propertyKey == ACTIVE_TAB) {
view.setActiveTabColor(model.activeTab()); view.setActiveTabColor(model.get(ACTIVE_TAB));
setActiveTabHint(model, view); setActiveTabHint(model, view);
} else if (propertyKey == PropertyKey.BOTTOM_OFFSET) { } else if (propertyKey == BOTTOM_OFFSET_PX) {
view.setBottomOffset(model.bottomOffset()); view.setBottomOffset(model.get(BOTTOM_OFFSET_PX));
} else if (propertyKey == PropertyKey.TAB_SELECTION_CALLBACKS) { } else if (propertyKey == TAB_SELECTION_CALLBACKS) {
// Don't add null as listener. It's a valid state but an invalid argument. // Don't add null as listener. It's a valid state but an invalid argument.
if (model.getTabSelectionCallbacks() == null) return; TabLayout.OnTabSelectedListener listener = model.get(TAB_SELECTION_CALLBACKS);
view.setTabSelectionAdapter(model.getTabSelectionCallbacks()); if (listener == null) return;
view.setTabSelectionAdapter(listener);
} else { } else {
assert false : "Every possible property update needs to be handled!"; assert false : "Every possible property update needs to be handled!";
} }
} }
private static void setActiveTabHint(KeyboardAccessoryModel model, KeyboardAccessoryView view) { private static void setActiveTabHint(PropertyModel model, KeyboardAccessoryView view) {
int activeTab = -1; int activeTab = -1;
if (model.activeTab() != null) { if (model.get(ACTIVE_TAB) != null) {
activeTab = model.activeTab(); activeTab = model.get(ACTIVE_TAB);
} }
for (int i = 0; i < model.getTabList().size(); ++i) { for (int i = 0; i < model.get(TABS).size(); ++i) {
Tab tab = model.getTabList().get(i); Tab tab = model.get(TABS).get(i);
if (activeTab == i) { if (activeTab == i) {
view.setTabDescription(i, R.string.keyboard_accessory_sheet_hide); view.setTabDescription(i, R.string.keyboard_accessory_sheet_hide);
} else { } else {
......
...@@ -7,7 +7,7 @@ package org.chromium.chrome.browser.download.home.empty; ...@@ -7,7 +7,7 @@ package org.chromium.chrome.browser.download.home.empty;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import org.chromium.chrome.browser.modelutil.PropertyKey; import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.IntPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableIntPropertyKey;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
...@@ -23,13 +23,13 @@ interface EmptyProperties { ...@@ -23,13 +23,13 @@ interface EmptyProperties {
} }
/** The current state of the empty view. */ /** The current state of the empty view. */
public static final IntPropertyKey STATE = new IntPropertyKey(); public static final WritableIntPropertyKey STATE = new WritableIntPropertyKey();
/** The current text resource to use for the empty view. */ /** The current text resource to use for the empty view. */
public static final IntPropertyKey EMPTY_TEXT_RES_ID = new IntPropertyKey(); public static final WritableIntPropertyKey EMPTY_TEXT_RES_ID = new WritableIntPropertyKey();
/** The current icon resource to use for the empty view. */ /** The current icon resource to use for the empty view. */
public static final IntPropertyKey EMPTY_ICON_RES_ID = new IntPropertyKey(); public static final WritableIntPropertyKey EMPTY_ICON_RES_ID = new WritableIntPropertyKey();
public static final PropertyKey[] ALL_KEYS = public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {STATE, EMPTY_TEXT_RES_ID, EMPTY_ICON_RES_ID}; new PropertyKey[] {STATE, EMPTY_TEXT_RES_ID, EMPTY_ICON_RES_ID};
......
...@@ -8,24 +8,25 @@ import android.view.View; ...@@ -8,24 +8,25 @@ import android.view.View;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.chrome.browser.modelutil.PropertyKey; import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.BooleanPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.IntPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableIntPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.ObjectPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableObjectPropertyKey;
/** The properties needed to render the download home filter view. */ /** The properties needed to render the download home filter view. */
public interface FilterProperties { public interface FilterProperties {
/** The {@link View} to show in the content area. */ /** The {@link View} to show in the content area. */
public static final ObjectPropertyKey<View> CONTENT_VIEW = new ObjectPropertyKey<>(); public static final WritableObjectPropertyKey<View> CONTENT_VIEW =
new WritableObjectPropertyKey<>();
/** Which {@code TabType} should be selected. */ /** Which {@code TabType} should be selected. */
public static final IntPropertyKey SELECTED_TAB = new IntPropertyKey(); public static final WritableIntPropertyKey SELECTED_TAB = new WritableIntPropertyKey();
/** The callback listener for {@code TabType} selection changes. */ /** The callback listener for {@code TabType} selection changes. */
public static final ObjectPropertyKey<Callback</* @TabType */ Integer>> CHANGE_LISTENER = public static final WritableObjectPropertyKey<Callback</* @TabType */ Integer>>
new ObjectPropertyKey<>(); CHANGE_LISTENER = new WritableObjectPropertyKey<>();
/** Whether or not to show the tabs or just show the content. */ /** Whether or not to show the tabs or just show the content. */
public static final BooleanPropertyKey SHOW_TABS = new BooleanPropertyKey(); public static final WritableBooleanPropertyKey SHOW_TABS = new WritableBooleanPropertyKey();
public static final PropertyKey[] ALL_KEYS = public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {CONTENT_VIEW, SELECTED_TAB, CHANGE_LISTENER, SHOW_TABS}; new PropertyKey[] {CONTENT_VIEW, SELECTED_TAB, CHANGE_LISTENER, SHOW_TABS};
......
...@@ -6,8 +6,8 @@ package org.chromium.chrome.browser.download.home.list; ...@@ -6,8 +6,8 @@ package org.chromium.chrome.browser.download.home.list;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.chrome.browser.modelutil.PropertyKey; import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.BooleanPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.ObjectPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableObjectPropertyKey;
import org.chromium.components.offline_items_collection.OfflineItem; import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItemVisuals; import org.chromium.components.offline_items_collection.OfflineItemVisuals;
import org.chromium.components.offline_items_collection.VisualsCallback; import org.chromium.components.offline_items_collection.VisualsCallback;
...@@ -32,34 +32,41 @@ public interface ListProperties { ...@@ -32,34 +32,41 @@ public interface ListProperties {
} }
/** Whether or not item animations should be enabled. */ /** Whether or not item animations should be enabled. */
BooleanPropertyKey ENABLE_ITEM_ANIMATIONS = new BooleanPropertyKey(); WritableBooleanPropertyKey ENABLE_ITEM_ANIMATIONS = new WritableBooleanPropertyKey();
/** The callback for when a UI action should open a {@link OfflineItem}. */ /** The callback for when a UI action should open a {@link OfflineItem}. */
ObjectPropertyKey<Callback<OfflineItem>> CALLBACK_OPEN = new ObjectPropertyKey<>(); WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_OPEN =
new WritableObjectPropertyKey<>();
/** The callback for when a UI action should pause a {@link OfflineItem}. */ /** The callback for when a UI action should pause a {@link OfflineItem}. */
ObjectPropertyKey<Callback<OfflineItem>> CALLBACK_PAUSE = new ObjectPropertyKey<>(); WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_PAUSE =
new WritableObjectPropertyKey<>();
/** The callback for when a UI action should resume a {@link OfflineItem}. */ /** The callback for when a UI action should resume a {@link OfflineItem}. */
ObjectPropertyKey<Callback<OfflineItem>> CALLBACK_RESUME = new ObjectPropertyKey<>(); WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_RESUME =
new WritableObjectPropertyKey<>();
/** The callback for when a UI action should cancel a {@link OfflineItem}. */ /** The callback for when a UI action should cancel a {@link OfflineItem}. */
ObjectPropertyKey<Callback<OfflineItem>> CALLBACK_CANCEL = new ObjectPropertyKey<>(); WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_CANCEL =
new WritableObjectPropertyKey<>();
/** The callback for when a UI action should share a {@link OfflineItem}. */ /** The callback for when a UI action should share a {@link OfflineItem}. */
ObjectPropertyKey<Callback<OfflineItem>> CALLBACK_SHARE = new ObjectPropertyKey<>(); WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_SHARE =
new WritableObjectPropertyKey<>();
/** The callback for when a UI action should remove a {@link OfflineItem}. */ /** The callback for when a UI action should remove a {@link OfflineItem}. */
ObjectPropertyKey<Callback<OfflineItem>> CALLBACK_REMOVE = new ObjectPropertyKey<>(); WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_REMOVE =
new WritableObjectPropertyKey<>();
/** The provider to retrieve expensive assets for a {@link OfflineItem}. */ /** The provider to retrieve expensive assets for a {@link OfflineItem}. */
ObjectPropertyKey<VisualsProvider> PROVIDER_VISUALS = new ObjectPropertyKey<>(); WritableObjectPropertyKey<VisualsProvider> PROVIDER_VISUALS = new WritableObjectPropertyKey<>();
/** The callback to trigger when a UI action selects or deselects a {@link ListItem}. */ /** The callback to trigger when a UI action selects or deselects a {@link ListItem}. */
ObjectPropertyKey<Callback<ListItem>> CALLBACK_SELECTION = new ObjectPropertyKey<>(); WritableObjectPropertyKey<Callback<ListItem>> CALLBACK_SELECTION =
new WritableObjectPropertyKey<>();
/** Whether or not selection mode is currently active. */ /** Whether or not selection mode is currently active. */
BooleanPropertyKey SELECTION_MODE_ACTIVE = new BooleanPropertyKey(); WritableBooleanPropertyKey SELECTION_MODE_ACTIVE = new WritableBooleanPropertyKey();
PropertyKey[] ALL_KEYS = new PropertyKey[] {ENABLE_ITEM_ANIMATIONS, CALLBACK_OPEN, PropertyKey[] ALL_KEYS = new PropertyKey[] {ENABLE_ITEM_ANIMATIONS, CALLBACK_OPEN,
CALLBACK_PAUSE, CALLBACK_RESUME, CALLBACK_CANCEL, CALLBACK_SHARE, CALLBACK_REMOVE, CALLBACK_PAUSE, CALLBACK_RESUME, CALLBACK_CANCEL, CALLBACK_SHARE, CALLBACK_REMOVE,
......
...@@ -12,7 +12,7 @@ import android.widget.TextView; ...@@ -12,7 +12,7 @@ import android.widget.TextView;
import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterSource; import org.chromium.chrome.browser.download.home.filter.OfflineItemFilterSource;
import org.chromium.chrome.browser.modelutil.PropertyKey; import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel; import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.chrome.browser.modelutil.PropertyModel.ObjectPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableObjectPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor; import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor;
import org.chromium.chrome.download.R; import org.chromium.chrome.download.R;
...@@ -48,7 +48,8 @@ public class StorageCoordinator { ...@@ -48,7 +48,8 @@ public class StorageCoordinator {
/** The properties needed to render the download home storage summary view. */ /** The properties needed to render the download home storage summary view. */
private static class StorageProperties { private static class StorageProperties {
/** The storage summary text to show in the content area. */ /** The storage summary text to show in the content area. */
public static final ObjectPropertyKey<String> STORAGE_INFO_TEXT = new ObjectPropertyKey<>(); public static final WritableObjectPropertyKey<String> STORAGE_INFO_TEXT =
new WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {STORAGE_INFO_TEXT}; public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {STORAGE_INFO_TEXT};
} }
......
...@@ -50,16 +50,21 @@ public class LazyConstructionPropertyMcp<M extends PropertyObservable<P>, V exte ...@@ -50,16 +50,21 @@ public class LazyConstructionPropertyMcp<M extends PropertyObservable<P>, V exte
mPendingProperties.addAll(mModel.getAllSetProperties()); mPendingProperties.addAll(mModel.getAllSetProperties());
mViewProvider.whenLoaded(this ::onViewCreated); mViewProvider.whenLoaded(this::onViewCreated);
// The model should start out hidden. // The model should start out hidden.
assert !mVisibilityPredicate.isVisible(mModel); assert !mVisibilityPredicate.isVisible(mModel);
// The visibility property should be set initially, to avoid spurious property change
// notifications that would cause the view to be inflated prematurely.
assert mPendingProperties.contains(mVisibilityProperty);
mModel.addObserver(this); mModel.addObserver(this);
} }
public static <M extends PropertyModel, V extends View> public static <M extends PropertyModel, V extends View>
LazyConstructionPropertyMcp<M, V, PropertyKey> create(M model, LazyConstructionPropertyMcp<M, V, PropertyKey> create(M model,
PropertyModel.BooleanPropertyKey visibilityProperty, PropertyModel.WritableBooleanPropertyKey visibilityProperty,
ViewProvider<V> viewFactory, ViewProvider<V> viewFactory,
PropertyModelChangeProcessor.ViewBinder<M, V, PropertyKey> viewBinder) { PropertyModelChangeProcessor.ViewBinder<M, V, PropertyKey> viewBinder) {
return new LazyConstructionPropertyMcp<>(model, visibilityProperty, return new LazyConstructionPropertyMcp<>(model, visibilityProperty,
......
...@@ -18,23 +18,40 @@ import java.util.Map; ...@@ -18,23 +18,40 @@ import java.util.Map;
* Generic property model that aims to provide an extensible and efficient model for ease of use. * Generic property model that aims to provide an extensible and efficient model for ease of use.
*/ */
public class PropertyModel extends PropertyObservable<PropertyKey> { public class PropertyModel extends PropertyObservable<PropertyKey> {
/** The key type for boolean model properties. */ /** The key type for read-ony boolean model properties. */
public final static class BooleanPropertyKey implements PropertyKey {} public static class ReadableBooleanPropertyKey implements PropertyKey {}
/** The key type for float model properties. */ /** The key type for mutable boolean model properties. */
public static class FloatPropertyKey implements PropertyKey {} public final static class WritableBooleanPropertyKey extends ReadableBooleanPropertyKey {}
/** The key type for int model properties. */ /** The key type for read-only float model properties. */
public static class IntPropertyKey implements PropertyKey {} public static class ReadableFloatPropertyKey implements PropertyKey {}
/** The key type for mutable float model properties. */
public final static class WritableFloatPropertyKey extends ReadableFloatPropertyKey {}
/** The key type for read-only int model properties. */
public static class ReadableIntPropertyKey implements PropertyKey {}
/** The key type for mutable int model properties. */
public final static class WritableIntPropertyKey extends ReadableIntPropertyKey {}
/** /**
* The key type for Object model properties. * The key type for read-only Object model properties.
* *
* @param <T> The type of the Object being tracked by the key. * @param <T> The type of the Object being tracked by the key.
*/ */
public static class ObjectPropertyKey<T> implements PropertyKey {} public static class ReadableObjectPropertyKey<T> implements PropertyKey {}
private final Map<PropertyKey, ValueContainer> mData = new HashMap<>(); /**
* The key type for mutable Object model properties.
*
* @param <T> The type of the Object being tracked by the key.
*/
public final static class WritableObjectPropertyKey<T>
extends ReadableObjectPropertyKey<T> implements PropertyKey {}
private final Map<PropertyKey, ValueContainer> mData;
/** /**
* Constructs a model for the given list of keys. * Constructs a model for the given list of keys.
...@@ -42,10 +59,11 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -42,10 +59,11 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
* @param keys The key types supported by this model. * @param keys The key types supported by this model.
*/ */
public PropertyModel(PropertyKey... keys) { public PropertyModel(PropertyKey... keys) {
for (PropertyKey key : keys) { this(buildData(keys));
if (mData.containsKey(key)) throw new IllegalArgumentException("Duplicate key: " + key); }
mData.put(key, null);
} private PropertyModel(Map<PropertyKey, ValueContainer> startingValues) {
mData = startingValues;
} }
@RemovableInRelease @RemovableInRelease
...@@ -58,7 +76,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -58,7 +76,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
/** /**
* Get the current value from the float based key. * Get the current value from the float based key.
*/ */
public float get(FloatPropertyKey key) { public float get(ReadableFloatPropertyKey key) {
validateKey(key); validateKey(key);
FloatContainer container = (FloatContainer) mData.get(key); FloatContainer container = (FloatContainer) mData.get(key);
return container == null ? 0f : container.value; return container == null ? 0f : container.value;
...@@ -67,7 +85,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -67,7 +85,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
/** /**
* Set the value for the float based key. * Set the value for the float based key.
*/ */
public void set(FloatPropertyKey key, float value) { public void set(WritableFloatPropertyKey key, float value) {
validateKey(key); validateKey(key);
FloatContainer container = (FloatContainer) mData.get(key); FloatContainer container = (FloatContainer) mData.get(key);
if (container == null) { if (container == null) {
...@@ -76,6 +94,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -76,6 +94,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
} else if (container.value == value) { } else if (container.value == value) {
return; return;
} }
container.value = value; container.value = value;
notifyPropertyChanged(key); notifyPropertyChanged(key);
} }
...@@ -83,7 +102,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -83,7 +102,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
/** /**
* Get the current value from the int based key. * Get the current value from the int based key.
*/ */
public int get(IntPropertyKey key) { public int get(ReadableIntPropertyKey key) {
validateKey(key); validateKey(key);
IntContainer container = (IntContainer) mData.get(key); IntContainer container = (IntContainer) mData.get(key);
return container == null ? 0 : container.value; return container == null ? 0 : container.value;
...@@ -92,7 +111,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -92,7 +111,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
/** /**
* Set the value for the int based key. * Set the value for the int based key.
*/ */
public void set(IntPropertyKey key, int value) { public void set(WritableIntPropertyKey key, int value) {
validateKey(key); validateKey(key);
IntContainer container = (IntContainer) mData.get(key); IntContainer container = (IntContainer) mData.get(key);
if (container == null) { if (container == null) {
...@@ -101,6 +120,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -101,6 +120,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
} else if (container.value == value) { } else if (container.value == value) {
return; return;
} }
container.value = value; container.value = value;
notifyPropertyChanged(key); notifyPropertyChanged(key);
} }
...@@ -108,7 +128,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -108,7 +128,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
/** /**
* Get the current value from the boolean based key. * Get the current value from the boolean based key.
*/ */
public boolean get(BooleanPropertyKey key) { public boolean get(ReadableBooleanPropertyKey key) {
validateKey(key); validateKey(key);
BooleanContainer container = (BooleanContainer) mData.get(key); BooleanContainer container = (BooleanContainer) mData.get(key);
return container == null ? false : container.value; return container == null ? false : container.value;
...@@ -117,7 +137,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -117,7 +137,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
/** /**
* Set the value for the boolean based key. * Set the value for the boolean based key.
*/ */
public void set(BooleanPropertyKey key, boolean value) { public void set(WritableBooleanPropertyKey key, boolean value) {
validateKey(key); validateKey(key);
BooleanContainer container = (BooleanContainer) mData.get(key); BooleanContainer container = (BooleanContainer) mData.get(key);
if (container == null) { if (container == null) {
...@@ -126,6 +146,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -126,6 +146,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
} else if (container.value == value) { } else if (container.value == value) {
return; return;
} }
container.value = value; container.value = value;
notifyPropertyChanged(key); notifyPropertyChanged(key);
} }
...@@ -134,7 +155,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -134,7 +155,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
* Get the current value from the object based key. * Get the current value from the object based key.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T get(ObjectPropertyKey<T> key) { public <T> T get(ReadableObjectPropertyKey<T> key) {
validateKey(key); validateKey(key);
ObjectContainer<T> container = (ObjectContainer<T>) mData.get(key); ObjectContainer<T> container = (ObjectContainer<T>) mData.get(key);
return container == null ? null : container.value; return container == null ? null : container.value;
...@@ -144,7 +165,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -144,7 +165,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
* Set the value for the Object based key. * Set the value for the Object based key.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> void set(ObjectPropertyKey<T> key, T value) { public <T> void set(WritableObjectPropertyKey<T> key, T value) {
validateKey(key); validateKey(key);
ObjectContainer<T> container = (ObjectContainer<T>) mData.get(key); ObjectContainer<T> container = (ObjectContainer<T>) mData.get(key);
if (container == null) { if (container == null) {
...@@ -153,6 +174,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -153,6 +174,7 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
} else if (ObjectsCompat.equals(container.value, value)) { } else if (ObjectsCompat.equals(container.value, value)) {
return; return;
} }
container.value = value; container.value = value;
notifyPropertyChanged(key); notifyPropertyChanged(key);
} }
...@@ -166,6 +188,75 @@ public class PropertyModel extends PropertyObservable<PropertyKey> { ...@@ -166,6 +188,75 @@ public class PropertyModel extends PropertyObservable<PropertyKey> {
return properties; return properties;
} }
/**
* Allows constructing a new {@link PropertyModel} with read-only properties.
*/
public static class Builder {
private final Map<PropertyKey, ValueContainer> mData;
public Builder(PropertyKey... keys) {
this(buildData(keys));
}
private Builder(Map<PropertyKey, ValueContainer> values) {
mData = values;
}
@RemovableInRelease
private void validateKey(PropertyKey key) {
if (!mData.containsKey(key)) {
throw new IllegalArgumentException("Invalid key passed in: " + key);
}
}
public Builder with(ReadableFloatPropertyKey key, float value) {
validateKey(key);
FloatContainer container = new FloatContainer();
container.value = value;
mData.put(key, container);
return this;
}
public Builder with(ReadableIntPropertyKey key, int value) {
validateKey(key);
IntContainer container = new IntContainer();
container.value = value;
mData.put(key, container);
return this;
}
public Builder with(ReadableBooleanPropertyKey key, boolean value) {
validateKey(key);
BooleanContainer container = new BooleanContainer();
container.value = value;
mData.put(key, container);
return this;
}
public <T> Builder with(ReadableObjectPropertyKey<T> key, T value) {
validateKey(key);
ObjectContainer<T> container = new ObjectContainer<>();
container.value = value;
mData.put(key, container);
return this;
}
public PropertyModel build() {
return new PropertyModel(mData);
}
}
private static Map<PropertyKey, ValueContainer> buildData(PropertyKey[] keys) {
Map<PropertyKey, ValueContainer> data = new HashMap<>();
for (PropertyKey key : keys) {
if (data.containsKey(key)) {
throw new IllegalArgumentException("Duplicate key: " + key);
}
data.put(key, null);
}
return data;
}
private interface ValueContainer {} private interface ValueContainer {}
private static class FloatContainer implements ValueContainer { public float value; } private static class FloatContainer implements ValueContainer { public float value; }
private static class IntContainer implements ValueContainer { public int value; } private static class IntContainer implements ValueContainer { public int value; }
......
...@@ -9,8 +9,8 @@ import android.view.ActionMode; ...@@ -9,8 +9,8 @@ import android.view.ActionMode;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.chrome.browser.WindowDelegate; import org.chromium.chrome.browser.WindowDelegate;
import org.chromium.chrome.browser.modelutil.PropertyKey; import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.BooleanPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.ObjectPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableObjectPropertyKey;
import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType; import org.chromium.chrome.browser.omnibox.UrlBar.ScrollType;
import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate; import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarDelegate;
import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarTextContextMenuDelegate; import org.chromium.chrome.browser.omnibox.UrlBar.UrlBarTextContextMenuDelegate;
...@@ -77,43 +77,46 @@ class UrlBarProperties { ...@@ -77,43 +77,46 @@ class UrlBarProperties {
} }
/** The callback for contextual action modes (cut, copy, etc...). */ /** The callback for contextual action modes (cut, copy, etc...). */
public static final ObjectPropertyKey<ActionMode.Callback> ACTION_MODE_CALLBACK = public static final WritableObjectPropertyKey<ActionMode.Callback> ACTION_MODE_CALLBACK =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** Whether focus should be allowed on the view. */ /** Whether focus should be allowed on the view. */
public static final BooleanPropertyKey ALLOW_FOCUS = new BooleanPropertyKey(); public static final WritableBooleanPropertyKey ALLOW_FOCUS = new WritableBooleanPropertyKey();
/** Specified the autocomplete text to be shown to the user. */ /** Specified the autocomplete text to be shown to the user. */
public static final ObjectPropertyKey<AutocompleteText> AUTOCOMPLETE_TEXT = public static final WritableObjectPropertyKey<AutocompleteText> AUTOCOMPLETE_TEXT =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** The main delegate that provides additional capabilities to the UrlBar. */ /** The main delegate that provides additional capabilities to the UrlBar. */
public static final ObjectPropertyKey<UrlBarDelegate> DELEGATE = new ObjectPropertyKey<>(); public static final WritableObjectPropertyKey<UrlBarDelegate> DELEGATE =
new WritableObjectPropertyKey<>();
/** The callback to be notified on focus changes. */ /** The callback to be notified on focus changes. */
public static final ObjectPropertyKey<Callback<Boolean>> FOCUS_CHANGE_CALLBACK = public static final WritableObjectPropertyKey<Callback<Boolean>> FOCUS_CHANGE_CALLBACK =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** Whether the cursor should be shown in the view. */ /** Whether the cursor should be shown in the view. */
public static final BooleanPropertyKey SHOW_CURSOR = new BooleanPropertyKey(); public static final WritableBooleanPropertyKey SHOW_CURSOR = new WritableBooleanPropertyKey();
/** Delegate that provides additional functionality to the textual context actions. */ /** Delegate that provides additional functionality to the textual context actions. */
public static final ObjectPropertyKey<UrlBarTextContextMenuDelegate> public static final WritableObjectPropertyKey<UrlBarTextContextMenuDelegate>
TEXT_CONTEXT_MENU_DELEGATE = new ObjectPropertyKey<>(); TEXT_CONTEXT_MENU_DELEGATE = new WritableObjectPropertyKey<>();
/** The primary text state for what is shown in the view. */ /** The primary text state for what is shown in the view. */
public static final ObjectPropertyKey<UrlBarTextState> TEXT_STATE = new ObjectPropertyKey<>(); public static final WritableObjectPropertyKey<UrlBarTextState> TEXT_STATE =
new WritableObjectPropertyKey<>();
/** The listener to be notified of URL direction changes. */ /** The listener to be notified of URL direction changes. */
public static final ObjectPropertyKey<UrlDirectionListener> URL_DIRECTION_LISTENER = public static final WritableObjectPropertyKey<UrlDirectionListener> URL_DIRECTION_LISTENER =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** Specifies whether dark text colors should be used in the view. */ /** Specifies whether dark text colors should be used in the view. */
public static final BooleanPropertyKey USE_DARK_TEXT_COLORS = new BooleanPropertyKey(); public static final WritableBooleanPropertyKey USE_DARK_TEXT_COLORS =
new WritableBooleanPropertyKey();
/** The delegate that provides Window capabilities to the view. */ /** The delegate that provides Window capabilities to the view. */
public static final ObjectPropertyKey<WindowDelegate> WINDOW_DELEGATE = public static final WritableObjectPropertyKey<WindowDelegate> WINDOW_DELEGATE =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS = public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {ACTION_MODE_CALLBACK, ALLOW_FOCUS, AUTOCOMPLETE_TEXT, DELEGATE, new PropertyKey[] {ACTION_MODE_CALLBACK, ALLOW_FOCUS, AUTOCOMPLETE_TEXT, DELEGATE,
......
...@@ -13,15 +13,16 @@ import org.chromium.chrome.browser.modelutil.PropertyModel; ...@@ -13,15 +13,16 @@ import org.chromium.chrome.browser.modelutil.PropertyModel;
class PasswordGenerationDialogModel extends PropertyModel { class PasswordGenerationDialogModel extends PropertyModel {
/** The generated password to be displayed in the dialog. */ /** The generated password to be displayed in the dialog. */
public static final ObjectPropertyKey<String> GENERATED_PASSWORD = new ObjectPropertyKey<>(); public static final WritableObjectPropertyKey<String> GENERATED_PASSWORD =
new WritableObjectPropertyKey<>();
/** Explanation text for how the generated password is saved. */ /** Explanation text for how the generated password is saved. */
public static final ObjectPropertyKey<String> public static final WritableObjectPropertyKey<String> SAVE_EXPLANATION_TEXT =
SAVE_EXPLANATION_TEXT = new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** Callback invoked when the password is accepted or rejected by the user. */ /** Callback invoked when the password is accepted or rejected by the user. */
public static final ObjectPropertyKey<Callback<Boolean>> PASSWORD_ACTION_CALLBACK = public static final WritableObjectPropertyKey<Callback<Boolean>> PASSWORD_ACTION_CALLBACK =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** Default constructor */ /** Default constructor */
public PasswordGenerationDialogModel() { public PasswordGenerationDialogModel() {
......
...@@ -16,39 +16,42 @@ import org.chromium.ui.resources.ResourceManager; ...@@ -16,39 +16,42 @@ import org.chromium.ui.resources.ResourceManager;
*/ */
public class BottomToolbarModel extends PropertyModel { public class BottomToolbarModel extends PropertyModel {
/** The Y offset of the view in px. */ /** The Y offset of the view in px. */
public static final IntPropertyKey Y_OFFSET = new IntPropertyKey(); public static final WritableIntPropertyKey Y_OFFSET = new WritableIntPropertyKey();
/** Whether the Android view version of the toolbar is visible. */ /** Whether the Android view version of the toolbar is visible. */
public static final BooleanPropertyKey ANDROID_VIEW_VISIBLE = new BooleanPropertyKey(); public static final WritableBooleanPropertyKey ANDROID_VIEW_VISIBLE =
new WritableBooleanPropertyKey();
/** Whether the composited version of the toolbar is visible. */ /** Whether the composited version of the toolbar is visible. */
public static final BooleanPropertyKey COMPOSITED_VIEW_VISIBLE = new BooleanPropertyKey(); public static final WritableBooleanPropertyKey COMPOSITED_VIEW_VISIBLE =
new WritableBooleanPropertyKey();
/** A {@link LayoutManager} to attach overlays to. */ /** A {@link LayoutManager} to attach overlays to. */
public static final ObjectPropertyKey<LayoutManager> LAYOUT_MANAGER = new ObjectPropertyKey<>(); public static final WritableObjectPropertyKey<LayoutManager> LAYOUT_MANAGER =
new WritableObjectPropertyKey<>();
/** The browser's {@link ToolbarSwipeLayout}. */ /** The browser's {@link ToolbarSwipeLayout}. */
public static final ObjectPropertyKey<ToolbarSwipeLayout> TOOLBAR_SWIPE_LAYOUT = public static final WritableObjectPropertyKey<ToolbarSwipeLayout> TOOLBAR_SWIPE_LAYOUT =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** A {@link ResourceManager} for loading textures into the compositor. */ /** A {@link ResourceManager} for loading textures into the compositor. */
public static final ObjectPropertyKey<ResourceManager> RESOURCE_MANAGER = public static final WritableObjectPropertyKey<ResourceManager> RESOURCE_MANAGER =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** A handler for swipe events on the toolbar. */ /** A handler for swipe events on the toolbar. */
public static final ObjectPropertyKey<EdgeSwipeHandler> TOOLBAR_SWIPE_HANDLER = public static final WritableObjectPropertyKey<EdgeSwipeHandler> TOOLBAR_SWIPE_HANDLER =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** Data used to show the first button. */ /** Data used to show the first button. */
public static final ObjectPropertyKey<ToolbarButtonData> FIRST_BUTTON_DATA = public static final WritableObjectPropertyKey<ToolbarButtonData> FIRST_BUTTON_DATA =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** Data used to show the second button. */ /** Data used to show the second button. */
public static final ObjectPropertyKey<ToolbarButtonData> SECOND_BUTTON_DATA = public static final WritableObjectPropertyKey<ToolbarButtonData> SECOND_BUTTON_DATA =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** Primary color of bottom toolbar. */ /** Primary color of bottom toolbar. */
public static final IntPropertyKey PRIMARY_COLOR = new IntPropertyKey(); public static final WritableIntPropertyKey PRIMARY_COLOR = new WritableIntPropertyKey();
/** Default constructor. */ /** Default constructor. */
public BottomToolbarModel() { public BottomToolbarModel() {
......
...@@ -9,26 +9,27 @@ import android.view.View.OnClickListener; ...@@ -9,26 +9,27 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener; import android.view.View.OnLongClickListener;
import org.chromium.chrome.browser.modelutil.PropertyKey; import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.IntPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableIntPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.ObjectPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableObjectPropertyKey;
/** /**
* The properties needed to render the tab switcher button. * The properties needed to render the tab switcher button.
*/ */
public interface TabSwitcherButtonProperties { public interface TabSwitcherButtonProperties {
/** The current number of tabs. */ /** The current number of tabs. */
public static final IntPropertyKey NUMBER_OF_TABS = new IntPropertyKey(); public static final WritableIntPropertyKey NUMBER_OF_TABS = new WritableIntPropertyKey();
/** The click listener for the tab switcher button. */ /** The click listener for the tab switcher button. */
public static final ObjectPropertyKey<OnClickListener> ON_CLICK_LISTENER = public static final WritableObjectPropertyKey<OnClickListener> ON_CLICK_LISTENER =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** The long click listener for the tab switcher button. */ /** The long click listener for the tab switcher button. */
public static final ObjectPropertyKey<OnLongClickListener> ON_LONG_CLICK_LISTENER = public static final WritableObjectPropertyKey<OnLongClickListener> ON_LONG_CLICK_LISTENER =
new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
/** The button tint. */ /** The button tint. */
public static final ObjectPropertyKey<ColorStateList> TINT = new ObjectPropertyKey<>(); public static final WritableObjectPropertyKey<ColorStateList> TINT =
new WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS = public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {NUMBER_OF_TABS, ON_CLICK_LISTENER, ON_LONG_CLICK_LISTENER, TINT}; new PropertyKey[] {NUMBER_OF_TABS, ON_CLICK_LISTENER, ON_LONG_CLICK_LISTENER, TINT};
......
...@@ -99,13 +99,13 @@ chrome_java_sources = [ ...@@ -99,13 +99,13 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessoryPagerAdapter.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessoryPagerAdapter.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetCoordinator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetMediator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetMediator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetModel.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetProperties.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryCoordinator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryCoordinator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewBinder.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/AccessorySheetViewBinder.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryData.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryData.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMediator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMediator.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMetricsRecorder.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryMetricsRecorder.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryModel.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryProperties.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryView.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryView.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewBinder.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryViewBinder.java",
"java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingCoordinator.java", "java/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingCoordinator.java",
......
...@@ -18,6 +18,11 @@ import static junit.framework.Assert.assertNull; ...@@ -18,6 +18,11 @@ import static junit.framework.Assert.assertNull;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.ACTIVE_TAB_INDEX;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.HEIGHT;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.NO_ACTIVE_TAB;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.TABS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.VISIBLE;
import static org.chromium.chrome.test.util.ViewUtils.waitForView; import static org.chromium.chrome.test.util.ViewUtils.waitForView;
import android.support.test.filters.MediumTest; import android.support.test.filters.MediumTest;
...@@ -40,6 +45,8 @@ import org.chromium.chrome.browser.ChromeSwitches; ...@@ -40,6 +45,8 @@ import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab; import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab;
import org.chromium.chrome.browser.modelutil.LazyConstructionPropertyMcp; import org.chromium.chrome.browser.modelutil.LazyConstructionPropertyMcp;
import org.chromium.chrome.browser.modelutil.ListModel;
import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.chrome.test.ChromeActivityTestRule; import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.ui.DeferredViewStubInflationProvider; import org.chromium.ui.DeferredViewStubInflationProvider;
...@@ -55,7 +62,7 @@ import java.util.concurrent.ExecutionException; ...@@ -55,7 +62,7 @@ import java.util.concurrent.ExecutionException;
@RunWith(ChromeJUnit4ClassRunner.class) @RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class AccessorySheetViewTest { public class AccessorySheetViewTest {
private AccessorySheetModel mModel; private PropertyModel mModel;
private BlockingQueue<ViewPager> mViewPager; private BlockingQueue<ViewPager> mViewPager;
@Rule @Rule
...@@ -68,13 +75,18 @@ public class AccessorySheetViewTest { ...@@ -68,13 +75,18 @@ public class AccessorySheetViewTest {
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
ViewStub viewStub = mActivityTestRule.getActivity().findViewById( ViewStub viewStub = mActivityTestRule.getActivity().findViewById(
R.id.keyboard_accessory_sheet_stub); R.id.keyboard_accessory_sheet_stub);
mModel = new AccessorySheetModel(); int height = mActivityTestRule.getActivity().getResources().getDimensionPixelSize(
mModel.setHeight(mActivityTestRule.getActivity().getResources().getDimensionPixelSize( R.dimen.keyboard_accessory_sheet_height);
org.chromium.chrome.R.dimen.keyboard_accessory_sheet_height)); mModel = new PropertyModel.Builder(TABS, ACTIVE_TAB_INDEX, VISIBLE, HEIGHT)
.with(HEIGHT, height)
.with(TABS, new ListModel<>())
.with(ACTIVE_TAB_INDEX, NO_ACTIVE_TAB)
.with(VISIBLE, false)
.build();
ViewProvider<ViewPager> provider = new DeferredViewStubInflationProvider<>(viewStub); ViewProvider<ViewPager> provider = new DeferredViewStubInflationProvider<>(viewStub);
mViewPager = new ArrayBlockingQueue<>(1); mViewPager = new ArrayBlockingQueue<>(1);
new LazyConstructionPropertyMcp<>(mModel, AccessorySheetModel.PropertyKey.VISIBLE, LazyConstructionPropertyMcp.create(
AccessorySheetModel::isVisible, provider, AccessorySheetViewBinder::bind); mModel, VISIBLE, provider, AccessorySheetViewBinder::bind);
provider.whenLoaded(mViewPager::add); provider.whenLoaded(mViewPager::add);
}); });
} }
...@@ -87,12 +99,12 @@ public class AccessorySheetViewTest { ...@@ -87,12 +99,12 @@ public class AccessorySheetViewTest {
assertNull(mViewPager.poll()); assertNull(mViewPager.poll());
// After setting the visibility to true, the view should exist and be visible. // After setting the visibility to true, the view should exist and be visible.
ThreadUtils.runOnUiThreadBlocking(() -> { mModel.setVisible(true); }); ThreadUtils.runOnUiThreadBlocking(() -> { mModel.set(VISIBLE, true); });
ViewPager viewPager = mViewPager.take(); ViewPager viewPager = mViewPager.take();
assertEquals(viewPager.getVisibility(), View.VISIBLE); assertEquals(viewPager.getVisibility(), View.VISIBLE);
// After hiding the view, the view should still exist but be invisible. // After hiding the view, the view should still exist but be invisible.
ThreadUtils.runOnUiThreadBlocking(() -> { mModel.setVisible(false); }); ThreadUtils.runOnUiThreadBlocking(() -> { mModel.set(VISIBLE, false); });
assertNotEquals(viewPager.getVisibility(), View.VISIBLE); assertNotEquals(viewPager.getVisibility(), View.VISIBLE);
} }
...@@ -100,7 +112,7 @@ public class AccessorySheetViewTest { ...@@ -100,7 +112,7 @@ public class AccessorySheetViewTest {
@MediumTest @MediumTest
public void testAddingTabToModelRendersTabsView() throws InterruptedException { public void testAddingTabToModelRendersTabsView() throws InterruptedException {
final String kSampleAction = "Some Action"; final String kSampleAction = "Some Action";
mModel.getTabList().add(new Tab(null, null, R.layout.empty_accessory_sheet, mModel.get(TABS).add(new Tab(null, null, R.layout.empty_accessory_sheet,
AccessoryTabType.PASSWORDS, new Tab.Listener() { AccessoryTabType.PASSWORDS, new Tab.Listener() {
@Override @Override
public void onTabCreated(ViewGroup view) { public void onTabCreated(ViewGroup view) {
...@@ -115,12 +127,12 @@ public class AccessorySheetViewTest { ...@@ -115,12 +127,12 @@ public class AccessorySheetViewTest {
@Override @Override
public void onTabShown() {} public void onTabShown() {}
})); }));
mModel.setActiveTabIndex(0); mModel.set(ACTIVE_TAB_INDEX, 0);
// Shouldn't cause the view to be inflated. // Shouldn't cause the view to be inflated.
assertNull(mViewPager.poll()); assertNull(mViewPager.poll());
// Setting visibility should cause the Tab to be rendered. // Setting visibility should cause the Tab to be rendered.
ThreadUtils.runOnUiThreadBlocking(() -> mModel.setVisible(true)); ThreadUtils.runOnUiThreadBlocking(() -> mModel.set(VISIBLE, true));
assertNotNull(mViewPager.take()); assertNotNull(mViewPager.take());
onView(withText(kSampleAction)).check(matches(isDisplayed())); onView(withText(kSampleAction)).check(matches(isDisplayed()));
...@@ -131,14 +143,14 @@ public class AccessorySheetViewTest { ...@@ -131,14 +143,14 @@ public class AccessorySheetViewTest {
public void testSettingActiveTabIndexChangesTab() { public void testSettingActiveTabIndexChangesTab() {
final String kFirstTab = "First Tab"; final String kFirstTab = "First Tab";
final String kSecondTab = "Second Tab"; final String kSecondTab = "Second Tab";
mModel.getTabList().add(createTestTabWithTextView(kFirstTab)); mModel.get(TABS).add(createTestTabWithTextView(kFirstTab));
mModel.getTabList().add(createTestTabWithTextView(kSecondTab)); mModel.get(TABS).add(createTestTabWithTextView(kSecondTab));
mModel.setActiveTabIndex(0); mModel.set(ACTIVE_TAB_INDEX, 0);
ThreadUtils.runOnUiThreadBlocking(() -> mModel.setVisible(true)); // Render view. ThreadUtils.runOnUiThreadBlocking(() -> mModel.set(VISIBLE, true)); // Render view.
onView(withText(kFirstTab)).check(matches(isDisplayed())); onView(withText(kFirstTab)).check(matches(isDisplayed()));
ThreadUtils.runOnUiThreadBlocking(() -> mModel.setActiveTabIndex(1)); ThreadUtils.runOnUiThreadBlocking(() -> mModel.set(ACTIVE_TAB_INDEX, 1));
onView(isRoot()).check((r, e) -> waitForView((ViewGroup) r, withText(kSecondTab))); onView(isRoot()).check((r, e) -> waitForView((ViewGroup) r, withText(kSecondTab)));
} }
...@@ -148,15 +160,14 @@ public class AccessorySheetViewTest { ...@@ -148,15 +160,14 @@ public class AccessorySheetViewTest {
public void testRemovingTabDeletesItsView() { public void testRemovingTabDeletesItsView() {
final String kFirstTab = "First Tab"; final String kFirstTab = "First Tab";
final String kSecondTab = "Second Tab"; final String kSecondTab = "Second Tab";
mModel.getTabList().add(createTestTabWithTextView(kFirstTab)); mModel.get(TABS).add(createTestTabWithTextView(kFirstTab));
mModel.getTabList().add(createTestTabWithTextView(kSecondTab)); mModel.get(TABS).add(createTestTabWithTextView(kSecondTab));
mModel.setActiveTabIndex(0); mModel.set(ACTIVE_TAB_INDEX, 0);
ThreadUtils.runOnUiThreadBlocking(() -> mModel.setVisible(true)); // Render view. ThreadUtils.runOnUiThreadBlocking(() -> mModel.set(VISIBLE, true)); // Render view.
onView(withText(kFirstTab)).check(matches(isDisplayed())); onView(withText(kFirstTab)).check(matches(isDisplayed()));
ThreadUtils.runOnUiThreadBlocking( ThreadUtils.runOnUiThreadBlocking(() -> mModel.get(TABS).remove(mModel.get(TABS).get(0)));
() -> mModel.getTabList().remove(mModel.getTabList().get(0)));
onView(withText(kFirstTab)).check(doesNotExist()); onView(withText(kFirstTab)).check(doesNotExist());
} }
...@@ -165,21 +176,21 @@ public class AccessorySheetViewTest { ...@@ -165,21 +176,21 @@ public class AccessorySheetViewTest {
@MediumTest @MediumTest
public void testReplaceLastTab() { public void testReplaceLastTab() {
final String kFirstTab = "First Tab"; final String kFirstTab = "First Tab";
mModel.getTabList().add(createTestTabWithTextView(kFirstTab)); mModel.get(TABS).add(createTestTabWithTextView(kFirstTab));
mModel.setActiveTabIndex(0); mModel.set(ACTIVE_TAB_INDEX, 0);
ThreadUtils.runOnUiThreadBlocking(() -> mModel.setVisible(true)); // Render view. ThreadUtils.runOnUiThreadBlocking(() -> mModel.set(VISIBLE, true)); // Render view.
// Remove the last tab. // Remove the last tab.
onView(withText(kFirstTab)).check(matches(isDisplayed())); onView(withText(kFirstTab)).check(matches(isDisplayed()));
ThreadUtils.runOnUiThreadBlocking( ThreadUtils.runOnUiThreadBlocking(
() -> { mModel.getTabList().remove(mModel.getTabList().get(0)); }); () -> { mModel.get(TABS).remove(mModel.get(TABS).get(0)); });
onView(withText(kFirstTab)).check(doesNotExist()); onView(withText(kFirstTab)).check(doesNotExist());
// Add a new first tab. // Add a new first tab.
final String kSecondTab = "Second Tab"; final String kSecondTab = "Second Tab";
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
mModel.getTabList().add(createTestTabWithTextView(kSecondTab)); mModel.get(TABS).add(createTestTabWithTextView(kSecondTab));
mModel.setActiveTabIndex(0); mModel.set(ACTIVE_TAB_INDEX, 0);
}); });
onView(isRoot()).check((r, e) -> waitForView((ViewGroup) r, withText(kSecondTab))); onView(isRoot()).check((r, e) -> waitForView((ViewGroup) r, withText(kSecondTab)));
} }
......
...@@ -23,6 +23,12 @@ import static org.junit.Assert.assertTrue; ...@@ -23,6 +23,12 @@ import static org.junit.Assert.assertTrue;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessoryAction.AUTOFILL_SUGGESTION; import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessoryAction.AUTOFILL_SUGGESTION;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessoryAction.GENERATE_PASSWORD_AUTOMATIC; import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessoryAction.GENERATE_PASSWORD_AUTOMATIC;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.ACTIONS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.ACTIVE_TAB;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.BOTTOM_OFFSET_PX;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.TABS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.TAB_SELECTION_CALLBACKS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryProperties.VISIBLE;
import static org.chromium.chrome.test.util.ViewUtils.VIEW_GONE; import static org.chromium.chrome.test.util.ViewUtils.VIEW_GONE;
import static org.chromium.chrome.test.util.ViewUtils.VIEW_INVISIBLE; import static org.chromium.chrome.test.util.ViewUtils.VIEW_INVISIBLE;
import static org.chromium.chrome.test.util.ViewUtils.VIEW_NULL; import static org.chromium.chrome.test.util.ViewUtils.VIEW_NULL;
...@@ -46,6 +52,8 @@ import org.chromium.chrome.R; ...@@ -46,6 +52,8 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches; import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeTabbedActivity; import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.modelutil.LazyConstructionPropertyMcp; import org.chromium.chrome.browser.modelutil.LazyConstructionPropertyMcp;
import org.chromium.chrome.browser.modelutil.ListModel;
import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.chrome.test.ChromeActivityTestRule; import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner; import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.ui.DeferredViewStubInflationProvider; import org.chromium.ui.DeferredViewStubInflationProvider;
...@@ -63,7 +71,7 @@ import java.util.concurrent.atomic.AtomicReference; ...@@ -63,7 +71,7 @@ import java.util.concurrent.atomic.AtomicReference;
@RunWith(ChromeJUnit4ClassRunner.class) @RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE}) @CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class KeyboardAccessoryViewTest { public class KeyboardAccessoryViewTest {
private KeyboardAccessoryModel mModel; private PropertyModel mModel;
private BlockingQueue<KeyboardAccessoryView> mKeyboardAccessoryView; private BlockingQueue<KeyboardAccessoryView> mKeyboardAccessoryView;
@Rule @Rule
...@@ -95,16 +103,21 @@ public class KeyboardAccessoryViewTest { ...@@ -95,16 +103,21 @@ public class KeyboardAccessoryViewTest {
public void setUp() throws InterruptedException { public void setUp() throws InterruptedException {
mActivityTestRule.startMainActivityOnBlankPage(); mActivityTestRule.startMainActivityOnBlankPage();
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
mModel = new KeyboardAccessoryModel(); mModel = new PropertyModel
.Builder(ACTIONS, TABS, VISIBLE, BOTTOM_OFFSET_PX, ACTIVE_TAB,
TAB_SELECTION_CALLBACKS)
.with(TABS, new ListModel<>())
.with(ACTIONS, new ListModel<>())
.with(VISIBLE, false)
.build();
ViewStub viewStub = ViewStub viewStub =
mActivityTestRule.getActivity().findViewById(R.id.keyboard_accessory_stub); mActivityTestRule.getActivity().findViewById(R.id.keyboard_accessory_stub);
mKeyboardAccessoryView = new ArrayBlockingQueue<>(1); mKeyboardAccessoryView = new ArrayBlockingQueue<>(1);
ViewProvider<KeyboardAccessoryView> provider = ViewProvider<KeyboardAccessoryView> provider =
new DeferredViewStubInflationProvider<>(viewStub); new DeferredViewStubInflationProvider<>(viewStub);
new LazyConstructionPropertyMcp<>(mModel, LazyConstructionPropertyMcp.create(
KeyboardAccessoryModel.PropertyKey.VISIBLE, KeyboardAccessoryModel::isVisible, mModel, VISIBLE, provider, KeyboardAccessoryViewBinder::bind);
provider, KeyboardAccessoryViewBinder::bind);
provider.whenLoaded(mKeyboardAccessoryView::add); provider.whenLoaded(mKeyboardAccessoryView::add);
}); });
} }
...@@ -117,12 +130,12 @@ public class KeyboardAccessoryViewTest { ...@@ -117,12 +130,12 @@ public class KeyboardAccessoryViewTest {
assertNull(mKeyboardAccessoryView.poll()); assertNull(mKeyboardAccessoryView.poll());
// After setting the visibility to true, the view should exist and be visible. // After setting the visibility to true, the view should exist and be visible.
ThreadUtils.runOnUiThreadBlocking(() -> { mModel.setVisible(true); }); ThreadUtils.runOnUiThreadBlocking(() -> { mModel.set(VISIBLE, true); });
KeyboardAccessoryView view = mKeyboardAccessoryView.take(); KeyboardAccessoryView view = mKeyboardAccessoryView.take();
assertEquals(view.getVisibility(), View.VISIBLE); assertEquals(view.getVisibility(), View.VISIBLE);
// After hiding the view, the view should still exist but be invisible. // After hiding the view, the view should still exist but be invisible.
ThreadUtils.runOnUiThreadBlocking(() -> { mModel.setVisible(false); }); ThreadUtils.runOnUiThreadBlocking(() -> { mModel.set(VISIBLE, false); });
assertNotEquals(view.getVisibility(), View.VISIBLE); assertNotEquals(view.getVisibility(), View.VISIBLE);
} }
...@@ -134,8 +147,8 @@ public class KeyboardAccessoryViewTest { ...@@ -134,8 +147,8 @@ public class KeyboardAccessoryViewTest {
"Test Button", GENERATE_PASSWORD_AUTOMATIC, action -> buttonClicked.set(true)); "Test Button", GENERATE_PASSWORD_AUTOMATIC, action -> buttonClicked.set(true));
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
mModel.setVisible(true); mModel.set(VISIBLE, true);
mModel.getActionList().add(testAction); mModel.get(ACTIONS).add(testAction);
}); });
onView(isRoot()).check((root, e) -> waitForView((ViewGroup) root, withText("Test Button"))); onView(isRoot()).check((root, e) -> waitForView((ViewGroup) root, withText("Test Button")));
...@@ -148,8 +161,8 @@ public class KeyboardAccessoryViewTest { ...@@ -148,8 +161,8 @@ public class KeyboardAccessoryViewTest {
@MediumTest @MediumTest
public void testCanAddSingleButtons() { public void testCanAddSingleButtons() {
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
mModel.setVisible(true); mModel.set(VISIBLE, true);
mModel.getActionList().set(new KeyboardAccessoryData.Action[] { mModel.get(ACTIONS).set(new KeyboardAccessoryData.Action[] {
new KeyboardAccessoryData.Action( new KeyboardAccessoryData.Action(
"First", GENERATE_PASSWORD_AUTOMATIC, action -> {}), "First", GENERATE_PASSWORD_AUTOMATIC, action -> {}),
new KeyboardAccessoryData.Action("Second", AUTOFILL_SUGGESTION, action -> {})}); new KeyboardAccessoryData.Action("Second", AUTOFILL_SUGGESTION, action -> {})});
...@@ -161,7 +174,7 @@ public class KeyboardAccessoryViewTest { ...@@ -161,7 +174,7 @@ public class KeyboardAccessoryViewTest {
ThreadUtils.runOnUiThreadBlocking( ThreadUtils.runOnUiThreadBlocking(
() ()
-> mModel.getActionList().add(new KeyboardAccessoryData.Action( -> mModel.get(ACTIONS).add(new KeyboardAccessoryData.Action(
"Third", GENERATE_PASSWORD_AUTOMATIC, action -> {}))); "Third", GENERATE_PASSWORD_AUTOMATIC, action -> {})));
onView(isRoot()).check((root, e) -> waitForView((ViewGroup) root, withText("Third"))); onView(isRoot()).check((root, e) -> waitForView((ViewGroup) root, withText("Third")));
...@@ -174,8 +187,8 @@ public class KeyboardAccessoryViewTest { ...@@ -174,8 +187,8 @@ public class KeyboardAccessoryViewTest {
@MediumTest @MediumTest
public void testCanRemoveSingleButtons() { public void testCanRemoveSingleButtons() {
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
mModel.setVisible(true); mModel.set(VISIBLE, true);
mModel.getActionList().set(new KeyboardAccessoryData.Action[] { mModel.get(ACTIONS).set(new KeyboardAccessoryData.Action[] {
new KeyboardAccessoryData.Action( new KeyboardAccessoryData.Action(
"First", GENERATE_PASSWORD_AUTOMATIC, action -> {}), "First", GENERATE_PASSWORD_AUTOMATIC, action -> {}),
new KeyboardAccessoryData.Action( new KeyboardAccessoryData.Action(
...@@ -190,7 +203,7 @@ public class KeyboardAccessoryViewTest { ...@@ -190,7 +203,7 @@ public class KeyboardAccessoryViewTest {
onView(withText("Third")).check(matches(isDisplayed())); onView(withText("Third")).check(matches(isDisplayed()));
ThreadUtils.runOnUiThreadBlocking( ThreadUtils.runOnUiThreadBlocking(
() -> mModel.getActionList().remove(mModel.getActionList().get(1))); () -> mModel.get(ACTIONS).remove(mModel.get(ACTIONS).get(1)));
onView(isRoot()).check((root, e) onView(isRoot()).check((root, e)
-> waitForView((ViewGroup) root, withText("Second"), -> waitForView((ViewGroup) root, withText("Second"),
...@@ -204,8 +217,8 @@ public class KeyboardAccessoryViewTest { ...@@ -204,8 +217,8 @@ public class KeyboardAccessoryViewTest {
@MediumTest @MediumTest
public void testRemovesTabs() { public void testRemovesTabs() {
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
mModel.setVisible(true); mModel.set(VISIBLE, true);
mModel.getTabList().set(new KeyboardAccessoryData.Tab[] {createTestTab("FirstTab"), mModel.get(TABS).set(new KeyboardAccessoryData.Tab[] {createTestTab("FirstTab"),
createTestTab("SecondTab"), createTestTab("ThirdTab")}); createTestTab("SecondTab"), createTestTab("ThirdTab")});
}); });
...@@ -215,8 +228,7 @@ public class KeyboardAccessoryViewTest { ...@@ -215,8 +228,7 @@ public class KeyboardAccessoryViewTest {
onView(isTabWithDescription("SecondTab")).check(matches(isDisplayed())); onView(isTabWithDescription("SecondTab")).check(matches(isDisplayed()));
onView(isTabWithDescription("ThirdTab")).check(matches(isDisplayed())); onView(isTabWithDescription("ThirdTab")).check(matches(isDisplayed()));
ThreadUtils.runOnUiThreadBlocking( ThreadUtils.runOnUiThreadBlocking(() -> mModel.get(TABS).remove(mModel.get(TABS).get(1)));
() -> mModel.getTabList().remove(mModel.getTabList().get(1)));
onView(isRoot()).check( onView(isRoot()).check(
(root, e) (root, e)
...@@ -231,8 +243,8 @@ public class KeyboardAccessoryViewTest { ...@@ -231,8 +243,8 @@ public class KeyboardAccessoryViewTest {
@MediumTest @MediumTest
public void testAddsTabs() { public void testAddsTabs() {
ThreadUtils.runOnUiThreadBlocking(() -> { ThreadUtils.runOnUiThreadBlocking(() -> {
mModel.setVisible(true); mModel.set(VISIBLE, true);
mModel.getTabList().set(new KeyboardAccessoryData.Tab[] { mModel.get(TABS).set(new KeyboardAccessoryData.Tab[] {
createTestTab("FirstTab"), createTestTab("SecondTab")}); createTestTab("FirstTab"), createTestTab("SecondTab")});
}); });
...@@ -242,7 +254,7 @@ public class KeyboardAccessoryViewTest { ...@@ -242,7 +254,7 @@ public class KeyboardAccessoryViewTest {
onView(isTabWithDescription("SecondTab")).check(matches(isDisplayed())); onView(isTabWithDescription("SecondTab")).check(matches(isDisplayed()));
onView(isTabWithDescription("ThirdTab")).check(doesNotExist()); onView(isTabWithDescription("ThirdTab")).check(doesNotExist());
ThreadUtils.runOnUiThreadBlocking(() -> mModel.getTabList().add(createTestTab("ThirdTab"))); ThreadUtils.runOnUiThreadBlocking(() -> mModel.get(TABS).add(createTestTab("ThirdTab")));
onView(isRoot()).check( onView(isRoot()).check(
(root, e) -> waitForView((ViewGroup) root, isTabWithDescription("ThirdTab"))); (root, e) -> waitForView((ViewGroup) root, isTabWithDescription("ThirdTab")));
......
...@@ -12,6 +12,11 @@ import static org.mockito.Mockito.times; ...@@ -12,6 +12,11 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.ACTIVE_TAB_INDEX;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.HEIGHT;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.TABS;
import static org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetProperties.VISIBLE;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.view.ViewGroup; import android.view.ViewGroup;
...@@ -26,9 +31,10 @@ import org.chromium.base.metrics.RecordHistogram; ...@@ -26,9 +31,10 @@ import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.test.ShadowRecordHistogram; import org.chromium.base.metrics.test.ShadowRecordHistogram;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.asynctask.CustomShadowAsyncTask; import org.chromium.base.test.asynctask.CustomShadowAsyncTask;
import org.chromium.chrome.browser.autofill.keyboard_accessory.AccessorySheetModel.PropertyKey;
import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab; import org.chromium.chrome.browser.autofill.keyboard_accessory.KeyboardAccessoryData.Tab;
import org.chromium.chrome.browser.modelutil.ListObservable; import org.chromium.chrome.browser.modelutil.ListObservable;
import org.chromium.chrome.browser.modelutil.PropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel;
import org.chromium.chrome.browser.modelutil.PropertyObservable; import org.chromium.chrome.browser.modelutil.PropertyObservable;
import org.chromium.chrome.test.util.browser.modelutil.FakeViewProvider; import org.chromium.chrome.test.util.browser.modelutil.FakeViewProvider;
...@@ -52,7 +58,7 @@ public class AccessorySheetControllerTest { ...@@ -52,7 +58,7 @@ public class AccessorySheetControllerTest {
private AccessorySheetCoordinator mCoordinator; private AccessorySheetCoordinator mCoordinator;
private AccessorySheetMediator mMediator; private AccessorySheetMediator mMediator;
private AccessorySheetModel mModel; private PropertyModel mModel;
@Before @Before
public void setUp() { public void setUp() {
...@@ -78,18 +84,19 @@ public class AccessorySheetControllerTest { ...@@ -78,18 +84,19 @@ public class AccessorySheetControllerTest {
// Calling show on the mediator should make model propagate that it's visible. // Calling show on the mediator should make model propagate that it's visible.
mMediator.show(); mMediator.show();
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.VISIBLE); verify(mMockPropertyObserver).onPropertyChanged(mModel, VISIBLE);
assertThat(mModel.isVisible(), is(true)); assertThat(mModel.get(VISIBLE), is(true));
// Calling show again does nothing. // Calling show again does nothing.
mMediator.show(); mMediator.show();
verify(mMockPropertyObserver) // Still the same call and no new one added. verify(mMockPropertyObserver) // Still the same call and no new one added.
.onPropertyChanged(mModel, PropertyKey.VISIBLE); .onPropertyChanged(mModel, VISIBLE);
// Calling hide on the mediator should make model propagate that it's invisible. // Calling hide on the mediator should make model propagate that it's invisible.
mMediator.hide(); mMediator.hide();
verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, PropertyKey.VISIBLE); verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, VISIBLE);
assertThat(mModel.isVisible(), is(false));
assertThat(mModel.get(VISIBLE), is(false));
} }
@Test @Test
...@@ -98,28 +105,29 @@ public class AccessorySheetControllerTest { ...@@ -98,28 +105,29 @@ public class AccessorySheetControllerTest {
// Setting height triggers the observer and changes the model. // Setting height triggers the observer and changes the model.
mCoordinator.setHeight(123); mCoordinator.setHeight(123);
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.HEIGHT); verify(mMockPropertyObserver).onPropertyChanged(mModel, HEIGHT);
assertThat(mModel.getHeight(), is(123)); assertThat(mModel.get(HEIGHT), is(123));
// Setting the same height doesn't trigger anything. // Setting the same height doesn't trigger anything.
mCoordinator.setHeight(123); mCoordinator.setHeight(123);
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.HEIGHT); // No 2nd call. verify(mMockPropertyObserver).onPropertyChanged(mModel, HEIGHT); // No 2nd call.
// Setting a different height triggers again. // Setting a different height triggers again.
mCoordinator.setHeight(234); mCoordinator.setHeight(234);
verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, PropertyKey.HEIGHT); verify(mMockPropertyObserver, times(2)).onPropertyChanged(mModel, HEIGHT);
assertThat(mModel.getHeight(), is(234));
assertThat(mModel.get(HEIGHT), is(234));
} }
@Test @Test
public void testModelNotifiesChangesForNewSheet() { public void testModelNotifiesChangesForNewSheet() {
mModel.addObserver(mMockPropertyObserver); mModel.addObserver(mMockPropertyObserver);
mModel.getTabList().addObserver(mTabListObserver); mModel.get(TABS).addObserver(mTabListObserver);
assertThat(mModel.getTabList().size(), is(0)); assertThat(mModel.get(TABS).size(), is(0));
mCoordinator.addTab(mTabs[0]); mCoordinator.addTab(mTabs[0]);
verify(mTabListObserver).onItemRangeInserted(mModel.getTabList(), 0, 1); verify(mTabListObserver).onItemRangeInserted(mModel.get(TABS), 0, 1);
assertThat(mModel.getTabList().size(), is(1)); assertThat(mModel.get(TABS).size(), is(1));
} }
@Test @Test
...@@ -127,21 +135,21 @@ public class AccessorySheetControllerTest { ...@@ -127,21 +135,21 @@ public class AccessorySheetControllerTest {
mModel.addObserver(mMockPropertyObserver); mModel.addObserver(mMockPropertyObserver);
// Initially, there is no active Tab. // Initially, there is no active Tab.
assertThat(mModel.getTabList().size(), is(0)); assertThat(mModel.get(TABS).size(), is(0));
assertThat(mCoordinator.getTab(), is(nullValue())); assertThat(mCoordinator.getTab(), is(nullValue()));
// The first tab becomes the active Tab. // The first tab becomes the active Tab.
mCoordinator.addTab(mTabs[0]); mCoordinator.addTab(mTabs[0]);
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.ACTIVE_TAB_INDEX); verify(mMockPropertyObserver).onPropertyChanged(mModel, ACTIVE_TAB_INDEX);
assertThat(mModel.getTabList().size(), is(1)); assertThat(mModel.get(TABS).size(), is(1));
assertThat(mModel.getActiveTabIndex(), is(0)); assertThat(mModel.get(ACTIVE_TAB_INDEX), is(0));
assertThat(mCoordinator.getTab(), is(mTabs[0])); assertThat(mCoordinator.getTab(), is(mTabs[0]));
// A second tab is added but doesn't become automatically active. // A second tab is added but doesn't become automatically active.
mCoordinator.addTab(mTabs[1]); mCoordinator.addTab(mTabs[1]);
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.ACTIVE_TAB_INDEX); verify(mMockPropertyObserver).onPropertyChanged(mModel, ACTIVE_TAB_INDEX);
assertThat(mModel.getTabList().size(), is(2)); assertThat(mModel.get(TABS).size(), is(2));
assertThat(mModel.getActiveTabIndex(), is(0)); assertThat(mModel.get(ACTIVE_TAB_INDEX), is(0));
} }
@Test @Test
...@@ -150,13 +158,13 @@ public class AccessorySheetControllerTest { ...@@ -150,13 +158,13 @@ public class AccessorySheetControllerTest {
mCoordinator.addTab(mTabs[1]); mCoordinator.addTab(mTabs[1]);
mCoordinator.addTab(mTabs[2]); mCoordinator.addTab(mTabs[2]);
mCoordinator.addTab(mTabs[3]); mCoordinator.addTab(mTabs[3]);
assertThat(mModel.getTabList().size(), is(4)); assertThat(mModel.get(TABS).size(), is(4));
assertThat(mModel.getActiveTabIndex(), is(0)); assertThat(mModel.get(ACTIVE_TAB_INDEX), is(0));
mCoordinator.removeTab(mTabs[0]); mCoordinator.removeTab(mTabs[0]);
assertThat(mModel.getTabList().size(), is(3)); assertThat(mModel.get(TABS).size(), is(3));
assertThat(mModel.getActiveTabIndex(), is(0)); assertThat(mModel.get(ACTIVE_TAB_INDEX), is(0));
} }
@Test @Test
...@@ -164,8 +172,8 @@ public class AccessorySheetControllerTest { ...@@ -164,8 +172,8 @@ public class AccessorySheetControllerTest {
mCoordinator.addTab(mTabs[0]); mCoordinator.addTab(mTabs[0]);
mCoordinator.removeTab(mTabs[0]); mCoordinator.removeTab(mTabs[0]);
assertThat(mModel.getTabList().size(), is(0)); assertThat(mModel.get(TABS).size(), is(0));
assertThat(mModel.getActiveTabIndex(), is(AccessorySheetModel.NO_ACTIVE_TAB)); assertThat(mModel.get(ACTIVE_TAB_INDEX), is(AccessorySheetProperties.NO_ACTIVE_TAB));
} }
@Test @Test
...@@ -174,14 +182,14 @@ public class AccessorySheetControllerTest { ...@@ -174,14 +182,14 @@ public class AccessorySheetControllerTest {
mCoordinator.addTab(mTabs[1]); mCoordinator.addTab(mTabs[1]);
mCoordinator.addTab(mTabs[2]); mCoordinator.addTab(mTabs[2]);
mCoordinator.addTab(mTabs[3]); mCoordinator.addTab(mTabs[3]);
mModel.setActiveTabIndex(2); mModel.set(ACTIVE_TAB_INDEX, 2);
mModel.addObserver(mMockPropertyObserver); mModel.addObserver(mMockPropertyObserver);
mCoordinator.removeTab(mTabs[2]); mCoordinator.removeTab(mTabs[2]);
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.ACTIVE_TAB_INDEX); verify(mMockPropertyObserver).onPropertyChanged(mModel, ACTIVE_TAB_INDEX);
assertThat(mModel.getTabList().size(), is(3)); assertThat(mModel.get(TABS).size(), is(3));
assertThat(mModel.getActiveTabIndex(), is(1)); assertThat(mModel.get(ACTIVE_TAB_INDEX), is(1));
} }
@Test @Test
...@@ -190,14 +198,14 @@ public class AccessorySheetControllerTest { ...@@ -190,14 +198,14 @@ public class AccessorySheetControllerTest {
mCoordinator.addTab(mTabs[1]); mCoordinator.addTab(mTabs[1]);
mCoordinator.addTab(mTabs[2]); mCoordinator.addTab(mTabs[2]);
mCoordinator.addTab(mTabs[3]); mCoordinator.addTab(mTabs[3]);
mModel.setActiveTabIndex(2); mModel.set(ACTIVE_TAB_INDEX, 2);
mModel.addObserver(mMockPropertyObserver); mModel.addObserver(mMockPropertyObserver);
mCoordinator.removeTab(mTabs[1]); mCoordinator.removeTab(mTabs[1]);
verify(mMockPropertyObserver).onPropertyChanged(mModel, PropertyKey.ACTIVE_TAB_INDEX); verify(mMockPropertyObserver).onPropertyChanged(mModel, ACTIVE_TAB_INDEX);
assertThat(mModel.getTabList().size(), is(3)); assertThat(mModel.get(TABS).size(), is(3));
assertThat(mModel.getActiveTabIndex(), is(1)); assertThat(mModel.get(ACTIVE_TAB_INDEX), is(1));
} }
@Test @Test
...@@ -206,12 +214,12 @@ public class AccessorySheetControllerTest { ...@@ -206,12 +214,12 @@ public class AccessorySheetControllerTest {
mCoordinator.addTab(mTabs[1]); mCoordinator.addTab(mTabs[1]);
mCoordinator.addTab(mTabs[2]); mCoordinator.addTab(mTabs[2]);
mCoordinator.addTab(mTabs[3]); mCoordinator.addTab(mTabs[3]);
mModel.setActiveTabIndex(2); mModel.set(ACTIVE_TAB_INDEX, 2);
mCoordinator.removeTab(mTabs[3]); mCoordinator.removeTab(mTabs[3]);
assertThat(mModel.getTabList().size(), is(3)); assertThat(mModel.get(TABS).size(), is(3));
assertThat(mModel.getActiveTabIndex(), is(2)); assertThat(mModel.get(ACTIVE_TAB_INDEX), is(2));
} }
@Test @Test
......
...@@ -27,9 +27,9 @@ import org.robolectric.annotation.Config; ...@@ -27,9 +27,9 @@ import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowLooper;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.modelutil.PropertyModel.BooleanPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.IntPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableIntPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.ObjectPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableObjectPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor.ViewBinder; import org.chromium.chrome.browser.modelutil.PropertyModelChangeProcessor.ViewBinder;
import org.chromium.chrome.test.util.browser.modelutil.FakeViewProvider; import org.chromium.chrome.test.util.browser.modelutil.FakeViewProvider;
...@@ -39,9 +39,10 @@ import org.chromium.chrome.test.util.browser.modelutil.FakeViewProvider; ...@@ -39,9 +39,10 @@ import org.chromium.chrome.test.util.browser.modelutil.FakeViewProvider;
@RunWith(BaseRobolectricTestRunner.class) @RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE) @Config(manifest = Config.NONE)
public class LazyConstructionPropertyMcpTest { public class LazyConstructionPropertyMcpTest {
private static final BooleanPropertyKey VISIBILITY = new BooleanPropertyKey(); private static final WritableBooleanPropertyKey VISIBILITY = new WritableBooleanPropertyKey();
private static final ObjectPropertyKey<String> STRING_PROPERTY = new ObjectPropertyKey<>(); private static final WritableObjectPropertyKey<String> STRING_PROPERTY =
private static final IntPropertyKey INT_PROPERTY = new IntPropertyKey(); new WritableObjectPropertyKey<>();
private static final WritableIntPropertyKey INT_PROPERTY = new WritableIntPropertyKey();
private static final PropertyKey[] ALL_PROPERTIES = private static final PropertyKey[] ALL_PROPERTIES =
new PropertyKey[] {VISIBILITY, STRING_PROPERTY, INT_PROPERTY}; new PropertyKey[] {VISIBILITY, STRING_PROPERTY, INT_PROPERTY};
private PropertyModel mModel; private PropertyModel mModel;
......
...@@ -17,10 +17,10 @@ import org.mockito.Mockito; ...@@ -17,10 +17,10 @@ import org.mockito.Mockito;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.modelutil.PropertyModel.BooleanPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.FloatPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableFloatPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.IntPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableIntPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyModel.ObjectPropertyKey; import org.chromium.chrome.browser.modelutil.PropertyModel.WritableObjectPropertyKey;
import org.chromium.chrome.browser.modelutil.PropertyObservable.PropertyObserver; import org.chromium.chrome.browser.modelutil.PropertyObservable.PropertyObserver;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -36,21 +36,24 @@ public class PropertyModelTest { ...@@ -36,21 +36,24 @@ public class PropertyModelTest {
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
public static BooleanPropertyKey BOOLEAN_PROPERTY_A = new BooleanPropertyKey(); public static WritableBooleanPropertyKey BOOLEAN_PROPERTY_A = new WritableBooleanPropertyKey();
public static BooleanPropertyKey BOOLEAN_PROPERTY_B = new BooleanPropertyKey(); public static WritableBooleanPropertyKey BOOLEAN_PROPERTY_B = new WritableBooleanPropertyKey();
public static BooleanPropertyKey BOOLEAN_PROPERTY_C = new BooleanPropertyKey(); public static WritableBooleanPropertyKey BOOLEAN_PROPERTY_C = new WritableBooleanPropertyKey();
public static FloatPropertyKey FLOAT_PROPERTY_A = new FloatPropertyKey(); public static WritableFloatPropertyKey FLOAT_PROPERTY_A = new WritableFloatPropertyKey();
public static FloatPropertyKey FLOAT_PROPERTY_B = new FloatPropertyKey(); public static WritableFloatPropertyKey FLOAT_PROPERTY_B = new WritableFloatPropertyKey();
public static FloatPropertyKey FLOAT_PROPERTY_C = new FloatPropertyKey(); public static WritableFloatPropertyKey FLOAT_PROPERTY_C = new WritableFloatPropertyKey();
public static IntPropertyKey INT_PROPERTY_A = new IntPropertyKey(); public static WritableIntPropertyKey INT_PROPERTY_A = new WritableIntPropertyKey();
public static IntPropertyKey INT_PROPERTY_B = new IntPropertyKey(); public static WritableIntPropertyKey INT_PROPERTY_B = new WritableIntPropertyKey();
public static IntPropertyKey INT_PROPERTY_C = new IntPropertyKey(); public static WritableIntPropertyKey INT_PROPERTY_C = new WritableIntPropertyKey();
public static ObjectPropertyKey<Object> OBJECT_PROPERTY_A = new ObjectPropertyKey<>(); public static WritableObjectPropertyKey<Object> OBJECT_PROPERTY_A =
public static ObjectPropertyKey<String> OBJECT_PROPERTY_B = new ObjectPropertyKey<>(); new WritableObjectPropertyKey<>();
public static ObjectPropertyKey<List<Integer>> OBJECT_PROPERTY_C = new ObjectPropertyKey<>(); public static WritableObjectPropertyKey<String> OBJECT_PROPERTY_B =
new WritableObjectPropertyKey<>();
public static WritableObjectPropertyKey<List<Integer>> OBJECT_PROPERTY_C =
new WritableObjectPropertyKey<>();
@Test @Test
public void getAllSetProperties() { public void getAllSetProperties() {
...@@ -73,7 +76,8 @@ public class PropertyModelTest { ...@@ -73,7 +76,8 @@ public class PropertyModelTest {
verifyBooleanUpdate(model, BOOLEAN_PROPERTY_B, false); verifyBooleanUpdate(model, BOOLEAN_PROPERTY_B, false);
} }
private void verifyBooleanUpdate(PropertyModel model, BooleanPropertyKey key, boolean value) { private void verifyBooleanUpdate(
PropertyModel model, WritableBooleanPropertyKey key, boolean value) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class); PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
model.addObserver(observer); model.addObserver(observer);
...@@ -102,7 +106,7 @@ public class PropertyModelTest { ...@@ -102,7 +106,7 @@ public class PropertyModelTest {
verifyFloatUpdate(model, FLOAT_PROPERTY_A, Float.MAX_VALUE); verifyFloatUpdate(model, FLOAT_PROPERTY_A, Float.MAX_VALUE);
} }
private void verifyFloatUpdate(PropertyModel model, FloatPropertyKey key, float value) { private void verifyFloatUpdate(PropertyModel model, WritableFloatPropertyKey key, float value) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class); PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
model.addObserver(observer); model.addObserver(observer);
...@@ -127,7 +131,7 @@ public class PropertyModelTest { ...@@ -127,7 +131,7 @@ public class PropertyModelTest {
verifyIntUpdate(model, INT_PROPERTY_A, Integer.MIN_VALUE); verifyIntUpdate(model, INT_PROPERTY_A, Integer.MIN_VALUE);
} }
private void verifyIntUpdate(PropertyModel model, IntPropertyKey key, int value) { private void verifyIntUpdate(PropertyModel model, WritableIntPropertyKey key, int value) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class); PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
model.addObserver(observer); model.addObserver(observer);
...@@ -163,7 +167,8 @@ public class PropertyModelTest { ...@@ -163,7 +167,8 @@ public class PropertyModelTest {
verifyObjectUpdate(model, OBJECT_PROPERTY_C, list); verifyObjectUpdate(model, OBJECT_PROPERTY_C, list);
} }
private <T> void verifyObjectUpdate(PropertyModel model, ObjectPropertyKey<T> key, T value) { private <T> void verifyObjectUpdate(
PropertyModel model, WritableObjectPropertyKey<T> key, T value) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class); PropertyObserver<PropertyKey> observer = Mockito.mock(PropertyObserver.class);
model.addObserver(observer); model.addObserver(observer);
......
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