Commit 0cf9aa09 authored by Yuwei Huang's avatar Yuwei Huang Committed by Commit Bot

[remoting][Android] Basic notification UI implement for Android

This CL implements a basic dialog-based notification UI for Android
client.

Bug: 1001291
Change-Id: If7df03179ad7a143a6c805c66595dbf7815eb074
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1846048
Commit-Queue: Yuwei Huang <yuweih@chromium.org>
Reviewed-by: default avatarJamie Walch <jamiewalch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#703926}
parent 0b2f16bf
...@@ -17,6 +17,7 @@ generate_jni("jni_headers") { ...@@ -17,6 +17,7 @@ generate_jni("jni_headers") {
"java/src/org/chromium/chromoting/jni/GlDisplay.java", "java/src/org/chromium/chromoting/jni/GlDisplay.java",
"java/src/org/chromium/chromoting/jni/JniInterface.java", "java/src/org/chromium/chromoting/jni/JniInterface.java",
"java/src/org/chromium/chromoting/jni/JniOAuthTokenGetter.java", "java/src/org/chromium/chromoting/jni/JniOAuthTokenGetter.java",
"java/src/org/chromium/chromoting/jni/NotificationPresenter.java",
"java/src/org/chromium/chromoting/jni/TouchEventData.java", "java/src/org/chromium/chromoting/jni/TouchEventData.java",
] ]
} }
......
...@@ -75,6 +75,7 @@ template("remoting_android_client_java_tmpl") { ...@@ -75,6 +75,7 @@ template("remoting_android_client_java_tmpl") {
"jni/DirectoryService.java", "jni/DirectoryService.java",
"jni/JniInterface.java", "jni/JniInterface.java",
"jni/JniOAuthTokenGetter.java", "jni/JniOAuthTokenGetter.java",
"jni/NotificationPresenter.java",
"jni/TouchEventData.java", "jni/TouchEventData.java",
] ]
...@@ -97,7 +98,10 @@ template("remoting_android_client_java_tmpl") { ...@@ -97,7 +98,10 @@ template("remoting_android_client_java_tmpl") {
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
srcjar_deps = [ "//remoting/client/jni:jni_enums" ] srcjar_deps = [
"//remoting/client/jni:jni_enums",
"//remoting/client/notification:notification_enums",
]
if (defined(invoker.play_services_package)) { if (defined(invoker.play_services_package)) {
deps += [ deps += [
......
...@@ -43,6 +43,7 @@ import org.chromium.chromoting.jni.ConnectionListener; ...@@ -43,6 +43,7 @@ import org.chromium.chromoting.jni.ConnectionListener;
import org.chromium.chromoting.jni.DirectoryService; import org.chromium.chromoting.jni.DirectoryService;
import org.chromium.chromoting.jni.DirectoryServiceRequestError; import org.chromium.chromoting.jni.DirectoryServiceRequestError;
import org.chromium.chromoting.jni.JniOAuthTokenGetter; import org.chromium.chromoting.jni.JniOAuthTokenGetter;
import org.chromium.chromoting.jni.NotificationPresenter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
...@@ -97,6 +98,9 @@ public class Chromoting extends AppCompatActivity ...@@ -97,6 +98,9 @@ public class Chromoting extends AppCompatActivity
/** Dialog for reporting connection progress. */ /** Dialog for reporting connection progress. */
private ProgressDialog mProgressIndicator; private ProgressDialog mProgressIndicator;
/** Helper for fetching notification and presenting it. */
private NotificationPresenter mNotificationPresenter;
/** /**
* Helper used by SessionConnection for session authentication. Receives onNewIntent() * Helper used by SessionConnection for session authentication. Receives onNewIntent()
* notifications to handle third-party authentication. * notifications to handle third-party authentication.
...@@ -236,6 +240,7 @@ public class Chromoting extends AppCompatActivity ...@@ -236,6 +240,7 @@ public class Chromoting extends AppCompatActivity
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
mDirectoryService = new DirectoryService(); mDirectoryService = new DirectoryService();
mNotificationPresenter = new NotificationPresenter(this);
// Get ahold of our view widgets. // Get ahold of our view widgets.
mHostListView = (ListView) findViewById(R.id.hostList_chooser); mHostListView = (ListView) findViewById(R.id.hostList_chooser);
...@@ -626,6 +631,7 @@ public class Chromoting extends AppCompatActivity ...@@ -626,6 +631,7 @@ public class Chromoting extends AppCompatActivity
} }
mAccount = accountName; mAccount = accountName;
JniOAuthTokenGetter.setAccount(accountName); JniOAuthTokenGetter.setAccount(accountName);
mNotificationPresenter.presentIfNecessary(accountName);
// The current host list is no longer valid for the new account, so clear the list. // The current host list is no longer valid for the new account, so clear the list.
mHosts = new HostInfo[0]; mHosts = new HostInfo[0];
......
// Copyright 2019 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.chromoting.jni;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AlertDialog;
import androidx.annotation.IntDef;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chromoting.NotificationAppearance;
import org.chromium.chromoting.Preconditions;
import org.chromium.chromoting.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Helper for fetching and presenting a notification */
@JNINamespace("remoting")
public final class NotificationPresenter {
@IntDef({State.NOT_FETCHED, State.FETCHING, State.FETCHED})
@Retention(RetentionPolicy.SOURCE)
private @interface State {
int NOT_FETCHED = 0;
int FETCHING = 1;
int FETCHED = 2;
}
private final long mNativeJniNotificationPresenter;
private final Activity mActivity;
private @State int mState;
/**
* @param activity The activity on which the notification will be shown.
*/
public NotificationPresenter(Activity activity) {
mNativeJniNotificationPresenter = NotificationPresenterJni.get().init(this);
mActivity = activity;
mState = State.NOT_FETCHED;
}
@Override
public void finalize() {
NotificationPresenterJni.get().destroy(mNativeJniNotificationPresenter);
}
/**
* Presents notification for the given |username| if no previous notification has been shown,
* and the user is selected for the notification.
* @param username String that can uniquely identify a user
*/
public void presentIfNecessary(String username) {
if (mState != State.NOT_FETCHED) {
return;
}
mState = State.FETCHING;
NotificationPresenterJni.get().fetchNotification(mNativeJniNotificationPresenter, username);
}
@CalledByNative
void onNotificationFetched(@NotificationAppearance int appearance, String messageText,
String linkText, String linkUrl) {
Preconditions.isTrue(mState == State.FETCHING);
mState = State.FETCHED;
// TODO(yuweih): Add Don't Show Again support.
final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setMessage(messageText);
builder.setPositiveButton(linkText, (DialogInterface dialog, int id) -> {
Intent openLink = new Intent(Intent.ACTION_VIEW, Uri.parse(linkUrl));
mActivity.startActivity(openLink);
});
builder.setNegativeButton(R.string.dismiss,
(DialogInterface dialog, int id)
-> {
// Do nothing
});
final AlertDialog dialog = builder.create();
dialog.show();
}
@CalledByNative
void onNoNotification() {
Preconditions.isTrue(mState == State.FETCHING);
mState = State.NOT_FETCHED;
}
@NativeMethods
interface Natives {
long init(NotificationPresenter javaPresenter);
void fetchNotification(long nativeJniNotificationPresenter, String username);
void destroy(long nativeJniNotificationPresenter);
}
}
...@@ -22,6 +22,7 @@ shared_library("remoting_client_jni") { ...@@ -22,6 +22,7 @@ shared_library("remoting_client_jni") {
"//remoting/base", "//remoting/base",
"//remoting/client", "//remoting/client",
"//remoting/client/display", "//remoting/client/display",
"//remoting/client/notification",
"//remoting/proto/remoting/v1:directory_grpc_library", "//remoting/proto/remoting/v1:directory_grpc_library",
"//remoting/protocol", "//remoting/protocol",
"//ui/gfx", "//ui/gfx",
...@@ -35,6 +36,8 @@ shared_library("remoting_client_jni") { ...@@ -35,6 +36,8 @@ shared_library("remoting_client_jni") {
"jni_directory_service.h", "jni_directory_service.h",
"jni_gl_display_handler.cc", "jni_gl_display_handler.cc",
"jni_gl_display_handler.h", "jni_gl_display_handler.h",
"jni_notification_presenter.cc",
"jni_notification_presenter.h",
"jni_oauth_token_getter.cc", "jni_oauth_token_getter.cc",
"jni_oauth_token_getter.h", "jni_oauth_token_getter.h",
"jni_runtime_delegate.cc", "jni_runtime_delegate.cc",
......
// Copyright 2019 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 "remoting/client/jni/jni_notification_presenter.h"
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "base/bind.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "remoting/android/jni_headers/NotificationPresenter_jni.h"
#include "remoting/client/chromoting_client_runtime.h"
#include "remoting/client/notification/notification_message.h"
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF8ToJavaString;
namespace remoting {
JniNotificationPresenter::JniNotificationPresenter(
const JavaObjectWeakGlobalRef& java_presenter)
: java_presenter_(java_presenter),
notification_client_(
ChromotingClientRuntime::GetInstance()->network_task_runner()),
sequence_(base::SequencedTaskRunnerHandle::Get()) {}
JniNotificationPresenter::~JniNotificationPresenter() {
DCHECK(sequence_->RunsTasksInCurrentSequence());
}
void JniNotificationPresenter::FetchNotification(
JNIEnv* env,
const base::android::JavaParamRef<jstring>& username) {
DCHECK(sequence_->RunsTasksInCurrentSequence());
std::string username_str = ConvertJavaStringToUTF8(env, username);
// Safe to use unretained: callback is dropped once client is deleted.
notification_client_.GetNotification(
username_str,
base::BindOnce(&JniNotificationPresenter::OnNotificationFetched,
base::Unretained(this)));
}
void JniNotificationPresenter::Destroy(JNIEnv* env) {
if (sequence_->RunsTasksInCurrentSequence()) {
delete this;
} else {
sequence_->DeleteSoon(FROM_HERE, this);
}
}
void JniNotificationPresenter::OnNotificationFetched(
base::Optional<NotificationMessage> notification) {
DCHECK(sequence_->RunsTasksInCurrentSequence());
JNIEnv* env = base::android::AttachCurrentThread();
auto java_presenter = java_presenter_.get(env);
if (!notification) {
Java_NotificationPresenter_onNoNotification(env, java_presenter);
return;
}
jint j_appearance = static_cast<jint>(notification->appearance);
auto j_message_text =
ConvertUTF8ToJavaString(env, notification->message_text);
auto j_link_text = ConvertUTF8ToJavaString(env, notification->link_text);
auto j_link_url = ConvertUTF8ToJavaString(env, notification->link_url);
Java_NotificationPresenter_onNotificationFetched(env, java_presenter,
j_appearance, j_message_text,
j_link_text, j_link_url);
}
static jlong JNI_NotificationPresenter_Init(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& java_presenter) {
return reinterpret_cast<intptr_t>(new JniNotificationPresenter(
JavaObjectWeakGlobalRef(env, java_presenter)));
}
} // namespace remoting
// Copyright 2019 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 REMOTING_CLIENT_JNI_JNI_NOTIFICATION_PRESENTER_H_
#define REMOTING_CLIENT_JNI_JNI_NOTIFICATION_PRESENTER_H_
#include <jni.h>
#include "base/android/jni_weak_ref.h"
#include "base/macros.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "remoting/client/notification/notification_client.h"
namespace remoting {
// C++ counterpart for org.chromium.chromoting.jni.NotificationPresenter.
class JniNotificationPresenter final {
public:
explicit JniNotificationPresenter(
const JavaObjectWeakGlobalRef& java_presenter);
~JniNotificationPresenter();
void FetchNotification(JNIEnv* env,
const base::android::JavaParamRef<jstring>& username);
void Destroy(JNIEnv* env);
private:
void OnNotificationFetched(base::Optional<NotificationMessage> notification);
JavaObjectWeakGlobalRef java_presenter_;
NotificationClient notification_client_;
scoped_refptr<base::SequencedTaskRunner> sequence_;
DISALLOW_COPY_AND_ASSIGN(JniNotificationPresenter);
};
} // namespace remoting
#endif // REMOTING_CLIENT_JNI_JNI_NOTIFICATION_PRESENTER_H_
\ No newline at end of file
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
if (is_android) {
import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
}
source_set("notification") { source_set("notification") {
sources = [ sources = [
"gstatic_json_fetcher.cc", "gstatic_json_fetcher.cc",
...@@ -25,6 +30,14 @@ source_set("notification") { ...@@ -25,6 +30,14 @@ source_set("notification") {
] ]
} }
if (is_android) {
java_cpp_enum("notification_enums") {
sources = [
"notification_message.h",
]
}
}
source_set("unit_tests") { source_set("unit_tests") {
testonly = true testonly = true
......
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
namespace remoting { namespace remoting {
struct NotificationMessage final { struct NotificationMessage final {
// GENERATED_JAVA_ENUM_PACKAGE: org.chromium.chromoting
// GENERATED_JAVA_CLASS_NAME_OVERRIDE: NotificationAppearance
enum class Appearance { enum class Appearance {
TOAST, TOAST,
DIALOG, DIALOG,
......
...@@ -659,7 +659,7 @@ ...@@ -659,7 +659,7 @@
<message name="IDS_PRIVACY_POLICY" desc="The label to access the privacy policy, displayed in navigation menu." formatter_data="android_java"> <message name="IDS_PRIVACY_POLICY" desc="The label to access the privacy policy, displayed in navigation menu." formatter_data="android_java">
Privacy Policy Privacy Policy
</message> </message>
<message name="IDS_DISMISS" desc="Label for button to dismiss a dialog"> <message name="IDS_DISMISS" desc="Label for button to dismiss a dialog" formatter_data="android_java">
Dismiss Dismiss
</message> </message>
<message name="IDS_DONT_SHOW_AGAIN" desc="Label for checkbox to make a dialog not showing again."> <message name="IDS_DONT_SHOW_AGAIN" desc="Label for checkbox to make a dialog not showing again.">
......
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