chromium/base/android/input_hint_checker.h

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_ANDROID_INPUT_HINT_CHECKER_H_
#define BASE_ANDROID_INPUT_HINT_CHECKER_H_

#include <jni.h>

#include "base/android/jni_weak_ref.h"
#include "base/base_export.h"
#include "base/feature_list.h"
#include "base/memory/raw_ptr.h"
#include "base/no_destructor.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"

namespace base::android {

BASE_DECLARE_FEATURE(kYieldWithInputHint);

// A class to track a single global root View object and ask it for presence of
// new unhandled input events.
//
// This class uses bits specific to Android V and does nothing on earlier
// releases.
//
// Must be constructed on UI thread. All public methods must be called on the UI
// thread.
class BASE_EXPORT InputHintChecker {
 public:
  InputHintChecker();
  virtual ~InputHintChecker();

  // Returns the singleton.
  static InputHintChecker& GetInstance();

  // Initializes features for this class. See `base::features::Init()`.
  static void InitializeFeatures();

  // Obtains a weak reference to |root_view| so that the following calls to
  // HasInput() take the input hint for this View. Requirements for the View
  // object are described in InputHintChecker.java.
  void SetView(JNIEnv* env, const jni_zero::JavaParamRef<jobject>& root_view);

  // Fetches and returns the input hint from the Android Framework.
  //
  // Works as a hint: when unhandled input events are detected, this method
  // returns |true| with high probability. However, the returned value neither
  // guarantees presence nor absence of input events in the queue. For example,
  // this method returns |false| while the singleton is going through
  // initialization.
  //
  // Throttles the calls to one every few milliseconds. When a call is made
  // before the minimal time interval passed since the previous call, returns
  // false.
  static bool HasInput();

  // RAII override of GetInstance() for testing.
  struct ScopedOverrideInstance {
    explicit ScopedOverrideInstance(InputHintChecker* checker);
    ~ScopedOverrideInstance();
  };

  bool IsInitializedForTesting();
  bool FailedToInitializeForTesting();
  bool HasInputImplNoThrottlingForTesting(_JNIEnv* env);
  bool HasInputImplWithThrottlingForTesting(_JNIEnv* env);

 protected:
  virtual bool HasInputImplWithThrottling();

 private:
  friend class base::NoDestructor<InputHintChecker>;
  class OffThreadInitInvoker;
  enum class InitState;
  InitState FetchState() const;
  void TransitionToState(InitState new_state);
  void RunOffThreadInitialization();
  void InitGlobalRefsAndMethodIds(JNIEnv* env);
  bool HasInputImpl(JNIEnv* env, jobject o);

  base::TimeTicks last_checked_;

  // Initialization state. It is made atomic because part of the initialization
  // happens on another thread while public methods of this class can be called
  // on the UI thread.
  std::atomic<InitState> init_state_;

  // The android.view.View object reference used to fetch the hint in
  // HasInput().
  JavaObjectWeakGlobalRef view_;

  // Represents a reference to android.view.View.class. Used during
  // initialization.
  ScopedJavaGlobalRef<jobject> view_class_;

  // Represents a reference to object of type j.l.reflect.Method for
  // View#probablyHasInput().
  ScopedJavaGlobalRef<jobject> reflect_method_for_has_input_;

  // The ID corresponding to j.l.reflect.Method#invoke(Object, Object...).
  jmethodID invoke_id_;

  // The ID corresponding to j.l.Boolean#booleanValue().
  jmethodID boolean_value_id_;
  THREAD_CHECKER(thread_checker_);
};

}  // namespace base::android

#endif  // BASE_ANDROID_INPUT_HINT_CHECKER_H_