Commit a2ffe5a5 authored by Pavel Yatsuk's avatar Pavel Yatsuk Committed by Chromium LUCI CQ

[Messages] Option to block save password message for a site

This CL introduces a logic to block trigering future Save Password
messages for the current site. This logic is similar to "Never button"
in Save Password infobar.

For M88 Save Password message shows a menu with a single "Never" item.
In the future we will introduce a modal dialog with other options.

BUG=1160393
R=twellington@chromium.org

Change-Id: Idecfb30827b123197a5ad5af06433e6a96dfad2d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2599118
Commit-Queue: Pavel Yatsuk <pavely@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#839045}
parent a1b49bd8
......@@ -67,7 +67,7 @@ void SavePasswordMessageDelegate::CreateMessage(
// SavePasswordMessageDelegate owns message_. Callbacks won't be called after
// the current object is destroyed.
message_ = std::make_unique<messages::MessageWrapper>(
base::BindOnce(&SavePasswordMessageDelegate::HandleActionClick,
base::BindOnce(&SavePasswordMessageDelegate::HandleSaveClick,
base::Unretained(this)),
base::BindOnce(&SavePasswordMessageDelegate::HandleDismissCallback,
base::Unretained(this)));
......@@ -95,17 +95,30 @@ void SavePasswordMessageDelegate::CreateMessage(
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON));
message_->SetIconResourceId(
ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_INFOBAR_SAVE_PASSWORD));
message_->SetSecondaryIconResourceId(
ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_AUTOFILL_SETTINGS));
message_->SetSecondaryActionText(
l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_BLOCKLIST_BUTTON));
message_->SetSecondaryActionCallback(base::BindOnce(
&SavePasswordMessageDelegate::HandleNeverClick, base::Unretained(this)));
// Recording metrics is not a part of message creation. It is included here to
// ensure metrics recording test coverage.
RecordMessageShownMetrics();
}
void SavePasswordMessageDelegate::HandleActionClick() {
void SavePasswordMessageDelegate::HandleSaveClick() {
form_to_save_->Save();
ui_dismissal_reason_ = password_manager::metrics_util::CLICKED_ACCEPT;
}
void SavePasswordMessageDelegate::HandleNeverClick() {
form_to_save_->Blocklist();
ui_dismissal_reason_ = password_manager::metrics_util::CLICKED_NEVER;
DismissSavePasswordPrompt();
}
void SavePasswordMessageDelegate::HandleDismissCallback() {
// The message is dismissed. Record metrics and cleanup state.
RecordDismissalReasonMetrics();
......
......@@ -40,8 +40,9 @@ class SavePasswordMessageDelegate {
std::unique_ptr<password_manager::PasswordFormManagerForUI> form_to_save,
bool is_saving_google_account);
// Called in response to user clicking "Save" button.
void HandleActionClick();
// Called in response to user clicking "Save" and "Never" buttons.
void HandleSaveClick();
void HandleNeverClick();
// Called when the message is dismissed.
void HandleDismissCallback();
......
......@@ -49,6 +49,7 @@ class SavePasswordMessageDelegateTest : public ChromeRenderViewHostTestHarness {
void CreateMessage(std::unique_ptr<PasswordFormManagerForUI> form_to_save,
bool is_saving_google_account);
void TriggerActionClick();
void TriggerBlocklistClick();
void TriggerMessageDismissedCallback();
messages::MessageWrapper* GetMessageWrapper();
......@@ -109,6 +110,11 @@ void SavePasswordMessageDelegateTest::TriggerActionClick() {
GetMessageWrapper()->HandleActionClick(base::android::AttachCurrentThread());
}
void SavePasswordMessageDelegateTest::TriggerBlocklistClick() {
GetMessageWrapper()->HandleSecondaryActionClick(
base::android::AttachCurrentThread());
}
void SavePasswordMessageDelegateTest::TriggerMessageDismissedCallback() {
GetMessageWrapper()->HandleDismissCallback(
base::android::AttachCurrentThread());
......@@ -158,9 +164,13 @@ TEST_F(SavePasswordMessageDelegateTest, MessagePropertyValues) {
EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_SAVE_BUTTON),
GetMessageWrapper()->GetPrimaryButtonText());
EXPECT_EQ(l10n_util::GetStringUTF16(IDS_PASSWORD_MANAGER_BLOCKLIST_BUTTON),
GetMessageWrapper()->GetSecondaryActionText());
EXPECT_EQ(
ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_INFOBAR_SAVE_PASSWORD),
GetMessageWrapper()->GetIconResourceId());
EXPECT_EQ(ResourceMapper::MapToJavaDrawableId(IDR_ANDROID_AUTOFILL_SETTINGS),
GetMessageWrapper()->GetSecondaryIconResourceId());
TriggerMessageDismissedCallback();
}
......
......@@ -7,13 +7,17 @@ package org.chromium.components.browser_ui.widget.listmenu;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableIntPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
/**
* The properties controlling the state of the list menu items. Any given list item can have either
* one start icon or one end icon but not both.
*/
public class ListMenuItemProperties {
// TODO(crbug.com/1161388): Consider passing menu item title through TITLE property instead of
// TITLE_ID.
public static final WritableIntPropertyKey TITLE_ID = new WritableIntPropertyKey();
public static final WritableObjectPropertyKey<String> TITLE = new WritableObjectPropertyKey<>();
public static final WritableIntPropertyKey START_ICON_ID = new WritableIntPropertyKey();
public static final WritableIntPropertyKey END_ICON_ID = new WritableIntPropertyKey();
public static final WritableIntPropertyKey TINT_COLOR_ID = new WritableIntPropertyKey();
......@@ -21,5 +25,5 @@ public class ListMenuItemProperties {
public static final WritableBooleanPropertyKey ENABLED = new WritableBooleanPropertyKey();
public static final PropertyKey[] ALL_KEYS = {
TITLE_ID, START_ICON_ID, END_ICON_ID, MENU_ITEM_ID, ENABLED, TINT_COLOR_ID};
TITLE_ID, TITLE, START_ICON_ID, END_ICON_ID, MENU_ITEM_ID, ENABLED, TINT_COLOR_ID};
}
......@@ -28,6 +28,8 @@ public class ListMenuItemViewBinder {
ImageView endIcon = view.findViewById(R.id.menu_item_end_icon);
if (propertyKey == ListMenuItemProperties.TITLE_ID) {
textView.setText(model.get(ListMenuItemProperties.TITLE_ID));
} else if (propertyKey == ListMenuItemProperties.TITLE) {
textView.setText(model.get(ListMenuItemProperties.TITLE));
} else if (propertyKey == ListMenuItemProperties.START_ICON_ID
|| propertyKey == ListMenuItemProperties.END_ICON_ID) {
int id = model.get((ReadableIntPropertyKey) propertyKey);
......
......@@ -114,6 +114,7 @@ android_library("javatests") {
testonly = true
sources = [
"java/src/org/chromium/components/messages/MessageBannerRenderTest.java",
"java/src/org/chromium/components/messages/MessageBannerViewTest.java",
"java/src/org/chromium/components/messages/SingleActionMessageTest.java",
]
resources_package = "org.chromium.components.messages"
......@@ -128,6 +129,7 @@ android_library("javatests") {
"//third_party/android_deps:androidx_appcompat_appcompat_resources_java",
"//third_party/android_deps:androidx_core_core_java",
"//third_party/android_deps:androidx_test_runner_java",
"//third_party/android_deps:espresso_java",
"//third_party/android_support_test_runner:rules_java",
"//third_party/android_support_test_runner:runner_java",
"//third_party/junit",
......
......@@ -53,7 +53,7 @@
</LinearLayout>
<!-- Content description is set programmatically according to secondary button icon. -->
<ImageView
<org.chromium.components.browser_ui.widget.listmenu.ListMenuButton
android:id="@+id/message_secondary_button"
android:tint="@color/default_icon_color_secondary"
android:layout_weight="0"
......@@ -63,7 +63,8 @@
android:contentDescription="@null"
android:minWidth="@dimen/message_banner_button_min_width"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:background="@null" />
<ImageView
android:id="@+id/message_divider"
......
......@@ -20,6 +20,8 @@ public class MessageBannerProperties {
new WritableObjectPropertyKey<>();
public static final WritableObjectPropertyKey<Runnable> ON_PRIMARY_ACTION =
new WritableObjectPropertyKey<>();
public static final WritableObjectPropertyKey<Runnable> ON_SECONDARY_ACTION =
new WritableObjectPropertyKey<>();
public static final WritableObjectPropertyKey<String> TITLE = new WritableObjectPropertyKey<>();
public static final WritableObjectPropertyKey<String> DESCRIPTION =
new WritableObjectPropertyKey<>();
......@@ -29,6 +31,10 @@ public class MessageBannerProperties {
// Secondary icon is shown as a button, so content description should be always set.
public static final WritableObjectPropertyKey<Drawable> SECONDARY_ICON =
new WritableObjectPropertyKey<>();
public static final WritableIntPropertyKey SECONDARY_ICON_RESOURCE_ID =
new WritableIntPropertyKey();
public static final WritableObjectPropertyKey<String> SECONDARY_ACTION_TEXT =
new WritableObjectPropertyKey<>();
public static final WritableObjectPropertyKey<String> SECONDARY_ICON_CONTENT_DESCRIPTION =
new WritableObjectPropertyKey<>();
// TODO(crbug.com/1123947): remove this since on_dismissed is not a property of the view?
......@@ -49,12 +55,13 @@ public class MessageBannerProperties {
// up references.
public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {PRIMARY_BUTTON_TEXT, PRIMARY_BUTTON_CLICK_LISTENER, TITLE,
DESCRIPTION, ICON, ICON_RESOURCE_ID, SECONDARY_ICON,
SECONDARY_ICON_CONTENT_DESCRIPTION, TRANSLATION_Y, ALPHA, ON_TOUCH_RUNNABLE,
ON_PRIMARY_ACTION};
DESCRIPTION, ICON, ICON_RESOURCE_ID, SECONDARY_ICON, SECONDARY_ICON_RESOURCE_ID,
SECONDARY_ACTION_TEXT, SECONDARY_ICON_CONTENT_DESCRIPTION, TRANSLATION_Y, ALPHA,
ON_TOUCH_RUNNABLE, ON_PRIMARY_ACTION, ON_SECONDARY_ACTION};
public static final PropertyKey[] SINGLE_ACTION_MESSAGE_KEYS =
new PropertyKey[] {PRIMARY_BUTTON_TEXT, PRIMARY_BUTTON_CLICK_LISTENER, TITLE,
DESCRIPTION, ICON, ICON_RESOURCE_ID, ON_DISMISSED, TRANSLATION_Y, ALPHA,
ON_TOUCH_RUNNABLE, ON_PRIMARY_ACTION};
DESCRIPTION, ICON, ICON_RESOURCE_ID, SECONDARY_ICON, SECONDARY_ICON_RESOURCE_ID,
SECONDARY_ACTION_TEXT, ON_DISMISSED, TRANSLATION_Y, ALPHA, ON_TOUCH_RUNNABLE,
ON_PRIMARY_ACTION, ON_SECONDARY_ACTION};
}
......@@ -18,6 +18,13 @@ import androidx.annotation.Nullable;
import org.chromium.components.browser_ui.widget.BoundedLinearLayout;
import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener;
import org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.SwipeHandler;
import org.chromium.components.browser_ui.widget.listmenu.BasicListMenu;
import org.chromium.components.browser_ui.widget.listmenu.ListMenu;
import org.chromium.components.browser_ui.widget.listmenu.ListMenuButton;
import org.chromium.components.browser_ui.widget.listmenu.ListMenuButtonDelegate;
import org.chromium.components.browser_ui.widget.listmenu.ListMenuItemProperties;
import org.chromium.ui.modelutil.MVCListAdapter;
import org.chromium.ui.modelutil.PropertyModel;
/**
* View representing the message banner.
......@@ -27,8 +34,10 @@ public class MessageBannerView extends BoundedLinearLayout {
private TextView mTitle;
private TextView mDescription;
private TextView mPrimaryButton;
private ImageView mSecondaryButton;
private ListMenuButton mSecondaryButton;
private View mDivider;
private String mSecondaryActionText;
private Runnable mSecondaryActionCallback;
private SwipeGestureListener mSwipeGestureDetector;
public MessageBannerView(Context context, @Nullable AttributeSet attrs) {
......@@ -44,6 +53,7 @@ public class MessageBannerView extends BoundedLinearLayout {
mIconView = findViewById(R.id.message_icon);
mSecondaryButton = findViewById(R.id.message_secondary_button);
mDivider = findViewById(R.id.message_divider);
mSecondaryButton.setOnClickListener((View v) -> { displayMenu(); });
}
void setTitle(String title) {
......@@ -74,6 +84,14 @@ public class MessageBannerView extends BoundedLinearLayout {
mDivider.setVisibility(VISIBLE);
}
void setSecondaryActionCallback(Runnable callback) {
mSecondaryActionCallback = callback;
}
void setSecondaryActionText(String text) {
mSecondaryActionText = text;
}
void setSecondaryIconContentDescription(String description) {
mSecondaryButton.setContentDescription(description);
}
......@@ -82,6 +100,39 @@ public class MessageBannerView extends BoundedLinearLayout {
mSwipeGestureDetector = new MessageSwipeGestureListener(getContext(), handler);
}
// TODO(pavely): For the M88 experiment we decided to display single item menu in response to
// the tap on secondary button. The code below implements this logic. Past M88 it will be
// replaced with modal dialog driven from the feature code.
void displayMenu() {
final PropertyModel menuItemPropertyModel =
new PropertyModel.Builder(ListMenuItemProperties.ALL_KEYS)
.with(ListMenuItemProperties.TITLE, mSecondaryActionText)
.with(ListMenuItemProperties.ENABLED, true)
.build();
MVCListAdapter.ModelList menuItems = new MVCListAdapter.ModelList();
menuItems.add(new MVCListAdapter.ListItem(
BasicListMenu.ListMenuItemType.MENU_ITEM, menuItemPropertyModel));
ListMenu.Delegate listMenuDelegate = (PropertyModel menuItem) -> {
assert menuItem == menuItemPropertyModel;
// There is only one menu item in the menu.
if (mSecondaryActionCallback != null) {
mSecondaryActionCallback.run();
}
};
BasicListMenu listMenu = new BasicListMenu(getContext(), menuItems, listMenuDelegate);
ListMenuButtonDelegate delegate = new ListMenuButtonDelegate() {
@Override
public ListMenu getListMenu() {
return listMenu;
}
};
mSecondaryButton.setDelegate(delegate);
mSecondaryButton.showMenu();
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
......
......@@ -8,11 +8,14 @@ import static org.chromium.components.messages.MessageBannerProperties.ALPHA;
import static org.chromium.components.messages.MessageBannerProperties.DESCRIPTION;
import static org.chromium.components.messages.MessageBannerProperties.ICON;
import static org.chromium.components.messages.MessageBannerProperties.ICON_RESOURCE_ID;
import static org.chromium.components.messages.MessageBannerProperties.ON_SECONDARY_ACTION;
import static org.chromium.components.messages.MessageBannerProperties.ON_TOUCH_RUNNABLE;
import static org.chromium.components.messages.MessageBannerProperties.PRIMARY_BUTTON_CLICK_LISTENER;
import static org.chromium.components.messages.MessageBannerProperties.PRIMARY_BUTTON_TEXT;
import static org.chromium.components.messages.MessageBannerProperties.SECONDARY_ACTION_TEXT;
import static org.chromium.components.messages.MessageBannerProperties.SECONDARY_ICON;
import static org.chromium.components.messages.MessageBannerProperties.SECONDARY_ICON_CONTENT_DESCRIPTION;
import static org.chromium.components.messages.MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID;
import static org.chromium.components.messages.MessageBannerProperties.TITLE;
import static org.chromium.components.messages.MessageBannerProperties.TRANSLATION_Y;
......@@ -44,8 +47,15 @@ public class MessageBannerViewBinder {
AppCompatResources.getDrawable(view.getContext(), model.get(ICON_RESOURCE_ID)));
} else if (propertyKey == SECONDARY_ICON) {
view.setSecondaryIcon(model.get(SECONDARY_ICON));
} else if (propertyKey == SECONDARY_ICON_RESOURCE_ID) {
view.setSecondaryIcon(AppCompatResources.getDrawable(
view.getContext(), model.get(SECONDARY_ICON_RESOURCE_ID)));
} else if (propertyKey == SECONDARY_ACTION_TEXT) {
view.setSecondaryActionText(model.get(SECONDARY_ACTION_TEXT));
} else if (propertyKey == SECONDARY_ICON_CONTENT_DESCRIPTION) {
view.setSecondaryIconContentDescription(model.get(SECONDARY_ICON_CONTENT_DESCRIPTION));
} else if (propertyKey == ON_SECONDARY_ACTION) {
view.setSecondaryActionCallback(model.get(ON_SECONDARY_ACTION));
} else if (propertyKey == ON_TOUCH_RUNNABLE) {
Runnable runnable = model.get(ON_TOUCH_RUNNABLE);
if (runnable == null) {
......
// Copyright 2020 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.components.messages;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.test.filters.MediumTest;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.chromium.base.test.BaseActivityTestRule;
import org.chromium.base.test.BaseJUnit4ClassRunner;
import org.chromium.base.test.util.Batch;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.test.util.DisableAnimationsTestRule;
import org.chromium.ui.test.util.DummyUiActivity;
/**
* Instrumentation tests for MessageBannerView.
*/
@RunWith(BaseJUnit4ClassRunner.class)
@Batch(Batch.UNIT_TESTS)
public class MessageBannerViewTest {
private static final String SECONDARY_ACTION_TEXT = "SecondaryActionText";
@ClassRule
public static DisableAnimationsTestRule sDisableAnimationsRule =
new DisableAnimationsTestRule();
@ClassRule
public static BaseActivityTestRule<DummyUiActivity> sActivityTestRule =
new BaseActivityTestRule<>(DummyUiActivity.class);
private static Activity sActivity;
private static ViewGroup sContentView;
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock
Runnable mSecondaryActionCallback;
MessageBannerView mMessageBannerView;
@BeforeClass
public static void setupSuite() {
sActivityTestRule.launchActivity(null);
TestThreadUtils.runOnUiThreadBlocking(() -> {
sActivity = sActivityTestRule.getActivity();
sContentView = new FrameLayout(sActivity);
sActivity.setContentView(sContentView);
});
}
@Before
public void setupTest() throws Exception {
TestThreadUtils.runOnUiThreadBlocking(() -> {
sContentView.removeAllViews();
mMessageBannerView = (MessageBannerView) LayoutInflater.from(sActivity).inflate(
R.layout.message_banner_view, sContentView, false);
sContentView.addView(mMessageBannerView);
});
}
/**
* Tests that clicking on secondary button opens a menu with an item with SECONDARY_ACTION_TEXT.
* Clicking on this item triggers ON_SECONDARY_ACTION callback invocation.
*/
@Test
@MediumTest
public void testSecondaryActionMenu() {
PropertyModel propertyModel =
new PropertyModel.Builder(MessageBannerProperties.SINGLE_ACTION_MESSAGE_KEYS)
.with(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID,
android.R.drawable.ic_menu_add)
.with(MessageBannerProperties.SECONDARY_ACTION_TEXT, SECONDARY_ACTION_TEXT)
.with(MessageBannerProperties.ON_SECONDARY_ACTION, mSecondaryActionCallback)
.build();
TestThreadUtils.runOnUiThreadBlocking(() -> {
PropertyModelChangeProcessor.create(
propertyModel, mMessageBannerView, MessageBannerViewBinder::bind);
});
onView(withId(R.id.message_secondary_button)).perform(click());
onView(withText(SECONDARY_ACTION_TEXT)).perform(click());
Mockito.verify(mSecondaryActionCallback).run();
}
}
......@@ -34,6 +34,8 @@ public final class MessageWrapper {
mMessageProperties =
new PropertyModel.Builder(MessageBannerProperties.SINGLE_ACTION_MESSAGE_KEYS)
.with(MessageBannerProperties.ON_PRIMARY_ACTION, this::handleActionClick)
.with(MessageBannerProperties.ON_SECONDARY_ACTION,
this::handleSecondaryActionClick)
.with(MessageBannerProperties.ON_DISMISSED, this::handleMessageDismissed)
.build();
}
......@@ -72,6 +74,16 @@ public final class MessageWrapper {
mMessageProperties.set(MessageBannerProperties.PRIMARY_BUTTON_TEXT, primaryButtonText);
}
@CalledByNative
String getSecondaryActionText() {
return mMessageProperties.get(MessageBannerProperties.SECONDARY_ACTION_TEXT);
}
@CalledByNative
void setSecondaryActionText(String secondaryActionText) {
mMessageProperties.set(MessageBannerProperties.SECONDARY_ACTION_TEXT, secondaryActionText);
}
@CalledByNative
@DrawableRes
int getIconResourceId() {
......@@ -83,6 +95,17 @@ public final class MessageWrapper {
mMessageProperties.set(MessageBannerProperties.ICON_RESOURCE_ID, resourceId);
}
@CalledByNative
@DrawableRes
int getSecondaryIconResourceId() {
return mMessageProperties.get(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID);
}
@CalledByNative
void setSecondaryIconResourceId(@DrawableRes int resourceId) {
mMessageProperties.set(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID, resourceId);
}
@CalledByNative
void clearNativePtr() {
mNativeMessageWrapper = 0;
......@@ -93,6 +116,11 @@ public final class MessageWrapper {
MessageWrapperJni.get().handleActionClick(mNativeMessageWrapper);
}
private void handleSecondaryActionClick() {
if (mNativeMessageWrapper == 0) return;
MessageWrapperJni.get().handleSecondaryActionClick(mNativeMessageWrapper);
}
private void handleMessageDismissed() {
// mNativeMessageWrapper can be null if the message was dismissed from native API.
// In this case dismiss callback should have already been called.
......@@ -103,6 +131,7 @@ public final class MessageWrapper {
@NativeMethods
interface Natives {
void handleActionClick(long nativeMessageWrapper);
void handleSecondaryActionClick(long nativeMessageWrapper);
void handleDismissCallback(long nativeMessageWrapper);
}
}
\ No newline at end of file
......@@ -62,9 +62,17 @@ public class MessageWrapperTest {
Assert.assertEquals("Button text doesn't match provided value", "Primary button",
messageProperties.get(MessageBannerProperties.PRIMARY_BUTTON_TEXT));
message.setSecondaryActionText("Primary button");
Assert.assertEquals("Button text doesn't match provided value", "Primary button",
messageProperties.get(MessageBannerProperties.SECONDARY_ACTION_TEXT));
message.setIconResourceId(1);
Assert.assertEquals("Icon resource id doesn't match provided value", 1,
messageProperties.get(MessageBannerProperties.ICON_RESOURCE_ID));
message.setSecondaryIconResourceId(2);
Assert.assertEquals("Icon resource id doesn't match provided value", 2,
messageProperties.get(MessageBannerProperties.SECONDARY_ICON_RESOURCE_ID));
}
/**
......@@ -78,6 +86,8 @@ public class MessageWrapperTest {
PropertyModel messageProperties = message.getMessageProperties();
messageProperties.get(MessageBannerProperties.ON_PRIMARY_ACTION).run();
Mockito.verify(mNativeMock).handleActionClick(nativePtr);
messageProperties.get(MessageBannerProperties.ON_SECONDARY_ACTION).run();
Mockito.verify(mNativeMock).handleSecondaryActionClick(nativePtr);
messageProperties.get(MessageBannerProperties.ON_DISMISSED).run();
Mockito.verify(mNativeMock).handleDismissCallback(nativePtr);
}
......@@ -95,6 +105,8 @@ public class MessageWrapperTest {
message.clearNativePtr();
messageProperties.get(MessageBannerProperties.ON_PRIMARY_ACTION).run();
Mockito.verify(mNativeMock, never()).handleActionClick(nativePtr);
messageProperties.get(MessageBannerProperties.ON_SECONDARY_ACTION).run();
Mockito.verify(mNativeMock, never()).handleSecondaryActionClick(nativePtr);
messageProperties.get(MessageBannerProperties.ON_DISMISSED).run();
Mockito.verify(mNativeMock, never()).handleDismissCallback(nativePtr);
}
......
......@@ -74,6 +74,24 @@ void MessageWrapper::SetPrimaryButtonText(
jprimary_button_text);
}
base::string16 MessageWrapper::GetSecondaryActionText() {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jsecondary_action_text =
Java_MessageWrapper_getSecondaryActionText(env, java_message_wrapper_);
return jsecondary_action_text.is_null()
? base::string16()
: base::android::ConvertJavaStringToUTF16(jsecondary_action_text);
}
void MessageWrapper::SetSecondaryActionText(
const base::string16& secondary_action_text) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jsecondary_action_text =
base::android::ConvertUTF16ToJavaString(env, secondary_action_text);
Java_MessageWrapper_setSecondaryActionText(env, java_message_wrapper_,
jsecondary_action_text);
}
int MessageWrapper::GetIconResourceId() {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_MessageWrapper_getIconResourceId(env, java_message_wrapper_);
......@@ -85,11 +103,32 @@ void MessageWrapper::SetIconResourceId(int resource_id) {
resource_id);
}
int MessageWrapper::GetSecondaryIconResourceId() {
JNIEnv* env = base::android::AttachCurrentThread();
return Java_MessageWrapper_getSecondaryIconResourceId(env,
java_message_wrapper_);
}
void MessageWrapper::SetSecondaryIconResourceId(int resource_id) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_MessageWrapper_setSecondaryIconResourceId(env, java_message_wrapper_,
resource_id);
}
void MessageWrapper::SetSecondaryActionCallback(base::OnceClosure callback) {
secondary_action_callback_ = std::move(callback);
}
void MessageWrapper::HandleActionClick(JNIEnv* env) {
if (!action_callback_.is_null())
std::move(action_callback_).Run();
}
void MessageWrapper::HandleSecondaryActionClick(JNIEnv* env) {
if (!secondary_action_callback_.is_null())
std::move(secondary_action_callback_).Run();
}
void MessageWrapper::HandleDismissCallback(JNIEnv* env) {
// Make sure message dismissed callback is called exactly once.
CHECK(!message_dismissed_);
......
......@@ -39,14 +39,21 @@ class MessageWrapper {
void SetDescription(const base::string16& description);
base::string16 GetPrimaryButtonText();
void SetPrimaryButtonText(const base::string16& primary_button_text);
base::string16 GetSecondaryActionText();
void SetSecondaryActionText(const base::string16& secondary_action_text);
int GetIconResourceId();
// When setting a message icon use ResourceMapper::MapToJavaDrawableId to
// translate from chromium resource_id to Android drawable resource_id.
int GetIconResourceId();
void SetIconResourceId(int resource_id);
int GetSecondaryIconResourceId();
void SetSecondaryIconResourceId(int resource_id);
void SetSecondaryActionCallback(base::OnceClosure callback);
// Following methods forward calls from java to provided callbacks.
void HandleActionClick(JNIEnv* env);
void HandleSecondaryActionClick(JNIEnv* env);
void HandleDismissCallback(JNIEnv* env);
const base::android::JavaRef<jobject>& GetJavaMessageWrapper() const;
......@@ -54,6 +61,7 @@ class MessageWrapper {
private:
base::android::ScopedJavaGlobalRef<jobject> java_message_wrapper_;
base::OnceClosure action_callback_;
base::OnceClosure secondary_action_callback_;
base::OnceClosure dismiss_callback_;
bool message_dismissed_;
};
......
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