| // 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_ |