Commit 1af6a2b0 authored by Clemens Arbesser's avatar Clemens Arbesser Committed by Commit Bot

[Autofill Assistant] Add support for list popup to generic UI.

Bug: b/145043394
Change-Id: I4fa18067d55314b2e2e01531343216aba3ff360e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2033158
Commit-Queue: Clemens Arbesser <arbesser@google.com>
Reviewed-by: default avatarMathias Carlen <mcarlen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#738152}
parent a276a7a2
...@@ -30,6 +30,13 @@ public class AssistantGenericUiDelegate { ...@@ -30,6 +30,13 @@ public class AssistantGenericUiDelegate {
AssistantGenericUiDelegate.this, identifier, value); AssistantGenericUiDelegate.this, identifier, value);
} }
void onListPopupSelectionChanged(String identifier, AssistantValue value) {
assert mNativeAssistantGenericUiDelegate != 0;
AssistantGenericUiDelegateJni.get().onListPopupSelectionChanged(
mNativeAssistantGenericUiDelegate, AssistantGenericUiDelegate.this, identifier,
value);
}
@CalledByNative @CalledByNative
private void clearNativePtr() { private void clearNativePtr() {
mNativeAssistantGenericUiDelegate = 0; mNativeAssistantGenericUiDelegate = 0;
...@@ -39,5 +46,7 @@ public class AssistantGenericUiDelegate { ...@@ -39,5 +46,7 @@ public class AssistantGenericUiDelegate {
interface Natives { interface Natives {
void onViewClicked(long nativeAssistantGenericUiDelegate, AssistantGenericUiDelegate caller, void onViewClicked(long nativeAssistantGenericUiDelegate, AssistantGenericUiDelegate caller,
String identifier, @Nullable AssistantValue value); String identifier, @Nullable AssistantValue value);
void onListPopupSelectionChanged(long nativeAssistantGenericUiDelegate,
AssistantGenericUiDelegate caller, String identifier, AssistantValue value);
} }
} }
...@@ -4,18 +4,43 @@ ...@@ -4,18 +4,43 @@
package org.chromium.chrome.browser.autofill_assistant.generic_ui; package org.chromium.chrome.browser.autofill_assistant.generic_ui;
import android.content.Context;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.view.View; import android.view.View;
import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.content.browser.input.PopupItemType;
import org.chromium.content.browser.input.SelectPopupDialog;
import org.chromium.content.browser.input.SelectPopupItem;
/** JNI bridge between {@code interaction_handler_android} and android views. */ import java.util.ArrayList;
import java.util.List;
/** JNI bridge between {@code interaction_handler_android} and Java. */
@JNINamespace("autofill_assistant") @JNINamespace("autofill_assistant")
public class AssistantViewInteractions { public class AssistantViewInteractions {
@CalledByNative @CalledByNative
public static void setOnClickListener(View view, String identifier, private static void setOnClickListener(View view, String identifier,
@Nullable AssistantValue value, AssistantGenericUiDelegate delegate) { @Nullable AssistantValue value, AssistantGenericUiDelegate delegate) {
view.setOnClickListener(unused -> { delegate.onViewClicked(identifier, value); }); view.setOnClickListener(unused -> delegate.onViewClicked(identifier, value));
}
@CalledByNative
private static void showListPopup(Context context, String[] itemNames,
@PopupItemType int[] itemTypes, int[] selectedItems, boolean multiple,
String identifier, AssistantGenericUiDelegate delegate) {
assert (itemNames.length == itemTypes.length);
List<SelectPopupItem> popupItems = new ArrayList<>();
for (int i = 0; i < itemNames.length; i++) {
popupItems.add(new SelectPopupItem(itemNames[i], itemTypes[i]));
}
SelectPopupDialog dialog = new SelectPopupDialog(context,
(indices)
-> delegate.onListPopupSelectionChanged(
identifier, new AssistantValue(indices)),
popupItems, multiple, selectedItems);
dialog.show();
} }
} }
...@@ -29,18 +29,38 @@ AssistantGenericUiDelegate::~AssistantGenericUiDelegate() { ...@@ -29,18 +29,38 @@ AssistantGenericUiDelegate::~AssistantGenericUiDelegate() {
void AssistantGenericUiDelegate::OnViewClicked( void AssistantGenericUiDelegate::OnViewClicked(
JNIEnv* env, JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller, const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jidentifier, const base::android::JavaParamRef<jstring>& jview_identifier,
const base::android::JavaParamRef<jobject>& jvalue) {
std::string view_identifier;
if (jview_identifier) {
base::android::ConvertJavaStringToUTF8(env, jview_identifier,
&view_identifier);
}
ValueProto value;
if (jvalue) {
value = ui_controller_android_utils::ToNativeValue(env, jvalue);
}
ui_controller_->OnViewEvent({EventProto::kOnViewClicked, view_identifier},
value);
}
void AssistantGenericUiDelegate::OnListPopupSelectionChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jmodel_identifier,
const base::android::JavaParamRef<jobject>& jvalue) { const base::android::JavaParamRef<jobject>& jvalue) {
std::string identifier; std::string identifier;
if (jidentifier) { if (jmodel_identifier) {
base::android::ConvertJavaStringToUTF8(env, jidentifier, &identifier); base::android::ConvertJavaStringToUTF8(env, jmodel_identifier, &identifier);
} }
ValueProto value; ValueProto value;
if (jvalue) { if (jvalue) {
value = ui_controller_android_utils::ToNativeValue(env, jvalue); value = ui_controller_android_utils::ToNativeValue(env, jvalue);
} }
ui_controller_->OnViewEvent({EventProto::kOnViewClicked, identifier}, value);
ui_controller_->OnValueChanged(identifier, value);
} }
base::android::ScopedJavaGlobalRef<jobject> base::android::ScopedJavaGlobalRef<jobject>
......
...@@ -9,16 +9,29 @@ ...@@ -9,16 +9,29 @@
namespace autofill_assistant { namespace autofill_assistant {
class UiControllerAndroid; class UiControllerAndroid;
// Delegate class for the generic UI. // Delegate class for the generic UI. Receives events from the Java UI and
// forwards them to the ui controller.
class AssistantGenericUiDelegate { class AssistantGenericUiDelegate {
public: public:
explicit AssistantGenericUiDelegate(UiControllerAndroid* ui_controller); explicit AssistantGenericUiDelegate(UiControllerAndroid* ui_controller);
~AssistantGenericUiDelegate(); ~AssistantGenericUiDelegate();
void OnViewClicked(JNIEnv* env, // A view was clicked in the UI. |jview_identifier| is the corresponding view
const base::android::JavaParamRef<jobject>& jcaller, // identifier.
const base::android::JavaParamRef<jstring>& jidentifier, void OnViewClicked(
const base::android::JavaParamRef<jobject>& jvalue); JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jview_identifier,
const base::android::JavaParamRef<jobject>& jvalue);
// The selection in a list popup has changed. |jmodel_identifier| is the model
// identifier that the new selection indices should be written to. |jvalue| is
// a Java array of integers containing the newly selected indices.
void OnListPopupSelectionChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jcaller,
const base::android::JavaParamRef<jstring>& jmodel_identifier,
const base::android::JavaParamRef<jobject>& jvalue);
base::android::ScopedJavaGlobalRef<jobject> GetJavaObject(); base::android::ScopedJavaGlobalRef<jobject> GetJavaObject();
......
...@@ -3,6 +3,9 @@ ...@@ -3,6 +3,9 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "chrome/browser/android/autofill_assistant/interaction_handler_android.h" #include "chrome/browser/android/autofill_assistant/interaction_handler_android.h"
#include <algorithm>
#include <vector>
#include "base/android/jni_array.h"
#include "base/android/jni_string.h" #include "base/android/jni_string.h"
#include "base/callback_helpers.h" #include "base/callback_helpers.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
...@@ -35,6 +38,94 @@ void ShowInfoPopup(const InfoPopupProto& proto, ...@@ -35,6 +38,94 @@ void ShowInfoPopup(const InfoPopupProto& proto,
jcontext_local); jcontext_local);
} }
void ShowListPopup(base::WeakPtr<UserModel> user_model,
const ShowListPopupProto& proto,
base::android::ScopedJavaGlobalRef<jobject> jcontext,
base::android::ScopedJavaGlobalRef<jobject> jdelegate,
const ValueProto& ignored) {
if (!user_model) {
return;
}
auto item_names = user_model->GetValue(proto.item_names_model_identifier());
if (!item_names.has_value()) {
DVLOG(2) << "Failed to show list popup: '"
<< proto.item_names_model_identifier() << "' not found in model.";
return;
}
if (item_names->strings().values().size() == 0) {
DVLOG(2) << "Failed to show list popup: the list of item names in '"
<< proto.item_names_model_identifier() << "' was empty.";
return;
}
base::Optional<ValueProto> item_types;
if (proto.has_item_types_model_identifier()) {
item_types = user_model->GetValue(proto.item_types_model_identifier());
if (!item_types.has_value()) {
DVLOG(2) << "Failed to show list popup: '"
<< proto.item_types_model_identifier()
<< "' not found in the model.";
return;
}
if (item_types->ints().values().size() !=
item_names->strings().values().size()) {
DVLOG(2) << "Failed to show list popup: Expected item_types to contain "
<< item_names->strings().values().size() << " integers, but got "
<< item_types->ints().values().size();
return;
}
} else {
item_types = ValueProto();
for (int i = 0; i < item_names->strings().values().size(); ++i) {
item_types->mutable_ints()->add_values(
static_cast<int>(ShowListPopupProto::ENABLED));
}
}
auto selected_indices =
user_model->GetValue(proto.selected_item_indices_model_identifier());
if (!selected_indices.has_value()) {
DVLOG(2) << "Failed to show list popup: '"
<< proto.selected_item_indices_model_identifier()
<< "' not found in model.";
return;
}
if (!(*selected_indices == ValueProto()) &&
selected_indices->kind_case() != ValueProto::kInts) {
DVLOG(2) << "Failed to show list popup: expected '"
<< proto.selected_item_indices_model_identifier()
<< "' to be int[], but was of type "
<< selected_indices->kind_case();
return;
}
JNIEnv* env = base::android::AttachCurrentThread();
auto jidentifier = base::android::ConvertUTF8ToJavaString(
env, proto.selected_item_indices_model_identifier());
std::vector<std::string> item_names_vec;
std::copy(item_names->strings().values().begin(),
item_names->strings().values().end(),
std::back_inserter(item_names_vec));
std::vector<int> item_types_vec;
std::copy(item_types->ints().values().begin(),
item_types->ints().values().end(),
std::back_inserter(item_types_vec));
std::vector<int> selected_indices_vec;
std::copy(selected_indices->ints().values().begin(),
selected_indices->ints().values().end(),
std::back_inserter(selected_indices_vec));
Java_AssistantViewInteractions_showListPopup(
env, jcontext, base::android::ToJavaArrayOfStrings(env, item_names_vec),
base::android::ToJavaIntArray(env, item_types_vec),
base::android::ToJavaIntArray(env, selected_indices_vec),
proto.allow_multiselect(), jidentifier, jdelegate);
}
base::Optional<EventHandler::EventKey> CreateEventKeyFromProto( base::Optional<EventHandler::EventKey> CreateEventKeyFromProto(
const EventProto& proto, const EventProto& proto,
JNIEnv* env, JNIEnv* env,
...@@ -73,11 +164,12 @@ base::Optional<InteractionHandlerAndroid::InteractionCallback> ...@@ -73,11 +164,12 @@ base::Optional<InteractionHandlerAndroid::InteractionCallback>
CreateInteractionCallbackFromProto( CreateInteractionCallbackFromProto(
const CallbackProto& proto, const CallbackProto& proto,
UserModel* user_model, UserModel* user_model,
base::android::ScopedJavaGlobalRef<jobject> jcontext) { base::android::ScopedJavaGlobalRef<jobject> jcontext,
base::android::ScopedJavaGlobalRef<jobject> jdelegate) {
switch (proto.kind_case()) { switch (proto.kind_case()) {
case CallbackProto::kSetValue: case CallbackProto::kSetValue:
if (proto.set_value().model_identifier().empty()) { if (proto.set_value().model_identifier().empty()) {
VLOG(1) DVLOG(1)
<< "Error creating SetValue interaction: model_identifier not set"; << "Error creating SetValue interaction: model_identifier not set";
return base::nullopt; return base::nullopt;
} }
...@@ -89,8 +181,24 @@ CreateInteractionCallbackFromProto( ...@@ -89,8 +181,24 @@ CreateInteractionCallbackFromProto(
base::BindRepeating(&ShowInfoPopup, base::BindRepeating(&ShowInfoPopup,
proto.show_info_popup().info_popup(), jcontext)); proto.show_info_popup().info_popup(), jcontext));
} }
case CallbackProto::kShowListPopup:
if (proto.show_list_popup().item_names_model_identifier().empty()) {
DVLOG(1) << "Error creating ShowListPopup interaction: "
"items_list_model_identifier not set";
return base::nullopt;
}
if (proto.show_list_popup()
.selected_item_indices_model_identifier()
.empty()) {
DVLOG(1) << "Error creating ShowListPopup interaction: "
"selected_item_indices_model_identifier not set";
return base::nullopt;
}
return base::Optional<InteractionHandlerAndroid::InteractionCallback>(
base::BindRepeating(&ShowListPopup, user_model->GetWeakPtr(),
proto.show_list_popup(), jcontext, jdelegate));
case CallbackProto::KIND_NOT_SET: case CallbackProto::KIND_NOT_SET:
VLOG(1) << "Error creating interaction: kind not set"; DVLOG(1) << "Error creating interaction: kind not set";
return base::nullopt; return base::nullopt;
} }
} }
...@@ -134,15 +242,15 @@ bool InteractionHandlerAndroid::AddInteractionsFromProto( ...@@ -134,15 +242,15 @@ bool InteractionHandlerAndroid::AddInteractionsFromProto(
auto key = CreateEventKeyFromProto(interaction_proto.trigger_event(), env, auto key = CreateEventKeyFromProto(interaction_proto.trigger_event(), env,
views, jdelegate); views, jdelegate);
if (!key) { if (!key) {
VLOG(1) << "Invalid trigger event for interaction"; DVLOG(1) << "Invalid trigger event for interaction";
return false; return false;
} }
for (const auto& callback_proto : interaction_proto.callbacks()) { for (const auto& callback_proto : interaction_proto.callbacks()) {
auto callback = CreateInteractionCallbackFromProto(callback_proto, auto callback = CreateInteractionCallbackFromProto(
user_model, jcontext_); callback_proto, user_model, jcontext_, jdelegate);
if (!callback) { if (!callback) {
VLOG(1) << "Invalid callback for interaction"; DVLOG(1) << "Invalid callback for interaction";
return false; return false;
} }
AddInteraction(*key, *callback); AddInteraction(*key, *callback);
......
...@@ -471,6 +471,11 @@ void UiControllerAndroid::OnViewEvent(const EventHandler::EventKey& key, ...@@ -471,6 +471,11 @@ void UiControllerAndroid::OnViewEvent(const EventHandler::EventKey& key,
ui_delegate_->DispatchEvent(key, value); ui_delegate_->DispatchEvent(key, value);
} }
void UiControllerAndroid::OnValueChanged(const std::string& identifier,
const ValueProto& value) {
ui_delegate_->GetUserModel()->SetValue(identifier, value);
}
void UiControllerAndroid::Shutdown(Metrics::DropOutReason reason) { void UiControllerAndroid::Shutdown(Metrics::DropOutReason reason) {
client_->Shutdown(reason); client_->Shutdown(reason);
} }
......
...@@ -119,6 +119,7 @@ class UiControllerAndroid : public ControllerObserver { ...@@ -119,6 +119,7 @@ class UiControllerAndroid : public ControllerObserver {
// Called by AssistantGenericUiDelegate: // Called by AssistantGenericUiDelegate:
void OnViewEvent(const EventHandler::EventKey& key, const ValueProto& value); void OnViewEvent(const EventHandler::EventKey& key, const ValueProto& value);
void OnValueChanged(const std::string& identifier, const ValueProto& value);
// Called by AssistantCollectUserDataDelegate: // Called by AssistantCollectUserDataDelegate:
void OnShippingAddressChanged( void OnShippingAddressChanged(
......
...@@ -30,6 +30,7 @@ message CallbackProto { ...@@ -30,6 +30,7 @@ message CallbackProto {
oneof kind { oneof kind {
SetModelValueProto set_value = 1; SetModelValueProto set_value = 1;
ShowInfoPopupProto show_info_popup = 2; ShowInfoPopupProto show_info_popup = 2;
ShowListPopupProto show_list_popup = 3;
} }
} }
...@@ -65,6 +66,30 @@ message ShowInfoPopupProto { ...@@ -65,6 +66,30 @@ message ShowInfoPopupProto {
optional InfoPopupProto info_popup = 1; optional InfoPopupProto info_popup = 1;
} }
// Displays a popup showing a list of strings to choose from. Depending on
// |allow_multiselect|, users will be able to select one or multiple items from
// the list.
message ShowListPopupProto {
// The popup item type, corresponding to
// chromium/content/browser/input/PopupItemType.java.
enum ItemType {
GROUP = 0;
DISABLED = 1;
ENABLED = 2;
}
// The item names to show in the list. Must be a list of strings.
optional string item_names_model_identifier = 1;
// Optional, must be the same length as the list stored at
// |items_list_model_identifier| if specified. Will default to ENABLED for all
// items if not specified. Must be a list of int32 |ItemType| if specified.
optional string item_types_model_identifier = 2;
// The indices of the selected items (both input and output). Must be a list
// of integers.
optional string selected_item_indices_model_identifier = 3;
// Whether to allow the selection of multiple items or not.
optional bool allow_multiselect = 4;
}
// Callback that expects a boolean input value and enables/disables the // Callback that expects a boolean input value and enables/disables the
// specified view. // specified view.
message SetViewEnabledCallbackProto { message SetViewEnabledCallbackProto {
......
...@@ -134,6 +134,14 @@ void UserModel::SetValue(const std::string& identifier, ...@@ -134,6 +134,14 @@ void UserModel::SetValue(const std::string& identifier,
} }
} }
base::Optional<ValueProto> UserModel::GetValue(const std::string& identifier) {
auto it = values_.find(identifier);
if (it != values_.end()) {
return it->second;
}
return base::nullopt;
}
void UserModel::MergeWithProto(const ModelProto& another, void UserModel::MergeWithProto(const ModelProto& another,
bool force_notifications) { bool force_notifications) {
for (const auto& another_value : another.values()) { for (const auto& another_value : another.values()) {
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/observer_list.h" #include "base/observer_list.h"
#include "base/optional.h"
#include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/service.pb.h"
namespace autofill_assistant { namespace autofill_assistant {
...@@ -43,6 +44,9 @@ class UserModel { ...@@ -43,6 +44,9 @@ class UserModel {
const ValueProto& value, const ValueProto& value,
bool force_notification = false); bool force_notification = false);
// Returns the value for |identifier| or nullopt if there is no such value.
base::Optional<ValueProto> GetValue(const std::string& identifier);
void AddObserver(Observer* observer); void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer); void RemoveObserver(Observer* 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