Commit 2d923bed authored by Mehran Mahmoudi's avatar Mehran Mahmoudi Committed by Commit Bot

Move AddToHomescreen to MVC (Part 2)

This is the second step in a series of CLs for moving the
AddToHomescreen component to MVC in Android.

In this CL view classes are added and hooked up. A subsequent CL will
add the coordinator, mediator, and installer classes.

Design doc: http://go/a2hs-mvc

Bug: 994759
Change-Id: I3dbe02a14c5966986a483c4ba68f60f492a52d61
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1793633
Commit-Queue: Mehran Mahmoudi <mahmoudi@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Reviewed-by: default avatarDominick Ng <dominickn@chromium.org>
Reviewed-by: default avatarPeter Kotwicz <pkotwicz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#701347}
parent be0266c5
......@@ -1780,7 +1780,10 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webapps/WebappScopePolicy.java",
"java/src/org/chromium/chrome/browser/webapps/WebappTabDelegate.java",
"java/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenManager.java",
"java/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenView.java",
"java/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenProperties.java",
"java/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenDialogView.java",
"java/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenViewBinder.java",
"java/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenViewDelegate.java",
"java/src/org/chromium/chrome/browser/webauth/AuthenticatorFactory.java",
"java/src/org/chromium/chrome/browser/webauth/AuthenticatorImpl.java",
"java/src/org/chromium/chrome/browser/webauth/Fido2ApiHandler.java",
......
......@@ -209,6 +209,8 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/webapps/WebappLauncherActivityTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebappInfoTest.java",
"junit/src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java",
"junit/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenDialogViewTest.java",
"junit/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenViewBinderTest.java",
"junit/src/org/chromium/chrome/browser/webshare/SharedFileCollatorTest.java",
"junit/src/org/chromium/chrome/browser/webshare/ShareServiceImplTest.java",
"junit/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetSwipeDetectorTest.java",
......
......@@ -523,7 +523,6 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/webapps/WebappSplashScreenThemeColorTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/WebappVisibilityTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenManagerTest.java",
"javatests/src/org/chromium/chrome/browser/webapps/addtohomescreen/AddToHomescreenViewTest.java",
"javatests/src/org/chromium/chrome/browser/webauth/AuthenticatorTest.java",
"javatests/src/org/chromium/chrome/browser/webshare/WebShareTest.java",
"javatests/src/org/chromium/chrome/browser/widget/ChromeTextInputLayoutRenderTest.java",
......
......@@ -36,7 +36,8 @@
android:singleLine="true"
android:paddingTop="0dp"
android:layout_marginTop="10dp"
android:layout_marginStart="48dp" />
android:layout_marginStart="48dp"
android:visibility="invisible"/>
<!-- The layout used for apps. -->
<LinearLayout
......
......@@ -17,7 +17,6 @@ import org.chromium.chrome.browser.ShortcutHelper;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.vr.VrModuleProvider;
import org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenView;
import org.chromium.content_public.browser.WebContents;
/**
......@@ -169,12 +168,6 @@ public class AppBannerManager extends EmptyTabObserver {
return R.string.menu_add_to_homescreen;
}
@VisibleForTesting
public AddToHomescreenView getAddToHomescreenDialogForTesting() {
return AppBannerManagerJni.get().getAddToHomescreenDialogForTesting(
mNativePointer, AppBannerManager.this);
}
/** Overrides whether the system supports add to home screen. Used in testing. */
@VisibleForTesting
public static void setIsSupported(boolean state) {
......@@ -217,9 +210,6 @@ public class AppBannerManager extends EmptyTabObserver {
boolean onAppDetailsRetrieved(long nativeAppBannerManagerAndroid, AppBannerManager caller,
AppData data, String title, String packageName, String imageUrl);
// Testing methods.
AddToHomescreenView getAddToHomescreenDialogForTesting(
long nativeAppBannerManagerAndroid, AppBannerManager caller);
boolean isRunningForTesting(long nativeAppBannerManagerAndroid, AppBannerManager caller);
void setDaysAfterDismissAndIgnoreToTrigger(int dismissDays, int ignoreDays);
void setTimeDeltaForTesting(int days);
......
......@@ -8,6 +8,7 @@ import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.util.Pair;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
......@@ -16,14 +17,19 @@ import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenView;
import org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenDialogView;
import org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenProperties;
import org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenViewBinder;
import org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenViewDelegate;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* Handles the promotion and installation of an app specified by the current web page. This object
* is created by and owned by the native AppBannerUiDelegate.
*/
@JNINamespace("banners")
public class AppBannerUiDelegateAndroid implements AddToHomescreenView.Delegate {
public class AppBannerUiDelegateAndroid implements AddToHomescreenViewDelegate {
private static final String TAG = "AppBannerUi";
/** Pointer to the native AppBannerUiDelegateAndroid. */
......@@ -31,7 +37,7 @@ public class AppBannerUiDelegateAndroid implements AddToHomescreenView.Delegate
private Tab mTab;
private AddToHomescreenView mDialog;
private PropertyModel mViewModel;
private boolean mAddedToHomescreen;
......@@ -41,7 +47,7 @@ public class AppBannerUiDelegateAndroid implements AddToHomescreenView.Delegate
}
@Override
public void addToHomescreen(String title) {
public void onAddToHomescreen(String title) {
mAddedToHomescreen = true;
// The title is ignored for app banners as we respect the developer-provided title.
if (mNativePointer != 0) {
......@@ -51,29 +57,25 @@ public class AppBannerUiDelegateAndroid implements AddToHomescreenView.Delegate
}
@Override
public void onNativeAppDetailsRequested() {
public boolean onAppDetailsRequested() {
if (mNativePointer != 0) {
AppBannerUiDelegateAndroidJni.get().showNativeAppDetails(
return AppBannerUiDelegateAndroidJni.get().showNativeAppDetails(
mNativePointer, AppBannerUiDelegateAndroid.this);
}
return false;
}
@Override
public void onDialogDismissed() {
public void onViewDismissed() {
if (!mAddedToHomescreen && mNativePointer != 0) {
AppBannerUiDelegateAndroidJni.get().onUiCancelled(
mNativePointer, AppBannerUiDelegateAndroid.this);
}
mDialog = null;
mViewModel = null;
mAddedToHomescreen = false;
}
@CalledByNative
private AddToHomescreenView getDialogForTesting() {
return mDialog;
}
@CalledByNative
private void destroy() {
mNativePointer = 0;
......@@ -107,28 +109,41 @@ public class AppBannerUiDelegateAndroid implements AddToHomescreenView.Delegate
}
/**
* Build a dialog based on the browser mode.
* @return A version of the {@link AddToHomescreenView}.
* Build a view model.
*
* @return An instance of {@link PropertyModel}.
*/
private AddToHomescreenView buildDialog() {
return new AddToHomescreenView(mTab.getActivity(), this);
private PropertyModel createViewModel() {
PropertyModel model = new PropertyModel.Builder(AddToHomescreenProperties.ALL_KEYS).build();
PropertyModelChangeProcessor.create(model,
new AddToHomescreenDialogView(mTab.getActivity(),
mTab.getActivity().getModalDialogManager(),
AppBannerManager.getHomescreenLanguageOption(), this),
AddToHomescreenViewBinder::bind);
return model;
}
@CalledByNative
private boolean showNativeAppDialog(String title, Bitmap iconBitmap, AppData appData) {
mDialog = buildDialog();
mDialog.show();
mDialog.onUserTitleAvailable(title, appData.installButtonText(), appData.rating());
mDialog.onIconAvailable(iconBitmap);
mViewModel = createViewModel();
mViewModel.set(AddToHomescreenProperties.TITLE, title);
mViewModel.set(AddToHomescreenProperties.ICON, new Pair<>(iconBitmap, false));
mViewModel.set(AddToHomescreenProperties.TYPE, AddToHomescreenProperties.AppType.NATIVE);
mViewModel.set(AddToHomescreenProperties.CAN_SUBMIT, true);
mViewModel.set(AddToHomescreenProperties.NATIVE_APP_RATING, appData.rating());
mViewModel.set(
AddToHomescreenProperties.NATIVE_INSTALL_BUTTON_TEXT, appData.installButtonText());
return true;
}
@CalledByNative
private boolean showWebAppDialog(String title, Bitmap iconBitmap, String url) {
mDialog = buildDialog();
mDialog.show();
mDialog.onUserTitleAvailable(title, url, true /* isWebapp */);
mDialog.onIconAvailable(iconBitmap);
mViewModel = createViewModel();
mViewModel.set(AddToHomescreenProperties.TITLE, title);
mViewModel.set(AddToHomescreenProperties.ICON, new Pair<>(iconBitmap, false));
mViewModel.set(AddToHomescreenProperties.TYPE, AddToHomescreenProperties.AppType.WEB_APK);
mViewModel.set(AddToHomescreenProperties.CAN_SUBMIT, true);
mViewModel.set(AddToHomescreenProperties.URL, url);
return true;
}
......@@ -141,9 +156,11 @@ public class AppBannerUiDelegateAndroid implements AddToHomescreenView.Delegate
interface Natives {
void addToHomescreen(
long nativeAppBannerUiDelegateAndroid, AppBannerUiDelegateAndroid caller);
void onUiCancelled(
long nativeAppBannerUiDelegateAndroid, AppBannerUiDelegateAndroid caller);
void showNativeAppDetails(
boolean showNativeAppDetails(
long nativeAppBannerUiDelegateAndroid, AppBannerUiDelegateAndroid caller);
}
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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.
......@@ -6,124 +6,84 @@ package org.chromium.chrome.browser.webapps.addtohomescreen;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.os.Build;
import android.support.v7.app.AlertDialog;
import android.support.annotation.StringRes;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RatingBar;
import android.widget.TextView;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.banners.AppBannerManager;
import org.chromium.ui.UiUtils;
import org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenProperties.AppType;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Displays the "Add to Homescreen" dialog, which contains a (possibly editable) title, icon, and
* possibly an origin.
*
* When show() is invoked, the dialog is shown immediately. A spinner is displayed if any data is
* not yet fetched, and accepting the dialog is disabled until all data is available and in its
* place on the screen.
* When the constructor is called, the dialog is shown immediately. A spinner is displayed if any
* data is not yet fetched, and accepting the dialog is disabled until all data is available and in
* its place on the screen.
*/
public class AddToHomescreenView implements View.OnClickListener {
/**
* The delegate for which this dialog is displayed. Used by the dialog to indicate when the user
* accedes to adding to home screen, and when the dialog is dismissed.
*/
public static interface Delegate {
/**
* Called when the user accepts adding the item to the home screen with the provided title.
*/
void addToHomescreen(String title);
/**
* Called when the user wants to view a native app in the Play Store.
*/
void onNativeAppDetailsRequested();
/**
* Called when the dialog's lifetime is over and it disappears from the screen.
*/
void onDialogDismissed();
}
private AlertDialog mDialog;
private View mProgressBarView;
private ImageView mIconView;
public class AddToHomescreenDialogView
implements View.OnClickListener, ModalDialogProperties.Controller {
private PropertyModel mDialogModel;
private ModalDialogManager mModalDialogManager;
private AddToHomescreenViewDelegate mDelegate;
private View mParentView;
/**
* The {@mShortcutTitleInput} and the {@mAppLayout} are mutually exclusive, depending on whether
* the home screen item is a bookmark shortcut or a web/native app.
* {@link #mShortcutTitleInput} and the {@link #mAppLayout} are mutually exclusive, depending on
* whether the home screen item is a bookmark shortcut or a web/native app.
*/
private EditText mShortcutTitleInput;
private LinearLayout mAppLayout;
private TextView mAppNameView;
private TextView mAppOriginView;
private RatingBar mAppRatingBar;
private ImageView mPlayLogoView;
private Context mContext;
private Delegate mDelegate;
private boolean mHasIcon;
private View mProgressBarView;
private ImageView mIconView;
public AddToHomescreenView(Context context, Delegate delegate) {
mContext = context;
mDelegate = delegate;
}
private boolean mCanSubmit;
@VisibleForTesting
public AlertDialog getAlertDialogForTesting() {
return mDialog;
}
public AddToHomescreenDialogView(Context context, ModalDialogManager modalDialogManager,
@StringRes int titleText, AddToHomescreenViewDelegate delegate) {
assert delegate != null;
/**
* Shows the dialog for adding a shortcut to the home screen.
*/
public void show() {
View view = LayoutInflater.from(mContext).inflate(R.layout.add_to_homescreen_dialog, null);
AlertDialog.Builder builder =
new UiUtils
.CompatibleAlertDialogBuilder(mContext, R.style.Theme_Chromium_AlertDialog)
.setTitle(AppBannerManager.getHomescreenLanguageOption())
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
mModalDialogManager = modalDialogManager;
mDelegate = delegate;
mParentView = LayoutInflater.from(context).inflate(R.layout.add_to_homescreen_dialog, null);
mDialog = builder.create();
mDialog.getDelegate().setHandleNativeActionModesEnabled(false);
// On click of the menu item for "add to homescreen", an alert dialog pops asking the user
// if the title needs to be edited. On click of "Add", shortcut is created. Default
// title is the title of the page.
mProgressBarView = view.findViewById(R.id.spinny);
mIconView = (ImageView) view.findViewById(R.id.icon);
mShortcutTitleInput = view.findViewById(R.id.text);
mAppLayout = (LinearLayout) view.findViewById(R.id.app_info);
mProgressBarView = mParentView.findViewById(R.id.spinny);
mIconView = (ImageView) mParentView.findViewById(R.id.icon);
mShortcutTitleInput = mParentView.findViewById(R.id.text);
mAppLayout = (LinearLayout) mParentView.findViewById(R.id.app_info);
mAppNameView = (TextView) mAppLayout.findViewById(R.id.name);
mAppOriginView = (TextView) mAppLayout.findViewById(R.id.origin);
mAppRatingBar = (RatingBar) mAppLayout.findViewById(R.id.control_rating);
mPlayLogoView = (ImageView) view.findViewById(R.id.play_logo);
mPlayLogoView = (ImageView) mParentView.findViewById(R.id.play_logo);
// The dialog's text field is disabled till the "user title" is fetched,
mShortcutTitleInput.setVisibility(View.INVISIBLE);
mAppNameView.setOnClickListener(this);
mIconView.setOnClickListener(this);
view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
mParentView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
......@@ -150,127 +110,124 @@ public class AddToHomescreenView implements View.OnClickListener {
@Override
public void afterTextChanged(Editable editableText) {
updateAddButtonEnabledState();
updateInstallButton();
}
});
mDialog.setView(view);
mDialog.setButton(DialogInterface.BUTTON_POSITIVE,
mContext.getResources().getString(R.string.add),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
mDelegate.addToHomescreen(mShortcutTitleInput.getText().toString());
}
});
mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface d) {
updateAddButtonEnabledState();
}
});
Resources resources = context.getResources();
mDialogModel =
new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
.with(ModalDialogProperties.CONTROLLER, this)
.with(ModalDialogProperties.TITLE, resources, titleText)
.with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, resources, R.string.add)
.with(ModalDialogProperties.POSITIVE_BUTTON_DISABLED, true)
.with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, resources,
R.string.cancel)
.with(ModalDialogProperties.CUSTOM_VIEW, mParentView)
.with(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, true)
.build();
mModalDialogManager.showDialog(mDialogModel, ModalDialogManager.ModalDialogType.APP);
}
mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
mDialog = null;
mDelegate.onDialogDismissed();
}
});
void setTitle(String title) {
mAppNameView.setText(title);
mShortcutTitleInput.setText(title);
}
mDialog.show();
void setUrl(String url) {
mAppOriginView.setText(url);
}
/**
* Called when the home screen icon title (and possibly information from the web manifest) is
* available. Used for web apps and bookmark shortcuts.
* @param title Text to be displayed in the title field.
* @param url URL of the web app/shortcut.
* @param isWebapp True if this is for a web app, and false otherwise.
*/
public void onUserTitleAvailable(String title, String url, boolean isWebapp) {
// Users may edit the title of bookmark shortcuts, but we respect web app names and do not
// let users change them.
if (isWebapp) {
mShortcutTitleInput.setVisibility(View.GONE);
mAppNameView.setText(title);
mAppOriginView.setText(url);
mAppRatingBar.setVisibility(View.GONE);
mPlayLogoView.setVisibility(View.GONE);
mAppLayout.setVisibility(View.VISIBLE);
return;
void setIcon(Bitmap icon, boolean isAdaptive) {
if (isAdaptive && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
setAdaptiveIcon(icon);
} else {
assert !isAdaptive : "Adaptive icons should not be provided pre-Android O.";
mIconView.setImageBitmap(icon);
}
mProgressBarView.setVisibility(View.GONE);
mIconView.setVisibility(View.VISIBLE);
}
mShortcutTitleInput.setText(title);
mShortcutTitleInput.setVisibility(View.VISIBLE);
@TargetApi(Build.VERSION_CODES.O)
private void setAdaptiveIcon(Bitmap icon) {
mIconView.setImageIcon(Icon.createWithAdaptiveBitmap(icon));
}
/**
* Called when the home screen icon title, install text, and app rating are available. Used for
* native apps.
* @param title Text to be displayed in the title field
* @param installText String to be displayed on the positive button
* @param rating The rating of the app in the store.
*/
public void onUserTitleAvailable(String title, String installText, float rating) {
mShortcutTitleInput.setVisibility(View.GONE);
mAppNameView.setText(title);
mAppOriginView.setVisibility(View.GONE);
void setType(@AppType int type) {
assert (type >= AppType.NATIVE && type <= AppType.SHORTCUT);
mShortcutTitleInput.setVisibility(type == AppType.SHORTCUT ? View.VISIBLE : View.GONE);
mAppLayout.setVisibility(type != AppType.SHORTCUT ? View.VISIBLE : View.GONE);
mAppOriginView.setVisibility(type == AppType.WEB_APK ? View.VISIBLE : View.GONE);
mAppRatingBar.setVisibility(type == AppType.NATIVE ? View.VISIBLE : View.GONE);
mPlayLogoView.setVisibility(type == AppType.NATIVE ? View.VISIBLE : View.GONE);
}
void setNativeInstallButtonText(String installButtonText) {
mDialogModel.set(ModalDialogProperties.POSITIVE_BUTTON_TEXT, installButtonText);
mDialogModel.set(ModalDialogProperties.POSITIVE_BUTTON_CONTENT_DESCRIPTION,
ContextUtils.getApplicationContext().getString(
R.string.app_banner_view_native_app_install_accessibility,
installButtonText));
}
void setNativeAppRating(float rating) {
mAppRatingBar.setRating(rating);
mPlayLogoView.setImageResource(R.drawable.google_play);
mAppLayout.setVisibility(View.VISIBLE);
}
// Update the text on the primary button.
Button button = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
button.setText(installText);
button.setContentDescription(mContext.getString(
R.string.app_banner_view_native_app_install_accessibility, installText));
void setCanSubmit(boolean canSubmit) {
mCanSubmit = canSubmit;
updateInstallButton();
}
// Clicking on the app title or the icon will open the Play Store for more details.
mAppNameView.setOnClickListener(this);
mIconView.setOnClickListener(this);
private void updateInstallButton() {
boolean missingTitle = mShortcutTitleInput.getVisibility() == View.VISIBLE
&& TextUtils.isEmpty(mShortcutTitleInput.getText());
mDialogModel.set(
ModalDialogProperties.POSITIVE_BUTTON_DISABLED, !mCanSubmit || missingTitle);
}
/**
* Called when the home screen icon is available. Must be called after onUserTitleAvailable().
* @param icon that will be used in the launcher.
* From {@link View.OnClickListener}. Called when the views that have this class registered as
* their {@link View.OnClickListener} are clicked.
*
* @param v The view that was clicked.
*/
public void onIconAvailable(Bitmap icon) {
mIconView.setImageBitmap(icon);
setIconAvailable();
@Override
public void onClick(View v) {
if ((v == mAppNameView || v == mIconView)) {
if (mDelegate.onAppDetailsRequested()) {
mModalDialogManager.dismissDialog(
mDialogModel, DialogDismissalCause.ACTION_ON_CONTENT);
}
}
}
/**
* Called when the home screen icon is available and was generated to be an Android adaptable
* icon. Must be called after onUserTitleAvailable().
* @param icon that will be used in the launcher.
* From {@link ModalDialogProperties.Controller}. Called when a dialog button is clicked.
*
* @param model The dialog model that is associated with this click event.
* @param buttonType The type of the button.
*/
@TargetApi(Build.VERSION_CODES.O)
public void onAdaptableIconAvailable(Bitmap icon) {
mIconView.setImageIcon(Icon.createWithAdaptiveBitmap(icon));
setIconAvailable();
}
private void setIconAvailable() {
mProgressBarView.setVisibility(View.GONE);
mIconView.setVisibility(View.VISIBLE);
mHasIcon = true;
updateAddButtonEnabledState();
@Override
public void onClick(PropertyModel model, int buttonType) {
int dismissalCause = DialogDismissalCause.NEGATIVE_BUTTON_CLICKED;
if (buttonType == ModalDialogProperties.ButtonType.POSITIVE) {
mDelegate.onAddToHomescreen(mShortcutTitleInput.getText().toString());
dismissalCause = DialogDismissalCause.POSITIVE_BUTTON_CLICKED;
}
mModalDialogManager.dismissDialog(mDialogModel, dismissalCause);
}
@Override
public void onClick(View v) {
if (v == mAppNameView || v == mIconView) {
mDelegate.onNativeAppDetailsRequested();
mDialog.cancel();
}
public void onDismiss(PropertyModel model, int dismissalCause) {
mDelegate.onViewDismissed();
}
void updateAddButtonEnabledState() {
boolean enable = mHasIcon
&& (!TextUtils.isEmpty(mShortcutTitleInput.getText())
|| mAppLayout.getVisibility() == View.VISIBLE);
mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(enable);
@VisibleForTesting
View getParentViewForTest() {
return mParentView;
}
}
......@@ -4,28 +4,31 @@
package org.chromium.chrome.browser.webapps.addtohomescreen;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Build;
import android.text.TextUtils;
import android.util.Pair;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.banners.AppBannerManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.content_public.browser.WebContents;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* Manages the add to home screen process. Coordinates the native-side data fetching, and owns
* a dialog prompting the user to confirm the action (and potentially supply a title).
*/
public class AddToHomescreenManager implements AddToHomescreenView.Delegate {
protected final Activity mActivity;
public class AddToHomescreenManager implements AddToHomescreenViewDelegate {
protected final ChromeActivity mActivity;
protected final Tab mTab;
protected AddToHomescreenView mDialog;
protected PropertyModel mViewModel;
private long mNativeAddToHomescreenManager;
public AddToHomescreenManager(Activity activity, Tab tab) {
public AddToHomescreenManager(ChromeActivity activity, Tab tab) {
mActivity = activity;
mTab = tab;
}
......@@ -47,7 +50,7 @@ public class AddToHomescreenManager implements AddToHomescreenView.Delegate {
* Puts the object in a state where it is safe to be destroyed.
*/
public void destroy() {
mDialog = null;
mViewModel = null;
if (mNativeAddToHomescreenManager == 0) return;
AddToHomescreenManagerJni.get().destroy(
......@@ -55,29 +58,21 @@ public class AddToHomescreenManager implements AddToHomescreenView.Delegate {
mNativeAddToHomescreenManager = 0;
}
/**
* Adds a shortcut for the current Tab. Must not be called unless start() has been called.
* @param userRequestedTitle Title of the shortcut displayed on the homescreen.
*/
@Override
public void addToHomescreen(String userRequestedTitle) {
public void onAddToHomescreen(String title) {
assert mNativeAddToHomescreenManager != 0;
AddToHomescreenManagerJni.get().addToHomescreen(
mNativeAddToHomescreenManager, AddToHomescreenManager.this, userRequestedTitle);
mNativeAddToHomescreenManager, AddToHomescreenManager.this, title);
}
@Override
public void onNativeAppDetailsRequested() {
// This should never be called.
assert false;
public boolean onAppDetailsRequested() {
return false;
}
@Override
/**
* Destroys this object once the dialog has been dismissed.
*/
public void onDialogDismissed() {
public void onViewDismissed() {
destroy();
}
......@@ -86,25 +81,28 @@ public class AddToHomescreenManager implements AddToHomescreenView.Delegate {
*/
@CalledByNative
public void showDialog() {
mDialog = new AddToHomescreenView(mActivity, this);
mDialog.show();
mViewModel = new PropertyModel.Builder(AddToHomescreenProperties.ALL_KEYS).build();
PropertyModelChangeProcessor.create(mViewModel,
new AddToHomescreenDialogView(mActivity, mActivity.getModalDialogManager(),
AppBannerManager.getHomescreenLanguageOption(), this),
AddToHomescreenViewBinder::bind);
}
@CalledByNative
private void onUserTitleAvailable(String title, String url, boolean isWebapp) {
// Users may edit the title of bookmark shortcuts, but we respect web app names and do not
// let users change them.
mDialog.onUserTitleAvailable(title, url, isWebapp);
mViewModel.set(AddToHomescreenProperties.TITLE, title);
mViewModel.set(AddToHomescreenProperties.URL, url);
mViewModel.set(AddToHomescreenProperties.TYPE,
isWebapp ? AddToHomescreenProperties.AppType.WEB_APK
: AddToHomescreenProperties.AppType.SHORTCUT);
}
@CalledByNative
private void onIconAvailable(Bitmap icon, boolean iconAdaptable) {
if (iconAdaptable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mDialog.onAdaptableIconAvailable(icon);
} else {
assert !iconAdaptable : "Adaptive icons should not be provided pre-Android O.";
mDialog.onIconAvailable(icon);
}
mViewModel.set(AddToHomescreenProperties.ICON, new Pair<>(icon, iconAdaptable));
mViewModel.set(AddToHomescreenProperties.CAN_SUBMIT, true);
}
@NativeMethods
......
// 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.addtohomescreen;
import android.graphics.Bitmap;
import android.support.annotation.IntDef;
import android.util.Pair;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Contains the properties that an add-to-homescreen {@link PropertyModel} can have.
*/
public class AddToHomescreenProperties {
@IntDef({AppType.NATIVE, AppType.WEB_APK, AppType.SHORTCUT})
@Retention(RetentionPolicy.SOURCE)
public @interface AppType {
int NATIVE = 0;
int WEB_APK = 1;
int SHORTCUT = 2;
}
public static final PropertyModel.WritableObjectPropertyKey<String> TITLE =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableObjectPropertyKey<String> URL =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableObjectPropertyKey<Pair<Bitmap, Boolean>> ICON =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableIntPropertyKey TYPE =
new PropertyModel.WritableIntPropertyKey();
public static final PropertyModel.WritableBooleanPropertyKey CAN_SUBMIT =
new PropertyModel.WritableBooleanPropertyKey();
public static final PropertyModel.WritableObjectPropertyKey<String> NATIVE_INSTALL_BUTTON_TEXT =
new PropertyModel.WritableObjectPropertyKey<>();
public static final PropertyModel.WritableFloatPropertyKey NATIVE_APP_RATING =
new PropertyModel.WritableFloatPropertyKey();
public static final PropertyKey[] ALL_KEYS = {
TITLE, URL, ICON, TYPE, CAN_SUBMIT, NATIVE_INSTALL_BUTTON_TEXT, NATIVE_APP_RATING};
}
// 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.addtohomescreen;
import android.graphics.Bitmap;
import android.util.Pair;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Binds an add-to-homescreen {@link PropertyModel} with a {@link AddToHomescreenDialogView}.
*/
public class AddToHomescreenViewBinder {
public static void bind(PropertyModel model,
AddToHomescreenDialogView addToHomescreenDialogView, PropertyKey propertyKey) {
if (propertyKey.equals(AddToHomescreenProperties.TITLE)) {
addToHomescreenDialogView.setTitle(model.get(AddToHomescreenProperties.TITLE));
} else if (propertyKey.equals(AddToHomescreenProperties.URL)) {
addToHomescreenDialogView.setUrl(model.get(AddToHomescreenProperties.URL));
} else if (propertyKey.equals(AddToHomescreenProperties.ICON)) {
Pair<Bitmap, Boolean> iconPair = model.get(AddToHomescreenProperties.ICON);
addToHomescreenDialogView.setIcon(iconPair.first, iconPair.second);
} else if (propertyKey.equals(AddToHomescreenProperties.TYPE)) {
addToHomescreenDialogView.setType(model.get(AddToHomescreenProperties.TYPE));
} else if (propertyKey.equals(AddToHomescreenProperties.CAN_SUBMIT)) {
addToHomescreenDialogView.setCanSubmit(model.get(AddToHomescreenProperties.CAN_SUBMIT));
} else if (propertyKey.equals(AddToHomescreenProperties.NATIVE_INSTALL_BUTTON_TEXT)) {
addToHomescreenDialogView.setNativeInstallButtonText(
model.get(AddToHomescreenProperties.NATIVE_INSTALL_BUTTON_TEXT));
} else if (propertyKey.equals(AddToHomescreenProperties.NATIVE_APP_RATING)) {
addToHomescreenDialogView.setNativeAppRating(
model.get(AddToHomescreenProperties.NATIVE_APP_RATING));
}
}
}
// 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.addtohomescreen;
/**
* Used by {@link AddToHomescreenDialogView} to propagate view events to the business logic.
*/
public interface AddToHomescreenViewDelegate {
/**
* Called when the user accepts adding the item to the home screen with the provided title.
*/
void onAddToHomescreen(String title);
/**
* Called when the user requests app details.
* @return Whether the view should be dismissed.
*/
boolean onAppDetailsRequested();
/**
* Called when the view's lifetime is over and it disappears from the screen.
*/
void onViewDismissed();
}
......@@ -8,7 +8,6 @@ import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
import android.app.Instrumentation.ActivityResult;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
......@@ -16,9 +15,10 @@ import android.graphics.Bitmap;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.filters.SmallTest;
import android.support.v7.app.AlertDialog;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiSelector;
import android.view.View;
import android.widget.Button;
import org.junit.After;
import org.junit.Assert;
......@@ -51,7 +51,6 @@ import org.chromium.chrome.browser.infobar.InfoBarContainerLayout.Item;
import org.chromium.chrome.browser.infobar.InstallableAmbientBadgeInfoBar;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenView;
import org.chromium.chrome.browser.webapps.WebappDataStorage;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
......@@ -63,9 +62,14 @@ import org.chromium.chrome.test.util.browser.webapps.WebappTestPage;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.browser.test.util.TouchCommon;
import org.chromium.content_public.common.ContentUrlConstants;
import org.chromium.net.test.EmbeddedTestServer;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modaldialog.ModalDialogProperties.ButtonType;
import org.chromium.ui.modelutil.PropertyModel;
import java.util.ArrayList;
import java.util.List;
......@@ -172,6 +176,7 @@ public class AppBannerManagerTest {
@Mock
private PackageManager mPackageManager;
private EmbeddedTestServer mTestServer;
private UiDevice mUiDevice;
@Before
public void setUp() throws Exception {
......@@ -192,6 +197,7 @@ public class AppBannerManagerTest {
AppBannerManager.setTotalEngagementForTesting(10);
mTestServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext());
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
}
@After
......@@ -252,32 +258,22 @@ public class AppBannerManagerTest {
}
}
private String getExpectedDialogTitle(Tab tab) {
return tab.getActivity().getString(AppBannerManager.getHomescreenLanguageOption());
}
private void waitUntilNoDialogsShowing(final Tab tab) {
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
AddToHomescreenView dialog =
getAppBannerManager(tab).getAddToHomescreenDialogForTesting();
return dialog == null || dialog.getAlertDialogForTesting() == null;
}
});
UiObject dialogUiObject =
mUiDevice.findObject(new UiSelector().text(getExpectedDialogTitle(tab)));
dialogUiObject.waitUntilGone(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL);
}
private void tapAndWaitForModalBanner(final Tab tab) {
TouchCommon.singleClickView(tab.getView());
CriteriaHelper.pollUiThread(new Criteria() {
@Override
public boolean isSatisfied() {
AddToHomescreenView dialog =
getAppBannerManager(tab).getAddToHomescreenDialogForTesting();
if (dialog != null) {
AlertDialog alertDialog = dialog.getAlertDialogForTesting();
return alertDialog != null && alertDialog.isShowing();
}
return false;
}
});
UiObject dialogUiObject =
mUiDevice.findObject(new UiSelector().text(getExpectedDialogTitle(tab)));
Assert.assertTrue(dialogUiObject.waitForExists(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL));
}
private void triggerModalWebAppBanner(ChromeActivityTestRule<? extends ChromeActivity> rule,
......@@ -293,7 +289,7 @@ public class AppBannerManagerTest {
if (!installApp) return;
// Click the button to trigger the adding of the shortcut.
clickButton(tab, DialogInterface.BUTTON_POSITIVE);
clickButton(rule.getActivity(), ButtonType.POSITIVE);
}
private void triggerModalNativeAppBanner(ChromeActivityTestRule<? extends ChromeActivity> rule,
......@@ -305,8 +301,8 @@ public class AppBannerManagerTest {
waitUntilAmbientBadgeInfoBarAppears(rule);
Assert.assertEquals(mDetailsDelegate.mReferrer, expectedReferrer);
final Tab tab = rule.getActivity().getActivityTab();
tapAndWaitForModalBanner(tab);
final ChromeActivity activity = rule.getActivity();
tapAndWaitForModalBanner(activity.getActivityTab());
if (!installApp) return;
// Click the button to trigger the installation.
......@@ -317,14 +313,12 @@ public class AppBannerManagerTest {
instrumentation.addMonitor(activityMonitor);
ThreadUtils.runOnUiThreadBlocking(() -> {
Button button = getAppBannerManager(tab)
.getAddToHomescreenDialogForTesting()
.getAlertDialogForTesting()
.getButton(DialogInterface.BUTTON_POSITIVE);
Assert.assertEquals(NATIVE_APP_INSTALL_TEXT, button.getText());
String buttonText = activity.getModalDialogManager().getCurrentDialogForTest().get(
ModalDialogProperties.POSITIVE_BUTTON_TEXT);
Assert.assertEquals(NATIVE_APP_INSTALL_TEXT, buttonText);
});
clickButton(tab, DialogInterface.BUTTON_POSITIVE);
clickButton(activity, ButtonType.POSITIVE);
// Wait until the installation triggers.
instrumentation.waitForMonitorWithTimeout(
......@@ -346,23 +340,21 @@ public class AppBannerManagerTest {
tapAndWaitForModalBanner(tab);
// Explicitly dismiss the banner. We should be able to show the banner after dismissing.
clickButton(tab, DialogInterface.BUTTON_NEGATIVE);
clickButton(rule.getActivity(), ButtonType.NEGATIVE);
waitUntilNoDialogsShowing(tab);
tapAndWaitForModalBanner(tab);
clickButton(tab, DialogInterface.BUTTON_NEGATIVE);
clickButton(rule.getActivity(), ButtonType.NEGATIVE);
waitUntilNoDialogsShowing(tab);
tapAndWaitForModalBanner(tab);
}
private void clickButton(final Tab tab, final int button) {
ThreadUtils.runOnUiThreadBlocking(() -> {
getAppBannerManager(tab)
.getAddToHomescreenDialogForTesting()
.getAlertDialogForTesting()
.getButton(button)
.performClick();
});
private void clickButton(final ChromeActivity activity, @ButtonType final int buttonType) {
ModalDialogManager manager = activity.getModalDialogManager();
PropertyModel model = manager.getCurrentDialogForTest();
ModalDialogProperties.Controller dialogController =
model.get(ModalDialogProperties.CONTROLLER);
TestThreadUtils.runOnUiThreadBlocking(() -> dialogController.onClick(model, buttonType));
}
@Test
......@@ -514,11 +506,12 @@ public class AppBannerManagerTest {
false);
// Explicitly dismiss the banner.
final Tab tab = mTabbedActivityTestRule.getActivity().getActivityTab();
clickButton(tab, DialogInterface.BUTTON_NEGATIVE);
final ChromeActivity activity = mTabbedActivityTestRule.getActivity();
clickButton(activity, ButtonType.NEGATIVE);
// Ensure userChoice is resolved.
new TabTitleObserver(tab, "Got userChoice: dismissed").waitForTitleUpdate(3);
new TabTitleObserver(activity.getActivityTab(), "Got userChoice: dismissed")
.waitForTitleUpdate(3);
}
@Test
......@@ -531,11 +524,12 @@ public class AppBannerManagerTest {
NATIVE_APP_BLANK_REFERRER, false);
// Explicitly dismiss the banner.
final Tab tab = mTabbedActivityTestRule.getActivity().getActivityTab();
clickButton(tab, DialogInterface.BUTTON_NEGATIVE);
final ChromeActivity activity = mTabbedActivityTestRule.getActivity();
clickButton(activity, ButtonType.NEGATIVE);
// Ensure userChoice is resolved.
new TabTitleObserver(tab, "Got userChoice: dismissed").waitForTitleUpdate(3);
new TabTitleObserver(activity.getActivityTab(), "Got userChoice: dismissed")
.waitForTitleUpdate(3);
}
@Test
......
......@@ -4,7 +4,6 @@
package org.chromium.chrome.browser.webapps.addtohomescreen;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
......@@ -40,11 +39,14 @@ import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.common.ContentSwitches;
import org.chromium.net.test.EmbeddedTestServerRule;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
import java.util.concurrent.Callable;
/**
* Tests org.chromium.chrome.browser.webapps.AddToHomescreenManager and its C++ counterpart.
* Tests org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenManager and its C++
* counterpart.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@RetryOnFailure
......@@ -136,8 +138,9 @@ public class AddToHomescreenManagerTest {
}
}
}
/**
* Test AddToHomescreenManager subclass which mocks showing the add-to-homescreen dialog and
* Test AddToHomescreenManager subclass which mocks showing the add-to-homescreen view and
* adds the shortcut to the home screen once it is ready.
*/
private static class TestAddToHomescreenManager extends AddToHomescreenManager {
......@@ -145,33 +148,33 @@ public class AddToHomescreenManagerTest {
/**
* Creates an instance of {@link TestAddToHomescreenManager}.
*
* @param title The title that the user entered into the add-to-homescreen dialog.
*/
public TestAddToHomescreenManager(Activity activity, Tab tab, String title) {
public TestAddToHomescreenManager(ChromeActivity activity, Tab tab, String title) {
super(activity, tab);
mTitle = title;
}
@Override
public void showDialog() {
mDialog = new AddToHomescreenView(mActivity, TestAddToHomescreenManager.this) {
@Override
public void onUserTitleAvailable(String title, String url, boolean isWebapp) {
if (TextUtils.isEmpty(mTitle)) {
mTitle = title;
}
}
@Override
public void onIconAvailable(Bitmap icon) {
TestAddToHomescreenManager.this.addToHomescreen(mTitle);
}
@Override
public void onAdaptableIconAvailable(Bitmap icon) {
TestAddToHomescreenManager.this.addToHomescreen(mTitle);
}
};
mViewModel = new PropertyModel.Builder(AddToHomescreenProperties.ALL_KEYS).build();
PropertyModelChangeProcessor.create(mViewModel,
new AddToHomescreenDialogView(mActivity, mActivity.getModalDialogManager(),
R.string.menu_add_to_homescreen, this) {
@Override
void setTitle(String title) {
if (TextUtils.isEmpty(mTitle)) {
mTitle = title;
}
}
@Override
void setIcon(Bitmap icon, boolean isAdaptive) {
TestAddToHomescreenManager.this.onAddToHomescreen(mTitle);
}
},
AddToHomescreenViewBinder::bind);
}
}
......
// Copyright 2014 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.addtohomescreen;
import android.app.Activity;
import android.content.DialogInterface;
import android.support.test.filters.SmallTest;
import android.support.v7.app.AlertDialog;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
/**
* Tests org.chromium.chrome.browser.webapps.AddToHomescreenView by verifying
* that the calling the show() method actually shows the dialog and checks that
* some expected elements inside the dialog are present.
*
* This is mostly intended as a smoke test.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
// Preconnect causes issues with the single-threaded Java test server.
"--disable-features=NetworkPrediction"})
public class AddToHomescreenViewTest {
@Rule
public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
new ChromeActivityTestRule<>(ChromeActivity.class);
private static class MockAddToHomescreenManager extends AddToHomescreenManager {
public MockAddToHomescreenManager(Activity activity, Tab tab) {
super(activity, tab);
}
@Override
public void addToHomescreen(String userRequestedTitle) {}
}
@Before
public void setUp() throws InterruptedException {
mActivityTestRule.startMainActivityOnBlankPage();
}
@Test
@SmallTest
@Feature("{Webapp}")
@RetryOnFailure
public void testSmoke() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
AddToHomescreenView dialog = new AddToHomescreenView(mActivityTestRule.getActivity(),
new MockAddToHomescreenManager(mActivityTestRule.getActivity(),
mActivityTestRule.getActivity().getActivityTab()));
dialog.show();
AlertDialog alertDialog = dialog.getAlertDialogForTesting();
Assert.assertNotNull(alertDialog);
Assert.assertTrue(alertDialog.isShowing());
Assert.assertNotNull(alertDialog.findViewById(R.id.spinny));
Assert.assertNotNull(alertDialog.findViewById(R.id.icon));
Assert.assertNotNull(alertDialog.findViewById(R.id.text));
Assert.assertNotNull(alertDialog.getButton(DialogInterface.BUTTON_POSITIVE));
Assert.assertNotNull(alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE));
});
}
}
// 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.addtohomescreen;
import android.app.Activity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.Robolectric;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.webapps.addtohomescreen.AddToHomescreenProperties.AppType;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.modaldialog.ModalDialogProperties;
import org.chromium.ui.modelutil.PropertyModel;
/**
* Tests for the {@link AddToHomescreenDialogView} class.
*/
@RunWith(BaseRobolectricTestRunner.class)
public class AddToHomescreenDialogViewTest {
private AddToHomescreenDialogView mAddToHomescreenDialogView;
private CallbackHelper mAddCallback = new CallbackHelper();
private CallbackHelper mTitleClickCallback = new CallbackHelper();
private CallbackHelper mDismissCallback = new CallbackHelper();
private MockModalDialogManager mModalDialogManager = new MockModalDialogManager();
private static final String TEST_TITLE = "YouTube";
private static final String TEST_URL = "youtube.com";
private static final String TEST_NATIVE_ADD_TEXT = "Install";
private class MockModalDialogManager extends ModalDialogManager {
private PropertyModel mShownDialogModel;
private PropertyModel mDismissedDialogModel;
private int mDismissalCause;
public MockModalDialogManager() {
super(Mockito.mock(Presenter.class), 0);
}
@Override
public void showDialog(PropertyModel model, int dialogType) {
mShownDialogModel = model;
}
@Override
public void dismissDialog(PropertyModel model, int dismissalCause) {
mDismissedDialogModel = model;
mDismissalCause = dismissalCause;
model.get(ModalDialogProperties.CONTROLLER).onDismiss(model, dismissalCause);
}
public @DialogDismissalCause int getDismissalCause() {
return mDismissalCause;
}
public PropertyModel getDismissedDialogModel() {
return mDismissedDialogModel;
}
public PropertyModel getShownDialogModel() {
return mShownDialogModel;
}
}
@Before
public void setUp() {
// Create and show the view.
Activity activity = Robolectric.buildActivity(Activity.class).setup().get();
mAddToHomescreenDialogView = new AddToHomescreenDialogView(activity, mModalDialogManager,
R.string.menu_add_to_homescreen, new AddToHomescreenViewDelegate() {
@Override
public void onAddToHomescreen(String title) {
mAddCallback.notifyCalled();
}
@Override
public boolean onAppDetailsRequested() {
mTitleClickCallback.notifyCalled();
return true;
}
@Override
public void onViewDismissed() {
mDismissCallback.notifyCalled();
}
});
}
@Test
@Feature({"Webapp"})
public void testLoadingState() {
// Assert dialog is showing.
PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
Assert.assertNotNull(shownDialogModel);
// Assert views exist.
View parentView = mAddToHomescreenDialogView.getParentViewForTest();
Assert.assertNotNull(parentView);
Assert.assertNotNull(parentView.findViewById(R.id.spinny));
Assert.assertNotNull(parentView.findViewById(R.id.icon));
Assert.assertNotNull(parentView.findViewById(R.id.text));
Assert.assertNotNull(parentView.findViewById(R.id.app_info));
Assert.assertNotNull(parentView.findViewById(R.id.name));
Assert.assertNotNull(parentView.findViewById(R.id.origin));
Assert.assertNotNull(parentView.findViewById(R.id.control_rating));
Assert.assertNotNull(parentView.findViewById(R.id.play_logo));
// Visibility test.
assertVisibility(R.id.spinny, true);
assertVisibility(R.id.icon, false);
assertVisibility(R.id.text, false);
assertVisibility(R.id.app_info, false);
// Assert dialog buttons text.
Assert.assertEquals(
"Add", shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_TEXT));
Assert.assertEquals(
"Cancel", shownDialogModel.get(ModalDialogProperties.NEGATIVE_BUTTON_TEXT));
// Assert 'Add' is disabled and 'Cancel' is enabled.
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
Assert.assertFalse(shownDialogModel.get(ModalDialogProperties.NEGATIVE_BUTTON_DISABLED));
}
/**
* Tests the view for {@link AppType#WEB_APK}.
*/
@Test
@Feature({"Webapp"})
public void testWebAPK() {
initDialogView(AppType.WEB_APK);
mAddToHomescreenDialogView.setUrl(TEST_URL);
assertVisibility(R.id.spinny, false);
assertVisibility(R.id.icon, true);
assertVisibility(R.id.app_info, true);
assertVisibility(R.id.text, false);
assertVisibility(R.id.origin, true);
assertVisibility(R.id.control_rating, false);
assertVisibility(R.id.play_logo, false);
Assert.assertEquals(TEST_TITLE, getTextForViewWithId(R.id.name));
Assert.assertEquals(TEST_URL, getTextForViewWithId(R.id.origin));
}
/**
* Tests the view for {@link AppType#SHORTCUT}.
*/
@Test
@Feature({"Webapp"})
public void testShortcut() {
initDialogView(AppType.SHORTCUT);
assertVisibility(R.id.spinny, false);
assertVisibility(R.id.icon, true);
assertVisibility(R.id.app_info, false);
assertVisibility(R.id.text, true);
assertVisibility(R.id.origin, false);
assertVisibility(R.id.control_rating, false);
assertVisibility(R.id.play_logo, false);
Assert.assertEquals(TEST_TITLE, getTextForViewWithId(R.id.text));
}
/**
* Tests the view for {@link AppType#NATIVE}.
*/
@Test
@Feature({"Webapp"})
public void testNativeApp() {
initDialogView(AppType.NATIVE);
mAddToHomescreenDialogView.setNativeAppRating(2.3f);
mAddToHomescreenDialogView.setNativeInstallButtonText(TEST_NATIVE_ADD_TEXT);
assertVisibility(R.id.spinny, false);
assertVisibility(R.id.icon, true);
assertVisibility(R.id.app_info, true);
assertVisibility(R.id.text, false);
assertVisibility(R.id.origin, false);
assertVisibility(R.id.control_rating, true);
assertVisibility(R.id.play_logo, true);
Assert.assertEquals(TEST_TITLE, getTextForViewWithId(R.id.text));
PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
Assert.assertEquals(TEST_NATIVE_ADD_TEXT,
shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_TEXT));
}
@Test
@Feature({"Webapp"})
public void testAddButtonState() {
PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
// Assert 'Add' will be enabled for AppType#WEB_APK after #setCanSubmit(true) is called.
mAddToHomescreenDialogView.setType(AppType.WEB_APK);
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
mAddToHomescreenDialogView.setCanSubmit(true);
Assert.assertFalse(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
mAddToHomescreenDialogView.setCanSubmit(false);
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
// Assert 'Add' will be enabled for AppType#NATIVE after #setCanSubmit(true) is called.
mAddToHomescreenDialogView.setType(AppType.NATIVE);
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
mAddToHomescreenDialogView.setCanSubmit(true);
Assert.assertFalse(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
mAddToHomescreenDialogView.setCanSubmit(false);
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
// Assert 'Add' will be enabled for AppType#SHORTCUT after #setCanSubmit(true) is called and
// title EditText is not empty.
EditText titleText =
mAddToHomescreenDialogView.getParentViewForTest().findViewById(R.id.text);
mAddToHomescreenDialogView.setType(AppType.SHORTCUT);
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
mAddToHomescreenDialogView.setCanSubmit(true);
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
mAddToHomescreenDialogView.setCanSubmit(false);
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
titleText.setText(TEST_TITLE);
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
mAddToHomescreenDialogView.setCanSubmit(true);
Assert.assertFalse(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
titleText.setText("");
Assert.assertTrue(shownDialogModel.get(ModalDialogProperties.POSITIVE_BUTTON_DISABLED));
}
/**
* Tests whether the callback for clicking on the title or icon functions correctly.
*/
@Test
@Feature({"Webapp"})
public void testTitleClickCallback() {
initDialogView(AppType.NATIVE);
Assert.assertEquals(0, mTitleClickCallback.getCallCount());
mAddToHomescreenDialogView.getParentViewForTest().findViewById(R.id.name).performClick();
mAddToHomescreenDialogView.getParentViewForTest().findViewById(R.id.icon).performClick();
Assert.assertEquals(2, mTitleClickCallback.getCallCount());
Assert.assertEquals(2, mDismissCallback.getCallCount());
Assert.assertNotNull(mModalDialogManager.getDismissedDialogModel());
Assert.assertEquals(
mModalDialogManager.getDismissalCause(), DialogDismissalCause.ACTION_ON_CONTENT);
}
/**
* Tests whether the callback for dismissal functions correctly.
*/
@Test
@Feature({"Webapp"})
public void testDismissCallback() {
initDialogView(AppType.NATIVE);
PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
Assert.assertEquals(0, mDismissCallback.getCallCount());
shownDialogModel.get(ModalDialogProperties.CONTROLLER)
.onClick(shownDialogModel, ModalDialogProperties.ButtonType.NEGATIVE);
Assert.assertEquals(1, mDismissCallback.getCallCount());
}
/**
* Tests whether the callback for clicking on the 'Add' button functions correctly.
*/
@Test
@Feature({"Webapp"})
public void testInstallCallback() {
initDialogView(AppType.WEB_APK);
PropertyModel shownDialogModel = mModalDialogManager.getShownDialogModel();
Assert.assertEquals(0, mAddCallback.getCallCount());
shownDialogModel.get(ModalDialogProperties.CONTROLLER)
.onClick(shownDialogModel, ModalDialogProperties.ButtonType.POSITIVE);
Assert.assertEquals(1, mAddCallback.getCallCount());
}
private void initDialogView(@AppType int appType) {
mAddToHomescreenDialogView.setType(appType);
mAddToHomescreenDialogView.setTitle(TEST_TITLE);
mAddToHomescreenDialogView.setIcon(null, false);
mAddToHomescreenDialogView.setCanSubmit(true);
}
private void assertVisibility(int viewId, boolean isVisible) {
Assert.assertEquals(isVisible,
View.VISIBLE
== mAddToHomescreenDialogView.getParentViewForTest()
.findViewById(viewId)
.getVisibility());
}
private String getTextForViewWithId(int viewId) {
return ((TextView) mAddToHomescreenDialogView.getParentViewForTest().findViewById(viewId))
.getText()
.toString();
}
}
// 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.addtohomescreen;
import android.graphics.Bitmap;
import android.util.Pair;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* Tests for the {@link AddToHomescreenViewBinder} class.
*/
@RunWith(BaseRobolectricTestRunner.class)
public class AddToHomescreenViewBinderTest {
@Test
public void testViewBinder() {
AddToHomescreenDialogView view = Mockito.mock(AddToHomescreenDialogView.class);
Bitmap icon = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
// Construct the model.
PropertyModel viewModel =
new PropertyModel.Builder(AddToHomescreenProperties.ALL_KEYS).build();
viewModel.set(AddToHomescreenProperties.CAN_SUBMIT, true);
viewModel.set(AddToHomescreenProperties.ICON, new Pair<>(icon, false));
viewModel.set(AddToHomescreenProperties.NATIVE_INSTALL_BUTTON_TEXT, "Install");
viewModel.set(AddToHomescreenProperties.NATIVE_APP_RATING, 3.4f);
viewModel.set(AddToHomescreenProperties.TITLE, "My App");
viewModel.set(AddToHomescreenProperties.TYPE, AddToHomescreenProperties.AppType.NATIVE);
viewModel.set(AddToHomescreenProperties.URL, "google.com");
// Invoke the binder.
PropertyModelChangeProcessor.create(viewModel, view, AddToHomescreenViewBinder::bind);
// Verify that the binder called the right methods on the view, with the correct arguments.
Mockito.verify(view, Mockito.times(1)).setCanSubmit(true);
Mockito.verify(view, Mockito.times(1)).setIcon(icon, false);
Mockito.verify(view, Mockito.times(1)).setNativeInstallButtonText("Install");
Mockito.verify(view, Mockito.times(1)).setNativeAppRating(3.4f);
Mockito.verify(view, Mockito.times(1)).setTitle("My App");
Mockito.verify(view, Mockito.times(1)).setType(AddToHomescreenProperties.AppType.NATIVE);
Mockito.verify(view, Mockito.times(1)).setUrl("google.com");
}
}
......@@ -27,8 +27,8 @@
#include "content/public/browser/web_contents.h"
#include "net/base/url_util.h"
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertJavaStringToUTF16;
using base::android::ConvertJavaStringToUTF8;
using base::android::ConvertUTF8ToJavaString;
using base::android::JavaParamRef;
......@@ -73,14 +73,6 @@ AppBannerManagerAndroid::GetJavaBannerManager() const {
return base::android::ScopedJavaLocalRef<jobject>(java_banner_manager_);
}
base::android::ScopedJavaLocalRef<jobject>
AppBannerManagerAndroid::GetAddToHomescreenDialogForTesting(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jobj) {
return ui_delegate_ ? ui_delegate_->GetAddToHomescreenDialogForTesting()
: nullptr;
}
bool AppBannerManagerAndroid::IsRunningForTesting(
JNIEnv* env,
const JavaParamRef<jobject>& obj) {
......@@ -447,16 +439,13 @@ void JNI_AppBannerManager_SetDaysAfterDismissAndIgnoreToTrigger(
}
// static
void JNI_AppBannerManager_SetTimeDeltaForTesting(
JNIEnv* env,
jint days) {
void JNI_AppBannerManager_SetTimeDeltaForTesting(JNIEnv* env, jint days) {
AppBannerManager::SetTimeDeltaForTesting(days);
}
// static
void JNI_AppBannerManager_SetTotalEngagementToTrigger(
JNIEnv* env,
jdouble engagement) {
void JNI_AppBannerManager_SetTotalEngagementToTrigger(JNIEnv* env,
jdouble engagement) {
AppBannerSettingsHelper::SetTotalEngagementToTrigger(engagement);
}
......
......@@ -54,12 +54,6 @@ class AppBannerManagerAndroid
// Returns a reference to the Java-side AppBannerManager owned by this object.
const base::android::ScopedJavaLocalRef<jobject> GetJavaBannerManager() const;
// Returns a reference to the Java-side AddToHomescreenDialog owned by
// |ui_delegate_|, or null if it does not exist.
base::android::ScopedJavaLocalRef<jobject> GetAddToHomescreenDialogForTesting(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& jobj);
// Returns true if the banner pipeline is currently running.
bool IsRunningForTesting(JNIEnv* env,
const base::android::JavaParamRef<jobject>& jobj);
......
......@@ -108,12 +108,6 @@ void AppBannerUiDelegateAndroid::AddToHomescreen(
InstallApp(weak_manager_->web_contents());
}
const base::android::ScopedJavaLocalRef<jobject>
AppBannerUiDelegateAndroid::GetAddToHomescreenDialogForTesting() const {
return Java_AppBannerUiDelegateAndroid_getDialogForTesting(
base::android::AttachCurrentThread(), java_delegate_);
}
bool AppBannerUiDelegateAndroid::InstallApp(
content::WebContents* web_contents) {
has_user_interaction_ = true;
......@@ -218,20 +212,21 @@ bool AppBannerUiDelegateAndroid::ShowDialog() {
env, java_delegate_, java_app_title, java_bitmap, java_app_url);
}
void AppBannerUiDelegateAndroid::ShowNativeAppDetails() {
bool AppBannerUiDelegateAndroid::ShowNativeAppDetails() {
if (native_app_data_.is_null())
return;
return false;
Java_AppBannerUiDelegateAndroid_showAppDetails(
base::android::AttachCurrentThread(), java_delegate_, native_app_data_);
TrackDismissEvent(DISMISS_EVENT_BANNER_CLICK);
return true;
}
void AppBannerUiDelegateAndroid::ShowNativeAppDetails(
bool AppBannerUiDelegateAndroid::ShowNativeAppDetails(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj) {
ShowNativeAppDetails();
return ShowNativeAppDetails();
}
AppBannerUiDelegateAndroid::AppBannerUiDelegateAndroid(
......
......@@ -70,11 +70,6 @@ class AppBannerUiDelegateAndroid {
void AddToHomescreen(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
// Returns a reference to the Java-side AddToHomescreenDialog owned by this
// object, or null if it does not exist.
const base::android::ScopedJavaLocalRef<jobject>
GetAddToHomescreenDialogForTesting() const;
// Installs the app referenced by the data in this object. Returns |true| if
// the installation UI should be dismissed.
bool InstallApp(content::WebContents* web_contents);
......@@ -101,9 +96,9 @@ class AppBannerUiDelegateAndroid {
bool ShowDialog();
// Called by the UI layer to display the details for a native app.
void ShowNativeAppDetails();
bool ShowNativeAppDetails();
void ShowNativeAppDetails(JNIEnv* env,
bool ShowNativeAppDetails(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
private:
......
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