Commit 2a38f605 authored by Pavel Yatsuk's avatar Pavel Yatsuk Committed by Commit Bot

[Messages] Introduce native interface for messages

Native interface includes MessageWrapper and MessageDispatcherBridge.
MessageWrapper represents a message for native feature code and allows
feature code to set message properties before enqueueing it. Java side
of MessageWrapper constructs PropertyModel that maintains property
values.
MessageDispatcherBridge enables feature code to interact with Java
MessageDispatcher to enqueue/dismiss message.

BUG=1123947
R=lazzzis@chromium.org

Change-Id: I28b58ed080dd63045f2e18f13e3875fabd9764e1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2472599Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarNasko Oskov <nasko@chromium.org>
Reviewed-by: default avatarFilip Gorski <fgorski@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Commit-Queue: Pavel Yatsuk <pavely@chromium.org>
Cr-Commit-Position: refs/heads/master@{#825040}
parent ccadb663
...@@ -3186,6 +3186,7 @@ static_library("browser") { ...@@ -3186,6 +3186,7 @@ static_library("browser") {
"//components/javascript_dialogs/android:jni_headers", "//components/javascript_dialogs/android:jni_headers",
"//components/language/android:language_bridge", "//components/language/android:language_bridge",
"//components/location/android:settings", "//components/location/android:settings",
"//components/messages/android",
"//components/messages/android:feature_flags", "//components/messages/android:feature_flags",
"//components/module_installer/android:native", "//components/module_installer/android:native",
"//components/offline_pages/task", "//components/offline_pages/task",
......
...@@ -17,16 +17,21 @@ android_library("java") { ...@@ -17,16 +17,21 @@ android_library("java") {
"java/src/org/chromium/components/messages/MessageBannerViewBinder.java", "java/src/org/chromium/components/messages/MessageBannerViewBinder.java",
"java/src/org/chromium/components/messages/MessageContainer.java", "java/src/org/chromium/components/messages/MessageContainer.java",
"java/src/org/chromium/components/messages/MessageDispatcher.java", "java/src/org/chromium/components/messages/MessageDispatcher.java",
"java/src/org/chromium/components/messages/MessageDispatcherBridge.java",
"java/src/org/chromium/components/messages/MessageDispatcherProvider.java", "java/src/org/chromium/components/messages/MessageDispatcherProvider.java",
"java/src/org/chromium/components/messages/MessageStateHandler.java", "java/src/org/chromium/components/messages/MessageStateHandler.java",
"java/src/org/chromium/components/messages/MessageWrapper.java",
"java/src/org/chromium/components/messages/SingleActionMessage.java", "java/src/org/chromium/components/messages/SingleActionMessage.java",
] ]
resources_package = "org.chromium.components.messages" resources_package = "org.chromium.components.messages"
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
deps = [ deps = [
":java_resources", ":java_resources",
"//base:base_java", "//base:base_java",
"//base:jni_java",
"//components/browser_ui/banners/android:java", "//components/browser_ui/banners/android:java",
"//components/browser_ui/widget/android:java", "//components/browser_ui/widget/android:java",
"//content/public/android:content_java",
"//third_party/android_deps:androidx_annotation_annotation_java", "//third_party/android_deps:androidx_annotation_annotation_java",
"//ui/android:ui_java", "//ui/android:ui_java",
] ]
...@@ -45,6 +50,27 @@ android_resources("java_resources") { ...@@ -45,6 +50,27 @@ android_resources("java_resources") {
] ]
} }
generate_jni("jni_headers") {
sources = [
"java/src/org/chromium/components/messages/MessageDispatcherBridge.java",
"java/src/org/chromium/components/messages/MessageWrapper.java",
]
}
static_library("android") {
sources = [
"message_dispatcher_bridge.cc",
"message_dispatcher_bridge.h",
"message_wrapper.cc",
"message_wrapper.h",
]
deps = [
":jni_headers",
"//base",
"//content/public/browser",
]
}
# Build target for Messages manager code, that owns and initializes # Build target for Messages manager code, that owns and initializes
# MessageDispatcher. # MessageDispatcher.
android_library("manager_java") { android_library("manager_java") {
...@@ -55,6 +81,18 @@ android_library("manager_java") { ...@@ -55,6 +81,18 @@ android_library("manager_java") {
deps = [ ":java" ] deps = [ ":java" ]
} }
android_library_factory("factory_java") {
sources = [
"internal/java/src/org/chromium/components/messages/MessagesFactory.java",
]
deps = [
":java",
":manager_java",
"//ui/android:ui_full_java",
]
}
static_library("feature_flags") { static_library("feature_flags") {
sources = [ sources = [
"messages_feature.cc", "messages_feature.cc",
...@@ -90,23 +128,14 @@ android_library("javatests") { ...@@ -90,23 +128,14 @@ android_library("javatests") {
] ]
} }
android_library_factory("factory_java") {
sources = [
"internal/java/src/org/chromium/components/messages/MessagesFactory.java",
]
deps = [
":java",
":manager_java",
"//ui/android:ui_full_java",
]
}
java_library("junit") { java_library("junit") {
# Skip platform checks since Robolectric depends on requires_android targets. # Skip platform checks since Robolectric depends on requires_android targets.
bypass_platform_checks = true bypass_platform_checks = true
testonly = true testonly = true
sources = [ "java/src/org/chromium/components/messages/MessageAutoDismissTimerTest.java" ] sources = [
"java/src/org/chromium/components/messages/MessageAutoDismissTimerTest.java",
"java/src/org/chromium/components/messages/MessageWrapperTest.java",
]
deps = [ deps = [
":java", ":java",
"//base:base_java_test_support", "//base:base_java_test_support",
...@@ -115,5 +144,6 @@ java_library("junit") { ...@@ -115,5 +144,6 @@ java_library("junit") {
"//third_party/android_deps:robolectric_all_java", "//third_party/android_deps:robolectric_all_java",
"//third_party/junit", "//third_party/junit",
"//third_party/mockito:mockito_java", "//third_party/mockito:mockito_java",
"//ui/android:ui_java",
] ]
} }
include_rules = [ include_rules = [
"+ui/android", "+ui/android",
"+base/test/android", "+base/test/android",
"+content/public/android",
"+content/public/browser",
"+content/public/test/android", "+content/public/test/android",
"+components/browser_ui/banners/android", "+components/browser_ui/banners/android",
"+components/browser_ui/widget/android", "+components/browser_ui/widget/android",
......
// 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 org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.content_public.browser.WebContents;
/**
* Java counterpart to MessageDispatcherBridge. Enables C++ feature code to enqueue/dismiss messages
* with MessageDispatcher.
*/
@JNINamespace("messages")
public class MessageDispatcherBridge {
@CalledByNative
private static void enqueueMessage(MessageWrapper message, WebContents webContents) {
MessageDispatcher messageDispatcher =
MessageDispatcherProvider.from(webContents.getTopLevelNativeWindow());
messageDispatcher.enqueueMessage(message.getMessageProperties());
}
@CalledByNative
private static void dismissMessage(MessageWrapper message, WebContents webContents) {
MessageDispatcher messageDispatcher =
MessageDispatcherProvider.from(webContents.getTopLevelNativeWindow());
messageDispatcher.dismissMessage(message.getMessageProperties());
}
}
\ No newline at end of file
// 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 android.view.View;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Java side of native MessageWrapper class that represents a message for native features.
*/
@JNINamespace("messages")
public final class MessageWrapper {
private long mNativeMessageWrapper;
private final PropertyModel mMessageProperties;
/**
* Creates an instance of MessageWrapper and links it with native MessageWrapper object.
* @param nativeMessageWrapper Pointer to native MessageWrapper.
* @return reference to created MessageWrapper.
*/
@CalledByNative
static MessageWrapper create(long nativeMessageWrapper) {
return new MessageWrapper(nativeMessageWrapper);
}
private MessageWrapper(long nativeMessageWrapper) {
mNativeMessageWrapper = nativeMessageWrapper;
mMessageProperties =
new PropertyModel.Builder(MessageBannerProperties.SINGLE_ACTION_MESSAGE_KEYS)
.with(MessageBannerProperties.PRIMARY_BUTTON_CLICK_LISTENER,
this::handleActionClick)
.with(MessageBannerProperties.ON_DISMISSED, this::handleMessageDismissed)
.build();
}
PropertyModel getMessageProperties() {
return mMessageProperties;
}
@CalledByNative
void setTitle(String title) {
mMessageProperties.set(MessageBannerProperties.TITLE, title);
}
@CalledByNative
void setDescription(String description) {
mMessageProperties.set(MessageBannerProperties.DESCRIPTION, description);
}
@CalledByNative
void setPrimaryButtonText(String primaryButtonText) {
mMessageProperties.set(MessageBannerProperties.PRIMARY_BUTTON_TEXT, primaryButtonText);
}
@CalledByNative
void clearNativePtr() {
mNativeMessageWrapper = 0;
}
private void handleActionClick(View v) {
if (mNativeMessageWrapper == 0) return;
MessageWrapperJni.get().handleActionClick(mNativeMessageWrapper);
}
private void handleMessageDismissed() {
MessageWrapperJni.get().handleDismissCallback(mNativeMessageWrapper);
}
@NativeMethods
interface Natives {
void handleActionClick(long nativeMessageWrapper);
void handleDismissCallback(long nativeMessageWrapper);
}
}
\ No newline at end of file
// 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 org.mockito.Mockito.never;
import androidx.test.filters.SmallTest;
import org.junit.Assert;
import org.junit.Before;
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.BaseRobolectricTestRunner;
import org.chromium.base.test.util.JniMocker;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Unit test for MessageWrapper.
*/
@RunWith(BaseRobolectricTestRunner.class)
public class MessageWrapperTest {
@Rule
public MockitoRule mMockitoRule = MockitoJUnit.rule();
@Rule
public JniMocker mJniMocker = new JniMocker();
@Mock
private MessageWrapper.Natives mNativeMock;
@Before
public void setUp() {
mJniMocker.mock(MessageWrapperJni.TEST_HOOKS, mNativeMock);
}
/**
* Tests that message properties are correctly propagated to PropertyModel.
*/
@Test
@SmallTest
public void testMessageProperties() {
MessageWrapper message = MessageWrapper.create(1);
message.setTitle("Title");
message.setDescription("Description");
message.setPrimaryButtonText("Primary button");
PropertyModel messageProperties = message.getMessageProperties();
Assert.assertEquals("Title doesn't match initial value", "Title",
messageProperties.get(MessageBannerProperties.TITLE));
Assert.assertEquals("Description doesn't match initial value", "Description",
messageProperties.get(MessageBannerProperties.DESCRIPTION));
Assert.assertEquals("Button text doesn't match initial value", "Primary button",
messageProperties.get(MessageBannerProperties.PRIMARY_BUTTON_TEXT));
}
/**
* Tests that native functions are called in response to callbacks invocation.
*/
@Test
@SmallTest
public void testCallbacks() {
final long nativePtr = 1;
MessageWrapper message = MessageWrapper.create(nativePtr);
PropertyModel messageProperties = message.getMessageProperties();
messageProperties.get(MessageBannerProperties.PRIMARY_BUTTON_CLICK_LISTENER).onClick(null);
Mockito.verify(mNativeMock).handleActionClick(nativePtr);
messageProperties.get(MessageBannerProperties.ON_DISMISSED).run();
Mockito.verify(mNativeMock).handleDismissCallback(nativePtr);
}
/**
* Tests that native callbacks are not delivered if the MessageWrapper was destroyed.
*/
@Test
@SmallTest
public void testDestroyedMessageWrapperCallbacks() {
final long nativePtr = 1;
MessageWrapper message = MessageWrapper.create(nativePtr);
PropertyModel messageProperties = message.getMessageProperties();
message.clearNativePtr();
messageProperties.get(MessageBannerProperties.PRIMARY_BUTTON_CLICK_LISTENER).onClick(null);
Mockito.verify(mNativeMock, never()).handleActionClick(nativePtr);
messageProperties.get(MessageBannerProperties.ON_DISMISSED).run();
Mockito.verify(mNativeMock, never()).handleDismissCallback(nativePtr);
}
}
// 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.
#include "components/messages/android/message_dispatcher_bridge.h"
#include <jni.h>
#include "base/android/jni_android.h"
#include "components/messages/android/jni_headers/MessageDispatcherBridge_jni.h"
#include "content/public/browser/web_contents.h"
namespace messages {
// static
void MessageDispatcherBridge::EnqueueMessage(
const MessageWrapper& message,
content::WebContents* web_contents) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_MessageDispatcherBridge_enqueueMessage(
env, message.GetJavaMessageWrapper(), web_contents->GetJavaWebContents());
}
// static
void MessageDispatcherBridge::DismissMessage(
const MessageWrapper& message,
content::WebContents* web_contents) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_MessageDispatcherBridge_dismissMessage(
env, message.GetJavaMessageWrapper(), web_contents->GetJavaWebContents());
}
} // namespace messages
\ No newline at end of file
// 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.
#ifndef COMPONENTS_MESSAGES_ANDROID_MESSAGE_DISPATCHER_BRIDGE_H_
#define COMPONENTS_MESSAGES_ANDROID_MESSAGE_DISPATCHER_BRIDGE_H_
#include "components/messages/android/message_wrapper.h"
namespace content {
class WebContents;
}
namespace messages {
// C++ counterpart to MessageDispatcherBridge.java. Enables C++ feature code to
// enqueue/dismiss messages with MessageDispatcher.java.
class MessageDispatcherBridge {
public:
static void EnqueueMessage(const MessageWrapper& message,
content::WebContents* web_contents);
static void DismissMessage(const MessageWrapper& message,
content::WebContents* web_contents);
};
} // namespace messages
#endif // COMPONENTS_MESSAGES_ANDROID_MESSAGE_DISPATCHER_BRIDGE_H_
\ No newline at end of file
// 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.
#include "components/messages/android/message_wrapper.h"
#include "base/android/jni_string.h"
#include "base/logging.h"
#include "components/messages/android/jni_headers/MessageWrapper_jni.h"
#include "content/public/browser/web_contents.h"
namespace messages {
MessageWrapper::MessageWrapper(base::OnceClosure action_callback,
base::OnceClosure dismiss_callback)
: action_callback_(std::move(action_callback)),
dismiss_callback_(std::move(dismiss_callback)),
message_dismissed_(false) {
JNIEnv* env = base::android::AttachCurrentThread();
java_message_wrapper_ =
Java_MessageWrapper_create(env, reinterpret_cast<int64_t>(this));
}
MessageWrapper::~MessageWrapper() {
JNIEnv* env = base::android::AttachCurrentThread();
Java_MessageWrapper_clearNativePtr(env, java_message_wrapper_);
CHECK(message_dismissed_);
}
void MessageWrapper::SetTitle(const base::string16& title) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jtitle =
base::android::ConvertUTF16ToJavaString(env, title);
Java_MessageWrapper_setTitle(env, java_message_wrapper_, jtitle);
}
void MessageWrapper::SetDescription(const base::string16& description) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jdescription =
base::android::ConvertUTF16ToJavaString(env, description);
Java_MessageWrapper_setDescription(env, java_message_wrapper_, jdescription);
}
void MessageWrapper::SetPrimaryButtonText(
const base::string16& primary_button_text) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jstring> jprimary_button_text =
base::android::ConvertUTF16ToJavaString(env, primary_button_text);
Java_MessageWrapper_setPrimaryButtonText(env, java_message_wrapper_,
jprimary_button_text);
}
void MessageWrapper::HandleActionClick(JNIEnv* env) {
if (!action_callback_.is_null())
std::move(action_callback_).Run();
}
void MessageWrapper::HandleDismissCallback(JNIEnv* env) {
message_dismissed_ = true;
if (!dismiss_callback_.is_null())
std::move(dismiss_callback_).Run();
}
const base::android::JavaRef<jobject>& MessageWrapper::GetJavaMessageWrapper()
const {
return java_message_wrapper_;
}
} // namespace messages
\ No newline at end of file
// 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.
#ifndef COMPONENTS_MESSAGES_ANDROID_MESSAGE_WRAPPER_H_
#define COMPONENTS_MESSAGES_ANDROID_MESSAGE_WRAPPER_H_
#include <jni.h>
#include <memory>
#include "base/android/scoped_java_ref.h"
#include "base/callback.h"
#include "base/strings/string16.h"
namespace messages {
// |MessagesWrapper| represents a message for native feature code. It accepts
// callbacks for action and dismiss events and provides methods for setting
// message properties. After setting message's properties feature code can
// enqueue the message through |MessageDispatcherBridge|.
class MessageWrapper {
public:
MessageWrapper(base::OnceClosure action_callback,
base::OnceClosure dismiss_callback);
~MessageWrapper();
MessageWrapper(const MessageWrapper&) = delete;
MessageWrapper& operator=(const MessageWrapper&) = delete;
// Methods to set message properties. On Android the values are propagated to
// Java and stored in |PropertyModel|.
void SetTitle(const base::string16& title);
void SetDescription(const base::string16& description);
void SetPrimaryButtonText(const base::string16& primary_button_text);
// Following methods forward calls from java to provided callbacks.
void HandleActionClick(JNIEnv* env);
void HandleDismissCallback(JNIEnv* env);
const base::android::JavaRef<jobject>& GetJavaMessageWrapper() const;
private:
base::android::ScopedJavaGlobalRef<jobject> java_message_wrapper_;
base::OnceClosure action_callback_;
base::OnceClosure dismiss_callback_;
bool message_dismissed_;
};
} // namespace messages
#endif // COMPONENTS_MESSAGES_ANDROID_MESSAGE_WRAPPER_H_
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment