Commit f2ff0687 authored by Becky Zhou's avatar Becky Zhou Committed by Commit Bot

[TabModal] Add dialog view to tab modal javascript dialogs

Browser controls are accessible if the browser controls are not hidden
before the tab modal javascript dialog is shown. The app modal dialog
is also using the same dialog view now for consistency.

Bug: 687010
Change-Id: I6f36e8ef06129dd220d75d08878480672993fc72
Reviewed-on: https://chromium-review.googlesource.com/757166
Commit-Queue: Becky Zhou <huayinz@chromium.org>
Reviewed-by: default avatarTed Choc (back but slow, ping me) <tedchoc@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#526726}
parent f7106123
......@@ -54,6 +54,19 @@
android:inflatedId="@+id/bottombar"
android:layout="@layout/custom_tabs_bottombar" />
<ViewStub
android:id="@+id/tab_modal_dialog_container_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inflatedId="@+id/tab_modal_dialog_container" />
<!-- Please do not add anything in between tab_modal_dialog_container_stub and
tab_modal_dialog_container_sibling_view. -->
<ViewStub
android:id="@+id/tab_modal_dialog_container_sibling_view"
android:layout_width="0dp"
android:layout_height="0dp" />
<org.chromium.chrome.browser.widget.FadingBackgroundView
android:id="@+id/fading_focus_target"
android:layout_width="match_parent"
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/scrim"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/modal_dialog_scrim_color" />
</FrameLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 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. -->
<!-- TODO(huayinz): rename menu_bg or change the dialog background to the desired one. -->
<org.chromium.chrome.browser.widget.BoundedLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:chrome="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@drawable/menu_bg"
chrome:maxWidth="@dimen/dialog_max_width">
<android.support.v7.widget.DialogTitle
android:id="@+id/title"
android:textAppearance="@style/BlackHeadline2"
style="@style/AlertDialogContent" />
<ScrollView
android:layout_height="0dp"
android:layout_weight="1"
style="@style/AlertDialogContent">
<org.chromium.ui.widget.TextViewWithLeading
android:id="@+id/message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/BlackBody"
chrome:leading="20sp" />
</ScrollView>
<FrameLayout
android:id="@+id/custom"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<android.support.v7.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/buttonBarStyle" >
<Button
android:id="@+id/negative_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/buttonBarNegativeButtonStyle" />
<Button
android:id="@+id/positive_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?attr/buttonBarPositiveButtonStyle" />
</android.support.v7.widget.ButtonBarLayout>
</org.chromium.chrome.browser.widget.BoundedLinearLayout>
\ No newline at end of file
......@@ -19,7 +19,7 @@
android:layout_gravity="center"
android:orientation="vertical"
android:background="@drawable/menu_bg"
chrome:maxWidth="@dimen/promo_dialog_max_width" >
chrome:maxWidth="@dimen/dialog_max_width" >
<org.chromium.chrome.browser.widget.FadingEdgeScrollView
android:id="@+id/promo_container"
......
......@@ -124,6 +124,29 @@
<item name="chrometint">@color/dark_mode_tint</item>
</style>
<style name="ModalDialogTheme" parent="AlertDialogTheme">
<item name="android:windowFrame">@null</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowMinWidthMajor">100%</item>
<item name="android:windowMinWidthMinor">100%</item>
<item name="buttonBarStyle">@style/ModalDialogButtonBarStyle</item>
<item name="buttonBarButtonStyle">@style/ModalDialogButtonStyle</item>
</style>
<style name="ModalDialogButtonBarStyle" parent="Widget.AppCompat.ButtonBar.AlertDialog">
<item name="android:orientation">horizontal</item>
<item name="android:gravity">bottom|end</item>
<item name="android:paddingStart">@dimen/modal_dialog_control_padding_horizontal</item>
<item name="android:paddingEnd">@dimen/modal_dialog_control_padding_horizontal</item>
<item name="android:paddingTop">@dimen/modal_dialog_control_padding_vertical</item>
<item name="android:paddingBottom">@dimen/modal_dialog_control_padding_vertical</item>
</style>
<style name="ModalDialogButtonStyle" parent="Widget.AppCompat.Button.ButtonBar.AlertDialog">
<item name="android:textColor">@color/light_active_color</item>
</style>
<style name="SimpleDialog" parent="AlertDialogTheme">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
......
......@@ -139,7 +139,6 @@
<dimen name="promo_dialog_illustration_margin">24dp</dimen>
<dimen name="promo_dialog_illustration_width">150dp</dimen>
<dimen name="promo_dialog_padding">16dp</dimen>
<dimen name="promo_dialog_max_width">600dp</dimen>
<dimen name="promo_dialog_max_content_width">320dp</dimen>
<dimen name="promo_dialog_min_scrollable_height">100dp</dimen>
<dimen name="promo_dialog_title_text_size">23sp</dimen>
......@@ -190,6 +189,12 @@
<!-- Alert dialog -->
<dimen name="dialog_padding_top">@dimen/abc_dialog_padding_top_material</dimen>
<dimen name="dialog_padding_sides">@dimen/abc_dialog_padding_material</dimen>
<dimen name="dialog_max_width">600dp</dimen>
<!-- ModalDialogView dimensions -->
<dimen name="modal_dialog_control_padding_vertical">4dp</dimen>
<dimen name="modal_dialog_control_padding_horizontal">12dp</dimen>
<dimen name="tab_modal_scrim_vertical_margin">16dp</dimen>
<!-- Tab Strip Dimensions -->
<dimen name="tab_strip_height">0dp</dimen>
......
......@@ -93,6 +93,8 @@ import org.chromium.chrome.browser.metrics.LaunchMetrics;
import org.chromium.chrome.browser.metrics.StartupMetrics;
import org.chromium.chrome.browser.metrics.UmaSessionStats;
import org.chromium.chrome.browser.metrics.WebApkUma;
import org.chromium.chrome.browser.modaldialog.AppModalPresenter;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.nfc.BeamController;
......@@ -246,6 +248,7 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
private ContextualSearchManager mContextualSearchManager;
protected ReaderModeManager mReaderModeManager;
private SnackbarManager mSnackbarManager;
private ModalDialogManager mModalDialogManager;
private DataUseSnackbarController mDataUseSnackbarController;
private DataReductionPromoSnackbarController mDataReductionPromoSnackbarController;
private AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
......@@ -390,6 +393,8 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
mBottomSheetContentController.init(mBottomSheet, mTabModelSelector, this);
}
((BottomContainer) findViewById(R.id.bottom_container)).initialize(mFullscreenManager);
mModalDialogManager = createModalDialogManager();
}
@Override
......@@ -1180,6 +1185,21 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
return mSnackbarManager;
}
/**
* @return The {@link ModalDialogManager} created for this class.
*/
protected ModalDialogManager createModalDialogManager() {
return new ModalDialogManager(new AppModalPresenter(this), ModalDialogManager.APP_MODAL);
}
/**
* @return The {@link ModalDialogManager} that manages the display of modal dialogs (e.g.
* JavaScript dialogs).
*/
public ModalDialogManager getModalDialogManager() {
return mModalDialogManager;
}
protected Drawable getBackgroundDrawable() {
return new ColorDrawable(
ApiCompatibilityUtils.getColor(getResources(), R.color.light_background_color));
......
......@@ -85,6 +85,8 @@ import org.chromium.chrome.browser.metrics.LaunchMetrics;
import org.chromium.chrome.browser.metrics.MainIntentBehaviorMetrics;
import org.chromium.chrome.browser.metrics.StartupMetrics;
import org.chromium.chrome.browser.metrics.UmaUtils;
import org.chromium.chrome.browser.modaldialog.ModalDialogManager;
import org.chromium.chrome.browser.modaldialog.TabModalLifetimeHandler;
import org.chromium.chrome.browser.multiwindow.MultiInstanceChromeTabbedActivity;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
......@@ -261,6 +263,8 @@ public class ChromeTabbedActivity
private ScreenshotMonitor mScreenshotMonitor;
private TabModalLifetimeHandler mTabModalHandler;
private boolean mUIInitialized;
private Boolean mMergeTabsOnResume;
......@@ -1901,6 +1905,8 @@ public class ChromeTabbedActivity
super.onOmniboxFocusChanged(hasFocus);
mMainIntentMetrics.onOmniboxFocused();
mTabModalHandler.onOmniboxFocusChanged(hasFocus);
}
private void recordBackPressedUma(String logMessage, @BackPressedResult int action) {
......@@ -1942,6 +1948,8 @@ public class ChromeTabbedActivity
if (getBottomSheet() != null && getBottomSheet().handleBackPress()) return true;
if (mTabModalHandler.handleBackPress()) return true;
if (currentTab == null) {
recordBackPressedUma("currentTab is null", BACK_PRESSED_TAB_IS_NULL);
moveTaskToBack(true);
......@@ -2158,6 +2166,11 @@ public class ChromeTabbedActivity
mUndoBarPopupController = null;
}
if (mTabModalHandler != null) {
mTabModalHandler.destroy();
mTabModalHandler = null;
}
super.onDestroyInternal();
FeatureUtilities.finalizePendingFeatures();
......@@ -2212,6 +2225,13 @@ public class ChromeTabbedActivity
return getLayoutManager().getOverviewListLayout();
}
@Override
protected ModalDialogManager createModalDialogManager() {
ModalDialogManager manager = super.createModalDialogManager();
mTabModalHandler = new TabModalLifetimeHandler(this, manager);
return manager;
}
// App Menu related code -----------------------------------------------------------------------
@Override
......
// Copyright 2017 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.jsdialog;
import android.support.annotation.StringRes;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.modaldialog.ModalDialogView;
/**
* The JavaScript dialog that is either app modal or tab modal.
*/
public class JavascriptModalDialogView extends ModalDialogView {
private final TextView mMessageView;
private final EditText mPromptEditText;
private final CheckBox mSuppressCheckBox;
private final View mScrollView;
/**
* Create a {@link JavascriptModalDialogView} with the specified properties.
* @param controller The controller for the dialog view.
* @param title The title of the dialog view.
* @param message The message of the dialog view.
* @param promptText The promptText of the dialog view. If null,
* prompt edit text will not be shown.
* @param shouldShowSuppressCheckBox Whether the suppress check box should be shown.
* @param positiveButtonTextId The string resource id of the positive button.
* @param negativeButtonTextId The string resource id of the negative button.
* @return A {@link JavascriptModalDialogView} with the specified properties.
*/
public static JavascriptModalDialogView create(Controller controller, String title,
String message, String promptText, boolean shouldShowSuppressCheckBox,
@StringRes int positiveButtonTextId, @StringRes int negativeButtonTextId) {
Params params = new Params();
params.title = title;
params.positiveButtonTextId = positiveButtonTextId;
params.negativeButtonTextId = negativeButtonTextId;
return new JavascriptModalDialogView(
controller, params, message, promptText, shouldShowSuppressCheckBox);
}
private JavascriptModalDialogView(Controller controller, Params params, String message,
String promptText, boolean shouldShowSuppressCheckBox) {
super(controller, params);
LayoutInflater inflater = LayoutInflater.from(getContext());
View customLayout = inflater.inflate(R.layout.js_modal_dialog, null);
params.customView = customLayout;
mScrollView = params.customView.findViewById(R.id.js_modal_dialog_scroll_view);
mMessageView = customLayout.findViewById(R.id.js_modal_dialog_message);
mPromptEditText = customLayout.findViewById(R.id.js_modal_dialog_prompt);
mSuppressCheckBox = customLayout.findViewById(R.id.suppress_js_modal_dialogs);
mMessageView.setText(message);
setPromptText(promptText);
setSuppressCheckBoxVisibility(shouldShowSuppressCheckBox);
}
@Override
protected void prepareBeforeShow() {
super.prepareBeforeShow();
// If the message is null or empty do not display the message text view.
// Hide parent scroll view instead of text view in order to prevent ui discrepancies.
if (mMessageView.getText().length() == 0) {
mScrollView.setVisibility(View.GONE);
} else {
// TODO(huayinz): See if View#canScrollVertictically() can be used for checking if
// scrollView is scrollable.
mScrollView.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
boolean isScrollable =
v.getMeasuredHeight() - v.getPaddingTop() - v.getPaddingBottom()
< ((ViewGroup) v).getChildAt(0).getMeasuredHeight();
v.setFocusable(isScrollable);
});
}
}
/**
* @param promptText Prompt text for prompt dialog. If null, prompt text is not visible.
*/
private void setPromptText(String promptText) {
if (promptText == null) return;
mPromptEditText.setVisibility(View.VISIBLE);
if (promptText.length() > 0) {
mPromptEditText.setText(promptText);
mPromptEditText.selectAll();
}
}
/**
* @return The prompt text edited by user.
*/
public String getPromptText() {
return mPromptEditText.getText().toString();
}
/**
* @param visible Whether the suppress check box should be visible. The check box should only
* be set visible if applicable for app modal JavaScript dialogs.
*/
private void setSuppressCheckBoxVisibility(boolean visible) {
mSuppressCheckBox.setVisibility(visible ? View.VISIBLE : View.GONE);
}
/**
* @return Whether the suppress check box is checked by user.
*/
public boolean isSuppressCheckBoxChecked() {
return mSuppressCheckBox.isChecked();
}
}
// Copyright 2017 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.modaldialog;
import android.app.Activity;
import android.app.Dialog;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.widget.AlwaysDismissedDialog;
/** The presenter that shows a {@link ModalDialogView} in an Android dialog. */
public class AppModalPresenter extends ModalDialogManager.Presenter {
private final Activity mActivity;
private Dialog mDialog;
public AppModalPresenter(Activity activity) {
mActivity = activity;
}
@Override
protected void addDialogView(View dialogView) {
mDialog = new AlwaysDismissedDialog(mActivity, R.style.ModalDialogTheme);
mDialog.setOnCancelListener(dialogInterface -> cancelCurrentDialog());
ViewGroup container = (ViewGroup) LayoutInflater.from(mActivity).inflate(
R.layout.modal_dialog_container, null);
mDialog.setContentView(container);
FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(ViewGroup.MarginLayoutParams.MATCH_PARENT,
ViewGroup.MarginLayoutParams.WRAP_CONTENT, Gravity.CENTER);
container.addView(dialogView, params);
mDialog.show();
}
@Override
protected void removeDialogView(View dialogView) {
// Dismiss the currently showing dialog.
if (mDialog != null) mDialog.dismiss();
mDialog = null;
}
}
// Copyright 2017 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.modaldialog;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
import org.chromium.base.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* Manager for managing the display of a queue of {@link ModalDialogView}s.
*/
public class ModalDialogManager {
/**
* Present a {@link ModalDialogView} in a container.
*/
public static abstract class Presenter {
private Runnable mCancelCallback;
private ModalDialogView mModalDialog;
private View mCurrentView;
/**
* @param dialog The dialog that's currently showing in this presenter. If null, no dialog
* is currently showing.
*/
private void setModalDialog(
@Nullable ModalDialogView dialog, @Nullable Runnable cancelCallback) {
if (dialog == null) {
removeDialogView(mCurrentView);
mModalDialog = null;
mCancelCallback = null;
} else {
assert mModalDialog
== null : "Should call setModalDialog(null) before setting a modal dialog.";
mModalDialog = dialog;
mCurrentView = dialog.getView();
mCancelCallback = cancelCallback;
addDialogView(mCurrentView);
}
}
/**
* Run the cached cancel callback and reset the cached callback.
*/
protected final void cancelCurrentDialog() {
if (mCancelCallback == null) return;
// Set #mCancelCallback to null before calling the callback to avoid it being
// updated during the callback.
Runnable callback = mCancelCallback;
mCancelCallback = null;
callback.run();
}
/**
* @return The modal dialog that this presenter is showing.
*/
protected final ModalDialogView getModalDialog() {
return mModalDialog;
}
/**
* Add the specified {@link ModalDialogView} in a container.
* @param dialogView The {@link ModalDialogView} that needs to be shown.
*/
protected abstract void addDialogView(View dialogView);
/**
* Remove the specified {@link ModalDialogView} from a container.
* @param dialogView The {@link ModalDialogView} that needs to be removed.
*/
protected abstract void removeDialogView(View dialogView);
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({APP_MODAL, TAB_MODAL})
public @interface ModalDialogType {}
public static final int APP_MODAL = 0;
public static final int TAB_MODAL = 1;
/** Mapping of the {@link Presenter}s and the type of dialogs they are showing. */
private final SparseArray<Presenter> mPresenters = new SparseArray<>();
/** The list of pending dialogs */
private final List<Pair<ModalDialogView, Integer>> mPendingDialogs = new ArrayList<>();
/** The default presenter to be used if a specified type is not supported. */
private final Presenter mDefaultPresenter;
/** The presenter of the type of the dialog that is currently showing. */
private Presenter mCurrentPresenter;
/**
* Constructor for initializing default {@link Presenter}.
* @param defaultPresenter The default presenter to be used when no presenter specified.
* @param defaultType The dialog type of the default presenter.
*/
public ModalDialogManager(
@NonNull Presenter defaultPresenter, @ModalDialogType int defaultType) {
mDefaultPresenter = defaultPresenter;
registerPresenter(defaultPresenter, defaultType);
}
/**
* Register a {@link Presenter} that shows a specific type of dialog. Note that only one
* presenter of each type can be registered.
* @param presenter The {@link Presenter} to be registered.
* @param dialogType The type of the dialog shown by the specified presenter.
*/
public void registerPresenter(Presenter presenter, @ModalDialogType int dialogType) {
assert mPresenters.get(dialogType)
== null : "Only one presenter can be registered for each type.";
mPresenters.put(dialogType, presenter);
}
/**
* @return Whether a dialog is currently showing.
*/
public boolean isShowing() {
return mCurrentPresenter != null;
}
/**
* Show the specified dialog. If another dialog is currently showing, the specified dialog will
* be added to the pending dialog list.
* @param dialog The dialog to be shown or added to pending list.
* @param dialogType The type of the dialog to be shown.
*/
public void showDialog(ModalDialogView dialog, @ModalDialogType int dialogType) {
if (isShowing()) {
mPendingDialogs.add(Pair.create(dialog, dialogType));
return;
}
dialog.prepareBeforeShow();
mCurrentPresenter = mPresenters.get(dialogType, mDefaultPresenter);
mCurrentPresenter.setModalDialog(dialog, () -> cancelDialog(dialog));
}
/**
* Dismiss the specified dialog. If the dialog is not currently showing, it will be removed from
* the pending dialog list.
* @param dialog The dialog to be dismissed or removed from pending list.
*/
public void dismissDialog(ModalDialogView dialog) {
if (dialog != mCurrentPresenter.getModalDialog()) {
for (int i = 0; i < mPendingDialogs.size(); ++i) {
if (mPendingDialogs.get(i).first == dialog) {
mPendingDialogs.remove(i);
break;
}
}
return;
}
if (!isShowing()) return;
assert dialog == mCurrentPresenter.getModalDialog();
mCurrentPresenter.setModalDialog(null, null);
mCurrentPresenter = null;
if (!mPendingDialogs.isEmpty()) {
Pair<ModalDialogView, Integer> nextDialog = mPendingDialogs.remove(0);
showDialog(nextDialog.first, nextDialog.second);
}
}
/**
* Cancel showing the specified dialog. This is essentially the same as
* {@link #dismissDialog(ModalDialogView)} but will also call the onCancelled callback from the
* modal dialog.
* @param dialog The dialog to be cancelled.
*/
public void cancelDialog(ModalDialogView dialog) {
dismissDialog(dialog);
dialog.getController().onCancel();
}
/**
* Dismiss the dialog currently shown and remove all pending dialogs and call the onCancelled
* callbacks from the modal dialogs.
*/
protected void cancelAllDialogs() {
while (!mPendingDialogs.isEmpty()) {
mPendingDialogs.remove(0).first.getController().onCancel();
}
if (isShowing()) cancelDialog(mCurrentPresenter.getModalDialog());
}
@VisibleForTesting
List getPendingDialogsForTest() {
return mPendingDialogs;
}
@VisibleForTesting
Presenter getPresenterForTest(@ModalDialogType int dialogType) {
return mPresenters.get(dialogType);
}
@VisibleForTesting
Presenter getCurrentPresenterForTest() {
return mCurrentPresenter;
}
}
// Copyright 2017 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.modaldialog;
import android.content.Context;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.text.TextUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Generic builder for app modal or tab modal alert dialogs.
*/
public class ModalDialogView implements View.OnClickListener {
/**
* Interface that controls the actions on the modal dialog.
*/
public interface Controller {
/**
* Handle click event of the buttons on the dialog.
* @param buttonType The type of the button.
*/
void onClick(@ButtonType int buttonType);
/**
* Handle dismiss event when the dialog is not dismissed by actions on the dialog such as
* back press, and on tab modal dialog, tab switcher button click.
*/
void onCancel();
}
/** Parameters that can be used to create a new ModalDialogView. */
public static class Params {
/** Optional: The String to show as the dialog title. */
public String title;
/** Optional: The String to show as descriptive text. */
public String message;
/**
* Optional: The customized View to show in the dialog. Note that the message and the
* custom view cannot be set together.
*/
public View customView;
/** Optional: Resource ID of the String to show on the positive button. */
public @StringRes int positiveButtonTextId;
/** Optional: Resource ID of the String to show on the negative button. */
public @StringRes int negativeButtonTextId;
}
@IntDef({BUTTON_POSITIVE, BUTTON_NEGATIVE})
@Retention(RetentionPolicy.SOURCE)
public @interface ButtonType {}
public static final int BUTTON_POSITIVE = 0;
public static final int BUTTON_NEGATIVE = 1;
private final Controller mController;
private final Context mContext;
private final Params mParams;
private final View mDialogView;
private final TextView mTitleView;
private final TextView mMessageView;
private final ViewGroup mCustomView;
private final Button mPositiveButton;
private final Button mNegativeButton;
/**
* Constructor for initializing controller and views.
* @param controller The controller for this dialog.
*/
public ModalDialogView(@NonNull Controller controller, @NonNull Params params) {
mController = controller;
mContext = new ContextThemeWrapper(
ContextUtils.getApplicationContext(), R.style.ModalDialogTheme);
mParams = params;
mDialogView = LayoutInflater.from(mContext).inflate(R.layout.modal_dialog_view, null);
mTitleView = mDialogView.findViewById(R.id.title);
mMessageView = mDialogView.findViewById(R.id.message);
mCustomView = mDialogView.findViewById(R.id.custom);
mPositiveButton = mDialogView.findViewById(R.id.positive_button);
mNegativeButton = mDialogView.findViewById(R.id.negative_button);
}
@Override
public void onClick(View view) {
if (view == mPositiveButton) {
mController.onClick(BUTTON_POSITIVE);
} else if (view == mNegativeButton) {
mController.onClick(BUTTON_NEGATIVE);
}
}
/**
* Prepare the contents before showing the dialog.
*/
protected void prepareBeforeShow() {
if (TextUtils.isEmpty(mParams.title)) {
mTitleView.setVisibility(View.GONE);
} else {
mTitleView.setText(mParams.title);
}
if (TextUtils.isEmpty(mParams.message)) {
((View) mMessageView.getParent()).setVisibility(View.GONE);
} else {
assert mParams.customView == null;
mMessageView.setText(mParams.message);
}
if (mParams.customView != null) {
if (mParams.customView.getParent() != null) {
((ViewGroup) mParams.customView.getParent()).removeView(mParams.customView);
}
mCustomView.addView(mParams.customView);
} else {
mCustomView.setVisibility(View.GONE);
}
if (mParams.positiveButtonTextId == 0) {
mPositiveButton.setVisibility(View.GONE);
} else {
mPositiveButton.setText(mParams.positiveButtonTextId);
mPositiveButton.setOnClickListener(this);
}
if (mParams.negativeButtonTextId == 0) {
mNegativeButton.setVisibility(View.GONE);
} else {
mNegativeButton.setText(mParams.negativeButtonTextId);
mNegativeButton.setOnClickListener(this);
}
}
/**
* @return The {@link Context} with the modal dialog theme set.
*/
public Context getContext() {
return mContext;
}
/**
* @return The content view of this dialog.
*/
public View getView() {
return mDialogView;
}
/**
* @return The controller that controls the actions on the dialogs.
*/
public Controller getController() {
return mController;
}
}
// Copyright 2017 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.modaldialog;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabObserver;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver;
/**
* Class responsible for handling dismissal of a tab modal dialog on user actions outside the tab
* modal dialog.
*/
public class TabModalLifetimeHandler {
/** The observer to dismiss all dialogs when the attached tab is not interactable. */
private final TabObserver mTabObserver = new EmptyTabObserver() {
@Override
public void onInteractabilityChanged(boolean isInteractable) {
if (!isInteractable && mPresenter.getModalDialog() != null) {
mManager.cancelAllDialogs();
}
}
};
private final ModalDialogManager mManager;
private final TabModalPresenter mPresenter;
private final TabModelSelectorTabModelObserver mTabModelObserver;
private final boolean mHasBottomControls;
private Tab mActiveTab;
/**
* @param activity The {@link ChromeActivity} that this handler is attached to.
* @param manager The {@link ModalDialogManager} that this handler handles.
*/
public TabModalLifetimeHandler(ChromeActivity activity, ModalDialogManager manager) {
mManager = manager;
mPresenter = new TabModalPresenter(activity);
mManager.registerPresenter(mPresenter, ModalDialogManager.TAB_MODAL);
mHasBottomControls = activity.getBottomSheet() != null;
TabModelSelector tabModelSelector = activity.getTabModelSelector();
mTabModelObserver = new TabModelSelectorTabModelObserver(tabModelSelector) {
@Override
public void didSelectTab(Tab tab, TabModel.TabSelectionType type, int lastId) {
if (mActiveTab != null) mActiveTab.removeObserver(mTabObserver);
mActiveTab = tabModelSelector.getCurrentTab();
if (mActiveTab != null) mActiveTab.addObserver(mTabObserver);
}
};
}
/**
* Notified when the focus of the omnibox has changed.
* @param hasFocus Whether the omnibox currently has focus.
*/
public void onOmniboxFocusChanged(boolean hasFocus) {
// If has bottom controls, the view hierarchy will be updated by mBottomSheetObserver.
if (mPresenter.getModalDialog() != null && !mHasBottomControls) {
mPresenter.updateContainerHierarchy(!hasFocus);
}
}
/**
* Handle a back press event.
*/
public boolean handleBackPress() {
if (mPresenter.getModalDialog() == null) return false;
mPresenter.cancelCurrentDialog();
return true;
}
/**
* Remove any remaining dependencies.
*/
public void destroy() {
mTabModelObserver.destroy();
}
}
......@@ -234,6 +234,7 @@ public class Tab
private boolean mIsClosing;
private boolean mIsShowingErrorPage;
private boolean mIsShowingTabModalDialog;
private Bitmap mFavicon;
......@@ -805,6 +806,13 @@ public class Tab
return getWebContents() != null && getWebContents().isShowingInterstitialPage();
}
/**
* @return Whether a tab modal dialog is showing.
*/
public boolean isShowingTabModalDialog() {
return mIsShowingTabModalDialog;
}
/**
* @return Whether the {@link Tab} is currently showing an error page.
*/
......@@ -3358,6 +3366,17 @@ public class Tab
hideMediaDownloadInProductHelp();
}
/**
* Handle browser controls when a tab modal dialog is shown.
* @param isShowing Whether a tab modal dialog is showing.
*/
public void onTabModalDialogStateChanged(boolean isShowing) {
mIsShowingTabModalDialog = isShowing;
if (mFullscreenManager == null) return;
mFullscreenManager.setPositionsForTabToNonFullscreen();
updateBrowserControlsState(BrowserControlsState.SHOWN, false);
}
@CalledByNative
private void showMediaDownloadInProductHelp(int x, int y, int width, int height) {
// If we are not currently showing the widget, ask the tracker if we can show it.
......
......@@ -153,6 +153,7 @@ public class TabStateBrowserControlsVisibilityDelegate
enableHidingBrowserControls &= (mTab.getFullscreenManager() != null);
enableHidingBrowserControls &= DeviceClassManager.enableFullscreen();
enableHidingBrowserControls &= !mIsFullscreenWaitingForLoad;
enableHidingBrowserControls &= !mTab.isShowingTabModalDialog();
return enableHidingBrowserControls;
}
......
......@@ -559,6 +559,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/invalidation/InvalidationController.java",
"java/src/org/chromium/chrome/browser/invalidation/InvalidationServiceFactory.java",
"java/src/org/chromium/chrome/browser/invalidation/UniqueIdInvalidationClientNameGenerator.java",
"java/src/org/chromium/chrome/browser/jsdialog/JavascriptModalDialogView.java",
"java/src/org/chromium/chrome/browser/locale/DefaultSearchEngineDialogHelper.java",
"java/src/org/chromium/chrome/browser/locale/DefaultSearchEnginePromoDialog.java",
"java/src/org/chromium/chrome/browser/locale/LocaleManager.java",
......@@ -632,6 +633,11 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/metrics/VariationsSession.java",
"java/src/org/chromium/chrome/browser/metrics/WebApkUma.java",
"java/src/org/chromium/chrome/browser/metrics/WebappUma.java",
"java/src/org/chromium/chrome/browser/modaldialog/AppModalPresenter.java",
"java/src/org/chromium/chrome/browser/modaldialog/ModalDialogManager.java",
"java/src/org/chromium/chrome/browser/modaldialog/ModalDialogView.java",
"java/src/org/chromium/chrome/browser/modaldialog/TabModalLifetimeHandler.java",
"java/src/org/chromium/chrome/browser/modaldialog/TabModalPresenter.java",
"java/src/org/chromium/chrome/browser/mojo/ChromeInterfaceRegistrar.java",
"java/src/org/chromium/chrome/browser/multiwindow/MultiInstanceChromeTabbedActivity.java",
"java/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtils.java",
......@@ -1601,6 +1607,7 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/metrics/PageLoadMetricsTest.java",
"javatests/src/org/chromium/chrome/browser/metrics/StartupLoadingMetricsTest.java",
"javatests/src/org/chromium/chrome/browser/metrics/UkmIncognitoTest.java",
"javatests/src/org/chromium/chrome/browser/modaldialog/ModalDialogManagerTest.java",
"javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestHelper.java",
"javatests/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsTest.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