blob: e24d0f937ea0c2bfe57485a914da2c1dc2bfaa15 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_AUTOFILL_ANDROID_TOUCH_TO_FILL_KEYBOARD_SUPPRESSOR_H_
#define COMPONENTS_AUTOFILL_ANDROID_TOUCH_TO_FILL_KEYBOARD_SUPPRESSOR_H_
#include "base/memory/weak_ptr.h"
#include "base/scoped_multi_source_observation.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "components/autofill/content/browser/content_autofill_driver_factory.h"
#include "components/autofill/core/browser/foundations/autofill_manager.h"
#if !BUILDFLAG(IS_ANDROID)
#error "Android-only header"
#endif
namespace autofill {
class ContentAutofillClient;
// Suppresses the Android IME.
//
// If a TTF surface intends to be displayed, it must suppress the keyboard
// preemptively before Autofill has parsed the form. Otherwise, the keyboard
// could be displayed while Autofill is parsing the form, which would lead to
// flickering.
//
// TouchToFillKeyboardSuppressor takes two callbacks from a TTF controller:
// - `is_showing()` returns true iff the TTF is currently shown;
// - `intends_to_show()` returns true iff the TTF is intended to be shown
// once parsing is completed.
//
// With TouchToFillKeyboardSuppressor, the controller can implement the
// following behavior:
//
// +--------------------------------------------------------+
// | After parsing: controller wants to show TTF? |
// +--------------------------+-----------------------------+
// | No | Yes |
// +----------+-----+--------------------------+-----------------------------+
// | Before | No | Don't suppress. |
// | parsing: | | Don't show TTF. |
// | con- +-----+--------------------------+-----------------------------+
// | troller | Yes | Suppress before parsing. | Suppress before parsing. |
// | intends | | Unsuppress after parsing | Unsuppress after timeout if |
// | to show | | or after timeout. | parsing is slow. Otherwise, |
// | TTF? | | Don't show TTF. | show TTF, then unsuppress. |
// +----------+-----+--------------------------+-----------------------------+
//
// The timeout mechanism is intended for cases where parsing takes extremely
// long.
//
// The TTF controller must satisfy the following conditions:
// - The `is_showing()` callback must be true after parsing only if
// `is_showing() || intends_to_show()` has been true before parsing.
// - The TTF controller must show the TTF only if `is_suppressing()` is true.
// - The TTF controller must call `Unsuppress()` after the TTF was shown.
//
// The lifecycle of a TouchToFillKeyboardSuppressor must be aligned to the
// lifecycle of a tab (represented here by a ContentAutofillClient).
// It must be created before any ContentAutofillDrivers of the tab have been
// created: it won't suppress the keyboard in frames that were created already.
//
// TouchToFillKeyboardSuppressor observes all AutofillManagers of a given tab
// (represented by the ContentAutofillClient). The event structure is this:
// 1. AutofillManager::Observer::OnBeforeAskForValuesToFill().
// 2. Asynchronous parsing.
// 3. Controller's shows TTF only if
// - `intends_to_show()` had been true in Step 1, and
// - `is_suppressing()` is true now.
// 4. AutofillManager::Observer::OnAfterAskForValuesToFill().
class TouchToFillKeyboardSuppressor
: public ContentAutofillDriverFactory::Observer,
public AutofillManager::Observer {
public:
explicit TouchToFillKeyboardSuppressor(
ContentAutofillClient* autofill_client,
base::RepeatingCallback<bool(AutofillManager&)> is_showing,
base::RepeatingCallback<
bool(AutofillManager&, FormGlobalId, FieldGlobalId, const FormData&)>
intends_to_show,
base::TimeDelta timeout);
TouchToFillKeyboardSuppressor(const TouchToFillKeyboardSuppressor&) = delete;
TouchToFillKeyboardSuppressor& operator=(
const TouchToFillKeyboardSuppressor&) = delete;
~TouchToFillKeyboardSuppressor() override;
// ContentAutofillDriverFactory::Observer:
void OnContentAutofillDriverFactoryDestroyed(
ContentAutofillDriverFactory& factory) override;
void OnContentAutofillDriverCreated(ContentAutofillDriverFactory& factory,
ContentAutofillDriver& driver) override;
// AutofillManager::Observer:
void OnAutofillManagerStateChanged(
AutofillManager& manager,
AutofillManager::LifecycleState old_state,
AutofillManager::LifecycleState new_state) override;
void OnBeforeAskForValuesToFill(AutofillManager& manager,
FormGlobalId form_id,
FieldGlobalId field_id,
const FormData& form_data) override;
void OnAfterAskForValuesToFill(AutofillManager& manager,
FormGlobalId form_id,
FieldGlobalId field_id) override;
// Returns true iff some AutofillManager's keyboard is currently suppressed.
bool is_suppressing() { return suppressed_manager_.get(); }
// Unsuppresses the keyboard if it's currently being suppressed.
// This does not raise the keyboard itself.
void Unsuppress();
private:
void KeepSuppressing() { unsuppress_timer_.Stop(); }
void Suppress(AutofillManager& manager);
base::RepeatingCallback<bool(AutofillManager&)> is_showing_;
base::RepeatingCallback<
bool(AutofillManager&, FormGlobalId, FieldGlobalId, const FormData&)>
intends_to_show_;
base::ScopedObservation<ContentAutofillDriverFactory,
ContentAutofillDriverFactory::Observer>
driver_factory_observation_{this};
base::ScopedMultiSourceObservation<AutofillManager, AutofillManager::Observer>
autofill_manager_observations_{this};
// The single AutofillManager whose keyboard is currently suppressed by this
// suppressor; `nullptr` if no AutofillManager's keyboard is suppressed.
// A raw pointer suffices because because OnAutofillManagerStateChanged()
// resets it if necessary.
raw_ptr<AutofillManager> suppressed_manager_ = nullptr;
// Unsuppresses the keyboard after `timeout_`.
base::OneShotTimer unsuppress_timer_;
base::TimeDelta timeout_;
base::WeakPtrFactory<TouchToFillKeyboardSuppressor> weak_ptr_factory_{this};
};
} // namespace autofill
#endif // COMPONENTS_AUTOFILL_ANDROID_TOUCH_TO_FILL_KEYBOARD_SUPPRESSOR_H_