Commit 8daef4fe authored by Matthew Jones's avatar Matthew Jones Committed by Commit Bot

Add touchless modal dialog presenter and view

This patch adds the basic framework for modal dialogs on
touchless devices. This patch adds a custom presenter and
several properties specifically for touchless devices. The
main addition to the dialog is the list of options that
the user can select (instead of the usual buttons). The
presenter is created and used by the NoTouchActivity.

Bug: 944512
Change-Id: I7a9a9e864b2cce5f76e57beb984f3cdf80558e0a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1534185Reviewed-by: default avatarTheresa <twellington@chromium.org>
Reviewed-by: default avatarBecky Zhou <huayinz@chromium.org>
Commit-Queue: Matthew Jones <mdjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#645334}
parent c47b5ed7
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="@color/modern_primary_color">
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/dialog_item_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="8dp" />
<TextView
android:id="@+id/dialog_item_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textAppearance="@style/TextAppearance.BlackBody" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- 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. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/modern_primary_color">
<org.chromium.ui.widget.ChromeImageView
android:id="@+id/touchless_dialog_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_gravity="center"
android:visibility="gone" />
<TextView
android:id="@+id/touchless_dialog_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.BlackHeadline"
android:visibility="gone" />
<TextView
android:id="@+id/touchless_dialog_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.BlackBody"
android:visibility="gone" />
<ListView
android:id="@+id/touchless_dialog_option_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_gravity="center" />
</LinearLayout>
\ No newline at end of file
...@@ -23,6 +23,7 @@ import org.chromium.chrome.browser.tab.Tab; ...@@ -23,6 +23,7 @@ import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabBuilder; import org.chromium.chrome.browser.tab.TabBuilder;
import org.chromium.chrome.browser.tab.TabRedirectHandler; import org.chromium.chrome.browser.tab.TabRedirectHandler;
import org.chromium.chrome.browser.tab.TabState; import org.chromium.chrome.browser.tab.TabState;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogPresenter;
import org.chromium.chrome.browser.touchless.ui.iph.KeyFunctionsIPHCoordinator; import org.chromium.chrome.browser.touchless.ui.iph.KeyFunctionsIPHCoordinator;
import org.chromium.chrome.browser.touchless.ui.progressbar.ProgressBarCoordinator; import org.chromium.chrome.browser.touchless.ui.progressbar.ProgressBarCoordinator;
import org.chromium.chrome.browser.touchless.ui.progressbar.ProgressBarView; import org.chromium.chrome.browser.touchless.ui.progressbar.ProgressBarView;
...@@ -30,6 +31,8 @@ import org.chromium.chrome.browser.touchless.ui.tooltip.TooltipView; ...@@ -30,6 +31,8 @@ import org.chromium.chrome.browser.touchless.ui.tooltip.TooltipView;
import org.chromium.content_public.browser.LoadUrlParams; import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.common.Referrer; import org.chromium.content_public.common.Referrer;
import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.PageTransition;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogManager.ModalDialogType;
/** /**
* An Activity used to display WebContents on devices that don't support touch. * An Activity used to display WebContents on devices that don't support touch.
...@@ -157,6 +160,11 @@ public class NoTouchActivity extends SingleTabActivity { ...@@ -157,6 +160,11 @@ public class NoTouchActivity extends SingleTabActivity {
@Override @Override
protected void initializeToolbar() {} protected void initializeToolbar() {}
@Override
public ModalDialogManager createModalDialogManager() {
return new ModalDialogManager(new TouchlessDialogPresenter(this), ModalDialogType.APP);
}
@Override @Override
protected ChromeFullscreenManager createFullscreenManager() { protected ChromeFullscreenManager createFullscreenManager() {
return new ChromeFullscreenManager(this, ChromeFullscreenManager.ControlsPosition.NONE); return new ChromeFullscreenManager(this, ChromeFullscreenManager.ControlsPosition.NONE);
......
// 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.chrome.browser.touchless.dialog;
import android.app.Activity;
import android.app.Dialog;
import android.support.v4.view.ViewCompat;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ListView;
import android.widget.TextView;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties;
import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.ListItemType;
import org.chromium.chrome.touchless.R;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager.Presenter;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.ModelListAdapter;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import org.chromium.ui.widget.ChromeImageView;
import java.util.ArrayList;
/** A modal dialog presenter that is specific to touchless dialogs. */
public class TouchlessDialogPresenter extends Presenter {
/** An activity to attach dialogs to. */
private final Activity mActivity;
/** The dialog this class abstracts. */
private Dialog mDialog;
/** The model change processor for the currently shown dialog. */
private PropertyModelChangeProcessor<PropertyModel, Pair<ViewGroup, ModelListAdapter>,
PropertyKey> mModelChangeProcessor;
public TouchlessDialogPresenter(Activity activity) {
mActivity = activity;
}
@Override
protected void addDialogView(PropertyModel model) {
// If the activity's decor view is not attached to window, we don't show the dialog because
// the window manager might have revoked the window token for this activity. See
// https://crbug.com/926688.
Window window = mActivity.getWindow();
if (window == null || !ViewCompat.isAttachedToWindow(window.getDecorView())) {
dismissCurrentDialog(DialogDismissalCause.NOT_ATTACHED_TO_WINDOW);
return;
}
mDialog = new Dialog(mActivity, R.style.Theme_Chromium_DialogWhenLarge);
mDialog.setOnCancelListener(dialogInterface
-> dismissCurrentDialog(DialogDismissalCause.NAVIGATE_BACK_OR_TOUCH_OUTSIDE));
// Cancel on touch outside should be disabled by default. The ModelChangeProcessor wouldn't
// notify change if the property is not set during initialization.
mDialog.setCanceledOnTouchOutside(false);
ViewGroup dialogView = (ViewGroup) LayoutInflater.from(mDialog.getContext())
.inflate(R.layout.touchless_dialog_view, null);
ModelListAdapter adapter = new ModelListAdapter(mActivity);
adapter.registerType(ListItemType.DEFAULT, new ModelListAdapter.ViewBuilder<View>() {
@Override
public View buildView() {
View view = LayoutInflater.from(mActivity).inflate(R.layout.dialog_list_item, null);
view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean focused) {
// TODO(mdjones): Do selected item styling here.
}
});
return view;
}
}, TouchlessDialogPresenter::bindListItem);
ListView dialogOptions = dialogView.findViewById(R.id.touchless_dialog_option_list);
dialogOptions.setAdapter(adapter);
dialogOptions.setItemsCanFocus(true);
mModelChangeProcessor = PropertyModelChangeProcessor.create(
model, Pair.create(dialogView, adapter), TouchlessDialogPresenter::bind);
mDialog.setContentView(dialogView);
mDialog.show();
dialogView.announceForAccessibility(getContentDescription(model));
}
@Override
protected void removeDialogView(PropertyModel model) {
if (mModelChangeProcessor != null) {
mModelChangeProcessor.destroy();
mModelChangeProcessor = null;
}
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
}
/**
* Bind a model to a touchless dialog view.
* @param model The model being bound.
* @param view The view to apply the model to.
* @param propertyKey The property that changed.
*/
private static void bind(
PropertyModel model, Pair<ViewGroup, ModelListAdapter> view, PropertyKey propertyKey) {
ViewGroup dialogView = view.first;
ModelListAdapter optionsAdapter = view.second;
// TODO(mdjones): If the default buttons are used assert no list items and convert the
// buttons to list items.
if (TouchlessDialogProperties.IS_FULLSCREEN == propertyKey) {
// TODO(mdjones): Implement fullscreen/non-fullscreen modes.
} else if (ModalDialogProperties.TITLE_ICON == propertyKey) {
ChromeImageView imageView = dialogView.findViewById(R.id.touchless_dialog_icon);
imageView.setImageDrawable(model.get(ModalDialogProperties.TITLE_ICON));
imageView.setVisibility(View.VISIBLE);
} else if (ModalDialogProperties.TITLE == propertyKey) {
TextView textView = dialogView.findViewById(R.id.touchless_dialog_title);
textView.setText(model.get(ModalDialogProperties.TITLE));
textView.setVisibility(View.VISIBLE);
} else if (ModalDialogProperties.MESSAGE == propertyKey) {
TextView textView = dialogView.findViewById(R.id.touchless_dialog_description);
textView.setText(model.get(ModalDialogProperties.MESSAGE));
textView.setVisibility(View.VISIBLE);
} else if (TouchlessDialogProperties.LIST_MODELS == propertyKey) {
ListView listView = dialogView.findViewById(R.id.touchless_dialog_option_list);
PropertyModel[] models = model.get(TouchlessDialogProperties.LIST_MODELS);
ArrayList<Pair<Integer, PropertyModel>> modelPairs = new ArrayList<>();
for (int i = 0; i < models.length; i++) modelPairs.add(Pair.create(0, models[i]));
optionsAdapter.updateModels(modelPairs);
}
}
/**
* Bind a list model item to its view.
* @param model The model to bind.
* @param view The view to be bound to.
* @param propertyKey The property that changed.
*/
private static void bindListItem(PropertyModel model, View view, PropertyKey propertyKey) {
if (DialogListItemProperties.ICON == propertyKey) {
ChromeImageView imageView = view.findViewById(R.id.dialog_item_icon);
imageView.setImageDrawable(model.get(DialogListItemProperties.ICON));
} else if (DialogListItemProperties.TEXT == propertyKey) {
TextView textView = view.findViewById(R.id.dialog_item_text);
textView.setText(model.get(DialogListItemProperties.TEXT));
} else if (DialogListItemProperties.CLICK_LISTENER == propertyKey) {
view.setOnClickListener(model.get(DialogListItemProperties.CLICK_LISTENER));
}
}
}
// 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.chrome.browser.touchless.dialog;
import android.graphics.drawable.Drawable;
import android.support.annotation.IntDef;
import android.view.View.OnClickListener;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModel.ReadableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** The properties that define a touchless dialog. */
public class TouchlessDialogProperties {
/** Possible list item types for the list of options in the dialog. */
@IntDef({ListItemType.DEFAULT})
@Retention(RetentionPolicy.SOURCE)
public @interface ListItemType {
int DEFAULT = 0;
}
/** Properties that determine how list items are displayed in the touchless dialog. */
public static class DialogListItemProperties {
/** The icon of the dialog. */
public static final WritableObjectPropertyKey<Drawable> ICON =
new WritableObjectPropertyKey<>();
/** The text shown for the list item. */
public static final WritableObjectPropertyKey<String> TEXT =
new WritableObjectPropertyKey<>();
/** The action to be performed when the item is selected. */
public static final WritableObjectPropertyKey<OnClickListener> CLICK_LISTENER =
new WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS = {ICON, TEXT, CLICK_LISTENER};
}
/**
* Whether the dialog is fullscreen. If false there will be a gap at the top showing the content
* behind the dialog.
*/
public static final ReadableBooleanPropertyKey IS_FULLSCREEN = new ReadableBooleanPropertyKey();
/** The title of the dialog. */
public static final WritableObjectPropertyKey<String> TITLE = new WritableObjectPropertyKey<>();
/**
* The list of options to be displayed in the dialog. These models should be using the
* {@link DialogListItemProperties} properties.
*/
public static final WritableObjectPropertyKey<PropertyModel[]> LIST_MODELS =
new WritableObjectPropertyKey<>();
public static final PropertyKey[] ALL_KEYS = {IS_FULLSCREEN, TITLE, LIST_MODELS};
public static final PropertyKey[] ALL_DIALOG_KEYS =
PropertyModel.concatKeys(ModalDialogProperties.ALL_KEYS, ALL_KEYS);
}
...@@ -25,6 +25,8 @@ touchless_java_sources = [ ...@@ -25,6 +25,8 @@ touchless_java_sources = [
"touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPageTopLayout.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessNewTabPageTopLayout.java",
"touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessTabObserver.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessTabObserver.java",
"touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessUiController.java", "touchless/java/src/org/chromium/chrome/browser/touchless/TouchlessUiController.java",
"touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java",
"touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogProperties.java",
"touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHCoordinator.java", "touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHCoordinator.java",
"touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHMediator.java", "touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHMediator.java",
"touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHProperties.java", "touchless/java/src/org/chromium/chrome/browser/touchless/ui/iph/KeyFunctionsIPHProperties.java",
......
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