| // Copyright 2018 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.autofill_assistant; |
| |
| import android.view.View; |
| import android.view.ViewGroup; |
| |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.chrome.autofill_assistant.R; |
| import org.chromium.chrome.browser.ChromeActivity; |
| import org.chromium.chrome.browser.autofill_assistant.carousel.AssistantCarouselCoordinator; |
| import org.chromium.chrome.browser.autofill_assistant.details.AssistantDetailsCoordinator; |
| import org.chromium.chrome.browser.autofill_assistant.header.AssistantHeaderCoordinator; |
| import org.chromium.chrome.browser.autofill_assistant.payment.AssistantPaymentRequestCoordinator; |
| import org.chromium.chrome.browser.help.HelpAndFeedback; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.chrome.browser.snackbar.Snackbar; |
| import org.chromium.chrome.browser.snackbar.SnackbarManager; |
| |
| /** |
| * The main coordinator for the Autofill Assistant, responsible for instantiating all other |
| * sub-components and shutting down the Autofill Assistant. |
| */ |
| class AssistantCoordinator implements TouchEventFilterView.Delegate { |
| interface Delegate { |
| /** |
| * Completely stop the Autofill Assistant. |
| */ |
| void stop(); |
| |
| /** |
| * Asks for an update of the touchable area. |
| */ |
| void updateTouchableArea(); |
| |
| /** |
| * Called when interaction within allowed touchable area was detected. The interaction |
| * could be any gesture. |
| */ |
| void onUserInteractionInsideTouchableArea(); |
| } |
| |
| private static final String FEEDBACK_CATEGORY_TAG = |
| "com.android.chrome.USER_INITIATED_FEEDBACK_REPORT_AUTOFILL_ASSISTANT"; |
| private static final int SNACKBAR_DELAY_MS = 5_000; |
| private static final int GRACEFUL_SHUTDOWN_DELAY_MS = 5_000; |
| |
| private final ChromeActivity mActivity; |
| private final Delegate mDelegate; |
| |
| private final AssistantModel mModel; |
| private final View mAssistantView; |
| |
| private final AssistantBottomBarCoordinator mBottomBarCoordinator; |
| private final AssistantHeaderCoordinator mHeaderCoordinator; |
| private final AssistantDetailsCoordinator mDetailsCoordinator; |
| private final AssistantPaymentRequestCoordinator mPaymentRequestCoordinator; |
| private final AssistantCarouselCoordinator mCarouselCoordinator; |
| private final AssistantKeyboardCoordinator mKeyboardCoordinator; |
| private final AssistantOverlayCoordinator mOverlayCoordinator; |
| |
| private boolean mIsShuttingDownGracefully; |
| |
| AssistantCoordinator(ChromeActivity activity, Delegate delegate) { |
| mActivity = activity; |
| mDelegate = delegate; |
| mModel = new AssistantModel(); |
| |
| // Inflate autofill_assistant_sheet layout and add it to the main coordinator view. |
| ViewGroup coordinator = activity.findViewById(R.id.coordinator); |
| mAssistantView = activity.getLayoutInflater() |
| .inflate(R.layout.autofill_assistant_sheet, coordinator) |
| .findViewById(R.id.autofill_assistant); |
| |
| // Instantiate child components. |
| mBottomBarCoordinator = new AssistantBottomBarCoordinator( |
| mAssistantView, mActivity.getResources().getDisplayMetrics()); |
| mHeaderCoordinator = new AssistantHeaderCoordinator( |
| mActivity, mBottomBarCoordinator.getView(), mModel.getHeaderModel()); |
| mCarouselCoordinator = new AssistantCarouselCoordinator( |
| mActivity, mBottomBarCoordinator::onChildVisibilityChanged); |
| mDetailsCoordinator = new AssistantDetailsCoordinator( |
| mActivity, mBottomBarCoordinator::onChildVisibilityChanged); |
| mPaymentRequestCoordinator = new AssistantPaymentRequestCoordinator( |
| mActivity, mBottomBarCoordinator::onChildVisibilityChanged); |
| mKeyboardCoordinator = new AssistantKeyboardCoordinator(activity); |
| mOverlayCoordinator = new AssistantOverlayCoordinator(activity, mAssistantView, this); |
| |
| // Attach child views to the bottom bar. |
| mBottomBarCoordinator.setDetailsView(mDetailsCoordinator.getView()); |
| mBottomBarCoordinator.setPaymentRequestView(mPaymentRequestCoordinator.getView()); |
| mBottomBarCoordinator.setCarouselView(mCarouselCoordinator.getView()); |
| |
| // Details, PR and carousel are initially hidden. |
| mDetailsCoordinator.setVisible(false); |
| mPaymentRequestCoordinator.setVisible(false); |
| mCarouselCoordinator.setVisible(false); |
| |
| showAssistantView(); |
| } |
| |
| /** |
| * Shut down the Autofill Assistant immediately, without showing a message. |
| */ |
| public void shutdownImmediately() { |
| detachAssistantView(); |
| mOverlayCoordinator.destroy(); |
| mDelegate.stop(); |
| } |
| |
| /** |
| * Schedule the shut down of the Autofill Assistant such that any status message currently |
| * visible will still be shown for a few seconds before shutting down. Optionally replace |
| * the status message with a generic error message iff {@code showGiveUpMessage} is true. |
| */ |
| public void gracefulShutdown(boolean showGiveUpMessage) { |
| mIsShuttingDownGracefully = true; |
| |
| // Make sure bottom bar is expanded. |
| mBottomBarCoordinator.expand(); |
| |
| // Hide everything except header. |
| mOverlayCoordinator.hide(); |
| mDetailsCoordinator.setVisible(false); |
| mPaymentRequestCoordinator.setVisible(false); |
| mCarouselCoordinator.setVisible(false); |
| |
| if (showGiveUpMessage) { |
| mHeaderCoordinator.setStatusMessage( |
| mActivity.getString(R.string.autofill_assistant_give_up)); |
| } |
| ThreadUtils.postOnUiThreadDelayed(this::shutdownImmediately, GRACEFUL_SHUTDOWN_DELAY_MS); |
| } |
| |
| /** |
| * Shut down the Autofill Assistant and close the current Chrome tab. |
| */ |
| public void close() { |
| shutdownImmediately(); |
| mActivity.finish(); |
| } |
| |
| /** |
| * Show the onboarding screen and call {@code onAccept} if the user agreed to proceed, shutdown |
| * otherwise. |
| */ |
| public void showOnboarding(Runnable onAccept) { |
| // Hide header buttons. |
| mHeaderCoordinator.setFeedbackButtonVisible(false); |
| mHeaderCoordinator.setCloseButtonVisible(false); |
| |
| // Show overlay to prevent user from interacting with the page during onboarding. |
| mOverlayCoordinator.showFullOverlay(); |
| |
| AssistantOnboardingCoordinator.show(mActivity, mBottomBarCoordinator.getView()) |
| .then(accepted -> { |
| if (!accepted) { |
| shutdownImmediately(); |
| return; |
| } |
| |
| // Show header buttons. |
| mHeaderCoordinator.setFeedbackButtonVisible(true); |
| mHeaderCoordinator.setCloseButtonVisible(true); |
| |
| // Hide overlay. |
| mOverlayCoordinator.hide(); |
| |
| onAccept.run(); |
| }); |
| } |
| |
| /** |
| * Show {@code message} to the user, unless we are shutting down. |
| */ |
| public void showStatusMessage(String message) { |
| if (!mIsShuttingDownGracefully) { |
| mHeaderCoordinator.setStatusMessage(message); |
| } |
| } |
| |
| /** |
| * Get the model representing the current state of the UI. |
| */ |
| |
| public AssistantModel getModel() { |
| return mModel; |
| } |
| |
| // Getters to retrieve the sub coordinators. |
| |
| public AssistantBottomBarCoordinator getBottomBarCoordinator() { |
| return mBottomBarCoordinator; |
| } |
| |
| public AssistantHeaderCoordinator getHeaderCoordinator() { |
| return mHeaderCoordinator; |
| } |
| |
| public AssistantDetailsCoordinator getDetailsCoordinator() { |
| return mDetailsCoordinator; |
| } |
| |
| public AssistantPaymentRequestCoordinator getPaymentRequestCoordinator() { |
| return mPaymentRequestCoordinator; |
| } |
| |
| public AssistantCarouselCoordinator getCarouselCoordinator() { |
| return mCarouselCoordinator; |
| } |
| |
| public AssistantKeyboardCoordinator getKeyboardCoordinator() { |
| return mKeyboardCoordinator; |
| } |
| |
| public AssistantOverlayCoordinator getOverlayCoordinator() { |
| return mOverlayCoordinator; |
| } |
| |
| /** |
| * Dismiss the assistant view and show a cancellable snackbar alerting the user that the |
| * Autofill assistant is shutting down. |
| */ |
| public void dismissAndShowSnackbar(String message) { |
| if (mIsShuttingDownGracefully) { |
| shutdownImmediately(); |
| return; |
| } |
| |
| hideAssistantView(); |
| |
| Snackbar snackBar = |
| Snackbar.make(message, |
| new SnackbarManager.SnackbarController() { |
| @Override |
| public void onAction(Object actionData) { |
| // Shutdown was cancelled. |
| showAssistantView(); |
| } |
| |
| @Override |
| public void onDismissNoAction(Object actionData) { |
| shutdownImmediately(); |
| } |
| }, |
| Snackbar.TYPE_ACTION, Snackbar.UMA_AUTOFILL_ASSISTANT_STOP_UNDO) |
| .setAction(mActivity.getString(R.string.undo), /* actionData= */ null); |
| snackBar.setSingleLine(false); |
| snackBar.setDuration(SNACKBAR_DELAY_MS); |
| mActivity.getSnackbarManager().showSnackbar(snackBar); |
| } |
| |
| private void dismissAndShowSnackbar(int message) { |
| dismissAndShowSnackbar(mActivity.getString(message)); |
| } |
| |
| /** |
| * Show the Chrome feedback form. |
| */ |
| public void showFeedback(String debugContext) { |
| HelpAndFeedback.getInstance(mActivity).showFeedback(mActivity, Profile.getLastUsedProfile(), |
| mActivity.getActivityTab().getUrl(), FEEDBACK_CATEGORY_TAG, |
| FeedbackContext.buildContextString(mActivity, debugContext, |
| mDetailsCoordinator.getCurrentDetails(), |
| mHeaderCoordinator.getStatusMessage(), 4)); |
| } |
| |
| // Implementation of methods from {@link TouchEventFilterView.Delegate}. |
| |
| @Override |
| public void onUnexpectedTaps() { |
| dismissAndShowSnackbar(R.string.autofill_assistant_maybe_give_up); |
| } |
| |
| @Override |
| public void updateTouchableArea() { |
| mDelegate.updateTouchableArea(); |
| } |
| |
| @Override |
| public void onUserInteractionInsideTouchableArea() { |
| mDelegate.onUserInteractionInsideTouchableArea(); |
| } |
| |
| // Private methods. |
| |
| private void showAssistantView() { |
| mAssistantView.setVisibility(View.VISIBLE); |
| mKeyboardCoordinator.enableListenForKeyboardVisibility(true); |
| |
| mBottomBarCoordinator.expand(); |
| mBottomBarCoordinator.getView().announceForAccessibility( |
| mActivity.getString(R.string.autofill_assistant_available_accessibility)); |
| } |
| |
| private void hideAssistantView() { |
| mAssistantView.setVisibility(View.GONE); |
| mKeyboardCoordinator.enableListenForKeyboardVisibility(false); |
| } |
| |
| private void detachAssistantView() { |
| mActivity.<ViewGroup>findViewById(R.id.coordinator).removeView(mAssistantView); |
| } |
| } |