blob: 821a46ef8574a8a8872319e7afd48b3d93e7e0b8 [file] [log] [blame]
// 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);
}
}