1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139

base / android / input_hint_checker.h [blame]

// 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);

// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// Distinguishes outcomes of returning |true| from HasInput() below.
enum class InputHintResult {
  // The yield went through the Looper and dispatched input in
  // CompositorViewHolder. This path probably reduces touch latency in the
  // web contents area.
  kCompositorViewTouchEvent = 0,
  // The yield returned back from the Looper to continue with native tasks. It
  // can happen because the Looper did not prioritize input handling or
  // because the input events were hitting the parts of the UI outside of the
  // renderer compositor view.
  kBackToNative = 1,
  kMaxValue = kBackToNative,
};

// 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();
  };

  // Used for UMA metrics to remember that the input hint was used to yield
  // recently.
  void set_is_after_input_yield(bool after) { is_after_input_yield_ = after; }
  bool is_after_input_yield() { return is_after_input_yield_; }

  // Records the UMA metric based on the InputHintResult.
  static void RecordInputHintResult(InputHintResult result);

  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);

  bool is_after_input_yield_ = false;

  // Last time the input hint was requested. Used for throttling.
  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_