Support add to apps dialog for touchless

Add touchless support for the "add to home screen" dialog. This patch
includes support for the "custom" view in the touchless dialog
(something supported by normal modal dialogs). The touchless dialog
extends the original "add to homescreen" dialog to keep most call
sites unchanged.

Bug: 963516
Change-Id: I8e0ce32a75127630702fc697330e52fea51b18f2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1613373
Commit-Queue: Matthew Jones <mdjones@chromium.org>
Reviewed-by: Theresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#660455}
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index 092f9f33..2344331 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -1644,6 +1644,7 @@
   "java/src/org/chromium/chrome/browser/webapps/SplashController.java",
   "java/src/org/chromium/chrome/browser/webapps/SplashDelegate.java",
   "java/src/org/chromium/chrome/browser/webapps/SplashscreenObserver.java",
+  "java/src/org/chromium/chrome/browser/webapps/TouchlessAddToHomescreenDialog.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkActivity.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkActivity0.java",
   "java/src/org/chromium/chrome/browser/webapps/WebApkActivity1.java",
diff --git a/chrome/android/java/res/layout/touchless_add_to_apps.xml b/chrome/android/java/res/layout/touchless_add_to_apps.xml
new file mode 100644
index 0000000..88bf8a1
--- /dev/null
+++ b/chrome/android/java/res/layout/touchless_add_to_apps.xml
@@ -0,0 +1,54 @@
+<?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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="start"
+    android:padding="8dp" >
+
+    <ProgressBar
+        android:id="@+id/spinny"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:padding="8dp" />
+
+    <!-- The icon that will be used on the home screen. -->
+    <ImageView
+        android:id="@+id/icon"
+        android:layout_width="36dp"
+        android:layout_height="36dp"
+        android:importantForAccessibility="no"
+        android:visibility="gone" />
+
+    <!-- The layout used for apps. -->
+    <LinearLayout
+        android:id="@+id/app_info"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="48dp"
+        android:visibility="gone" >
+
+        <!-- The name of the web app that will be used on the home screen. Not
+             editable as we preserve the developer's name choice for apps. -->
+        <TextView
+            android:id="@+id/name"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.BlackTitle1" />
+
+        <!-- The origin of the web app, displayed for security purposes. -->
+        <TextView
+            android:id="@+id/origin"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="2dp"
+            android:textAppearance="@style/TextAppearance.BlackBody" />
+    </LinearLayout>
+</FrameLayout>
+
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java
index 56b50519..d670de5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java
@@ -15,7 +15,9 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.chrome.browser.webapps.AddToHomescreenDialog;
+import org.chromium.chrome.browser.webapps.TouchlessAddToHomescreenDialog;
 
 /**
  * Handles the promotion and installation of an app specified by the current web page. This object
@@ -102,9 +104,21 @@
         mTab.getWindowAndroid().showIntent(appData.detailsIntent(), null, null);
     }
 
+    /**
+     * Build a dialog based on the browser mode.
+     * @return A version of the {@link AddToHomescreenDialog}.
+     */
+    private AddToHomescreenDialog buildDialog() {
+        if (FeatureUtilities.isNoTouchModeEnabled()) {
+            return new TouchlessAddToHomescreenDialog(
+                    mTab.getActivity(), mTab.getActivity().getModalDialogManager(), this);
+        }
+        return new AddToHomescreenDialog(mTab.getActivity(), this);
+    }
+
     @CalledByNative
     private boolean showNativeAppDialog(String title, Bitmap iconBitmap, AppData appData) {
-        mDialog = new AddToHomescreenDialog(mTab.getActivity(), this);
+        mDialog = buildDialog();
         mDialog.show();
         mDialog.onUserTitleAvailable(title, appData.installButtonText(), appData.rating());
         mDialog.onIconAvailable(iconBitmap);
@@ -113,7 +127,7 @@
 
     @CalledByNative
     private boolean showWebAppDialog(String title, Bitmap iconBitmap, String url) {
-        mDialog = new AddToHomescreenDialog(mTab.getActivity(), this);
+        mDialog = buildDialog();
         mDialog.show();
         mDialog.onUserTitleAvailable(title, url, true /* isWebapp */);
         mDialog.onIconAvailable(iconBitmap);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java
index e6f07d7..8e9b25a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/AddToHomescreenManager.java
@@ -11,6 +11,7 @@
 
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.util.FeatureUtilities;
 import org.chromium.content_public.browser.WebContents;
 
 /**
@@ -82,7 +83,12 @@
      */
     @CalledByNative
     public void showDialog() {
-        mDialog = new AddToHomescreenDialog(mActivity, this);
+        if (FeatureUtilities.isNoTouchModeEnabled()) {
+            mDialog = new TouchlessAddToHomescreenDialog(
+                    mActivity, mTab.getActivity().getModalDialogManager(), this);
+        } else {
+            mDialog = new AddToHomescreenDialog(mActivity, this);
+        }
         mDialog.show();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/TouchlessAddToHomescreenDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/TouchlessAddToHomescreenDialog.java
new file mode 100644
index 0000000..f1dfdd5
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/TouchlessAddToHomescreenDialog.java
@@ -0,0 +1,155 @@
+// 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.webapps;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties;
+import org.chromium.chrome.browser.touchless.dialog.TouchlessDialogProperties.DialogListItemProperties;
+import org.chromium.ui.modaldialog.DialogDismissalCause;
+import org.chromium.ui.modaldialog.ModalDialogManager;
+import org.chromium.ui.modaldialog.ModalDialogProperties;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/** A touchless variation of the {@link AddToHomescreenDialog}. */
+public class TouchlessAddToHomescreenDialog extends AddToHomescreenDialog {
+    private Activity mActivity;
+    private Delegate mDelegate;
+    private ModalDialogManager mDialogManager;
+    private PropertyModel mModel;
+
+    public TouchlessAddToHomescreenDialog(
+            Activity activity, ModalDialogManager dialogManager, Delegate delegate) {
+        super(activity, delegate);
+        mActivity = activity;
+        mDialogManager = dialogManager;
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void show() {
+        mModel = buildTouchlessDialogModel();
+        mDialogManager.showDialog(mModel, ModalDialogManager.ModalDialogType.APP);
+    }
+
+    /**
+     * Build the property model for the dialog in touchless mode.
+     * @return A model to pass to a {@link ModalDialogManager}.
+     */
+    private PropertyModel buildTouchlessDialogModel() {
+        Resources res = mActivity.getResources();
+        ModalDialogProperties.Controller controller = new ModalDialogProperties.Controller() {
+            @Override
+            public void onClick(PropertyModel model, int buttonType) {}
+
+            @Override
+            public void onDismiss(PropertyModel model, int dismissalCause) {
+                mDelegate.onDialogDismissed();
+            }
+        };
+        final PropertyModel model =
+                new PropertyModel.Builder(TouchlessDialogProperties.ALL_DIALOG_KEYS)
+                        .with(TouchlessDialogProperties.IS_FULLSCREEN, false)
+                        .with(ModalDialogProperties.CONTROLLER, controller)
+                        .build();
+        model.set(TouchlessDialogProperties.PRIORITY, TouchlessDialogProperties.Priority.HIGH);
+        model.set(ModalDialogProperties.TITLE,
+                res.getString(org.chromium.chrome.R.string.menu_add_to_apps));
+        TouchlessDialogProperties.ActionNames actions = new TouchlessDialogProperties.ActionNames();
+        actions.cancel = org.chromium.chrome.R.string.cancel;
+        actions.select = org.chromium.chrome.R.string.select;
+        actions.alt = 0;
+        model.set(TouchlessDialogProperties.ACTION_NAMES, actions);
+
+        model.set(TouchlessDialogProperties.CANCEL_ACTION,
+                (v) -> mDialogManager.dismissDialog(model, DialogDismissalCause.UNKNOWN));
+
+        model.set(TouchlessDialogProperties.LIST_MODELS,
+                new PropertyModel[] {buildListItemModel("")});
+
+        model.set(ModalDialogProperties.CUSTOM_VIEW,
+                mActivity.getLayoutInflater().inflate(R.layout.touchless_add_to_apps, null));
+
+        return model;
+    }
+
+    /**
+     * Build the list item that adds the app or site to the home screen.
+     * @param title The title of the app or site.
+     * @return The list item for the dialog model.
+     */
+    private PropertyModel buildListItemModel(final String title) {
+        Resources res = mActivity.getResources();
+        return new PropertyModel.Builder(DialogListItemProperties.ALL_KEYS)
+                .with(DialogListItemProperties.TEXT,
+                        res.getString(org.chromium.chrome.R.string.add))
+                .with(DialogListItemProperties.ICON,
+                        ApiCompatibilityUtils.getDrawable(
+                                res, org.chromium.chrome.R.drawable.ic_add))
+                .with(DialogListItemProperties.MULTI_CLICKABLE, false)
+                .with(DialogListItemProperties.CLICK_LISTENER,
+                        (v) -> {
+                            if (TextUtils.isEmpty(title)) return;
+                            mDelegate.addToHomescreen(title);
+                            mDialogManager.dismissDialog(mModel, DialogDismissalCause.UNKNOWN);
+                        })
+                .build();
+    }
+
+    /**
+     * Update the custom view shown for the touchless dialog. This shows the name of the app, icon,
+     * and site.
+     */
+    private void updateModelCustomView(Bitmap icon, String title, String origin) {
+        ViewGroup group = (ViewGroup) mModel.get(ModalDialogProperties.CUSTOM_VIEW);
+
+        group.findViewById(R.id.spinny).setVisibility(View.GONE);
+        group.findViewById(R.id.icon).setVisibility(View.VISIBLE);
+        group.findViewById(R.id.app_info).setVisibility(View.VISIBLE);
+
+        if (icon != null) ((ImageView) group.findViewById(R.id.icon)).setImageBitmap(icon);
+        if (title != null) ((TextView) group.findViewById(R.id.name)).setText(title);
+        if (origin != null) ((TextView) group.findViewById(R.id.origin)).setText(origin);
+    }
+
+    @Override
+    public void onUserTitleAvailable(String title, String url, boolean isWebapp) {
+        updateModelCustomView(null, title, url);
+        mModel.set(TouchlessDialogProperties.LIST_MODELS,
+                new PropertyModel[] {buildListItemModel(title)});
+    }
+
+    @Override
+    public void onUserTitleAvailable(String title, String installText, float rating) {
+        updateModelCustomView(null, title, null);
+    }
+
+    @Override
+    public void onIconAvailable(Bitmap icon) {
+        updateModelCustomView(icon, null, null);
+    }
+
+    @Override
+    @TargetApi(Build.VERSION_CODES.O)
+    public void onAdaptableIconAvailable(Bitmap icon) {
+        updateModelCustomView(icon, null, null);
+    }
+
+    @Override
+    public void onClick(View v) {
+        // Intentionally do nothing.
+    }
+}
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index 590b4f5..1870f55e 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -2315,6 +2315,9 @@
       <message name="IDS_NOTIFICATION_WEBAPK_INSTALLED" desc="Indicates that a WebAPK has been successfully added to Home screen.">
         Added to Home screen
       </message>
+      <message name="IDS_MENU_ADD_TO_APPS" desc="Text to accompany icon that will navigate to a page showing a categorized view of different applications or sites">
+        Add to My apps
+      </message>
 
       <!-- Page info popup -->
       <message name="IDS_PAGE_INFO_SITE_SETTINGS_BUTTON" desc="Text in the button that opens a website's Site Settings from the Page Info dialog.">
diff --git a/chrome/android/touchless/java/res/layout/touchless_dialog_view.xml b/chrome/android/touchless/java/res/layout/touchless_dialog_view.xml
index 2d999ad..28e4e2b 100644
--- a/chrome/android/touchless/java/res/layout/touchless_dialog_view.xml
+++ b/chrome/android/touchless/java/res/layout/touchless_dialog_view.xml
@@ -35,6 +35,13 @@
         android:textAppearance="@style/TextAppearance.BlackBody"
         android:visibility="gone" />
 
+    <FrameLayout
+        android:id="@+id/custom"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:visibility="gone" />
+
     <ListView
         android:id="@+id/touchless_dialog_option_list"
         android:layout_width="match_parent"
diff --git a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java
index f26030b4..359947d 100644
--- a/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java
+++ b/chrome/android/touchless/java/src/org/chromium/chrome/browser/touchless/dialog/TouchlessDialogPresenter.java
@@ -153,6 +153,10 @@
             TextView textView = dialogView.findViewById(R.id.touchless_dialog_description);
             textView.setText(model.get(ModalDialogProperties.MESSAGE));
             textView.setVisibility(View.VISIBLE);
+        } else if (ModalDialogProperties.CUSTOM_VIEW == propertyKey) {
+            ViewGroup customGroup = dialogView.findViewById(R.id.custom);
+            customGroup.addView(model.get(ModalDialogProperties.CUSTOM_VIEW));
+            customGroup.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);