0

Send Viz InputTransferToken to Browser after InputReceiver creation

In a previous change(https://crrev.com/c/5872759) we are creating an
Android InputReceiver on Viz, in this change we send the input transfer
token for receiver back to Browser so that it can be used for
transferring a touch sequence later.

This change does the following:
* Update `SetSurface` api in
   `/content/public/browser/android/compositor.h` to return
   std::optional<int> the surface id in case set surface leads to adding
   a native widget in `GpuSurfaceTracker`.
* Add a binder call to `IGpuProcessCallback.aidl` for sending the input
  transfer token back from Viz to Browser.
* Introduce InputTransferHandler for handling logic related to touch
  transfer, it doesn't do much yet but more logic will be added
  incrementally.
* A thread safe map from surface id to input transfer handler for
  associating input transfer token with correct handler. There could be
  multiple handlers in case of split window with multiple chrome
  instances.
* Finally embedders using these to create input transfer handler, and
  adding and removing them from the map.

Bug: 364201006
Change-Id: Iead01d6063512a3dcf7ec04fdcf0743f83478d05
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5899517
Reviewed-by: Colin Blundell <blundell@chromium.org>
Reviewed-by: Alexander Timin <altimin@chromium.org>
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Reviewed-by: Kinuko Yasuda <kinuko@chromium.org>
Commit-Queue: Kartar Singh <kartarsingh@google.com>
Cr-Commit-Position: refs/heads/main@{#1371279}
This commit is contained in:
Kartar Singh
2024-10-21 10:33:38 +00:00
committed by Chromium LUCI CQ
parent 8b4f956985
commit aace15366c
26 changed files with 459 additions and 54 deletions

@ -21,6 +21,7 @@ import android.window.InputTransferToken;
import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;
import org.chromium.base.ContextUtils;
@ -37,7 +38,9 @@ import org.chromium.chrome.browser.layouts.scene_layer.SceneLayer;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.content_public.browser.InputTransferHandler;
import org.chromium.content_public.browser.SelectionPopupController;
import org.chromium.content_public.browser.SurfaceInputTransferHandlerMap;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.common.InputUtils;
import org.chromium.ui.base.WindowAndroid;
@ -92,6 +95,8 @@ public class CompositorView extends FrameLayout
private boolean mHaveSwappedFramesSinceSurfaceCreated;
private Integer mSurfaceId;
// On P and above, toggling the screen off gets us in a state where the Surface is destroyed but
// it is never recreated when it is turned on again. This is the only workaround that seems to
// be working, see crbug.com/931195.
@ -483,17 +488,27 @@ public class CompositorView extends FrameLayout
browserInputToken = rootSurfaceControl.getInputTransferToken();
}
CompositorViewJni.get()
.surfaceChanged(
mNativeCompositorView,
CompositorView.this,
format,
width,
height,
canUseSurfaceControl(),
surface,
browserInputToken);
Integer surfaceId =
CompositorViewJni.get()
.surfaceChanged(
mNativeCompositorView,
CompositorView.this,
format,
width,
height,
canUseSurfaceControl(),
surface,
browserInputToken);
mRenderHost.onSurfaceResized(width, height);
if (InputUtils.isTransferInputToVizSupported()
&& surfaceId != null
&& browserInputToken != null) {
InputTransferHandler handler = new InputTransferHandler(browserInputToken);
assert mSurfaceId == null;
mSurfaceId = surfaceId;
SurfaceInputTransferHandlerMap.getMap().put(mSurfaceId, handler);
}
}
@Override
@ -526,6 +541,10 @@ public class CompositorView extends FrameLayout
if (mScreenStateReceiver != null) {
mScreenStateReceiver.maybeResetCompositorSurfaceManager();
}
if (InputUtils.isTransferInputToVizSupported() && mSurfaceId != null) {
SurfaceInputTransferHandlerMap.getMap().remove(mSurfaceId);
mSurfaceId = null;
}
}
@Override
@ -796,7 +815,8 @@ public class CompositorView extends FrameLayout
void surfaceDestroyed(long nativeCompositorView, CompositorView caller);
void surfaceChanged(
@JniType("std::optional<int>")
Integer surfaceChanged(
long nativeCompositorView,
CompositorView caller,
int format,

@ -169,7 +169,7 @@ void CompositorView::SurfaceDestroyed(JNIEnv* env,
tab_content_manager_->OnUIResourcesWereEvicted();
}
void CompositorView::SurfaceChanged(
std::optional<int> CompositorView::SurfaceChanged(
JNIEnv* env,
const JavaParamRef<jobject>& object,
jint format,
@ -178,17 +178,19 @@ void CompositorView::SurfaceChanged(
bool can_be_used_with_surface_control,
const JavaParamRef<jobject>& surface,
const JavaParamRef<jobject>& browser_input_token) {
std::optional<int> surface_handle = std::nullopt;
DCHECK(surface);
if (current_surface_format_ != format) {
current_surface_format_ = format;
compositor_->SetSurface(surface, can_be_used_with_surface_control,
browser_input_token);
surface_handle = compositor_->SetSurface(
surface, can_be_used_with_surface_control, browser_input_token);
}
gfx::Size size = gfx::Size(width, height);
compositor_->SetWindowBounds(size);
content_width_ = size.width();
content_height_ = size.height();
root_layer_->SetBounds(gfx::Size(content_width_, content_height_));
return surface_handle;
}
void CompositorView::OnPhysicalBackingSizeChanged(

@ -31,6 +31,20 @@ class ResourceManager;
class UIResourceProvider;
} // namespace ui
namespace jni_zero {
template <>
inline ScopedJavaLocalRef<jobject> ToJniType<int>(JNIEnv* env,
const int& input) {
ScopedJavaLocalRef<jclass> integer_class =
base::android::GetClass(env, "java/lang/Integer");
jmethodID constructor =
base::android::MethodID::Get<base::android::MethodID::TYPE_INSTANCE>(
env, integer_class.obj(), "<init>", "(I)V");
return ScopedJavaLocalRef<jobject>(
env, env->NewObject(integer_class.obj(), constructor, input));
}
} // namespace jni_zero
namespace android {
class SceneLayer;
@ -64,7 +78,7 @@ class CompositorView : public content::CompositorClient,
const base::android::JavaParamRef<jobject>& object);
void SurfaceDestroyed(JNIEnv* env,
const base::android::JavaParamRef<jobject>& object);
void SurfaceChanged(
std::optional<int> SurfaceChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& object,
jint format,

@ -18,8 +18,11 @@ import android.window.InputTransferToken;
import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.JniType;
import org.jni_zero.NativeMethods;
import org.chromium.content_public.browser.InputTransferHandler;
import org.chromium.content_public.browser.SurfaceInputTransferHandlerMap;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.common.InputUtils;
import org.chromium.ui.base.WindowAndroid;
@ -42,11 +45,13 @@ public class ContentViewRenderView extends FrameLayout {
private int mWidth;
private int mHeight;
private Integer mSurfaceId;
/**
* Constructs a new ContentViewRenderView.
* This should be called and the {@link ContentViewRenderView} should be added to the view
* hierarchy before the first draw to avoid a black flash that is seen every time a
* {@link SurfaceView} is added.
* Constructs a new ContentViewRenderView. This should be called and the {@link
* ContentViewRenderView} should be added to the view hierarchy before the first draw to avoid a
* black flash that is seen every time a {@link SurfaceView} is added.
*
* @param context The context used to create this.
*/
public ContentViewRenderView(Context context) {
@ -80,22 +85,30 @@ public class ContentViewRenderView extends FrameLayout {
SurfaceHolder holder, int format, int width, int height) {
assert mNativeContentViewRenderView != 0;
InputTransferToken hostInputToken = null;
InputTransferToken browserInputToken = null;
Window window = mWindowAndroid.getWindow();
if (InputUtils.isTransferInputToVizSupported() && window != null) {
AttachedSurfaceControl rootSurfaceControl =
window.getRootSurfaceControl();
hostInputToken = rootSurfaceControl.getInputTransferToken();
browserInputToken = rootSurfaceControl.getInputTransferToken();
}
Integer surfaceId =
ContentViewRenderViewJni.get()
.surfaceChanged(
mNativeContentViewRenderView,
ContentViewRenderView.this,
format,
width,
height,
holder.getSurface(),
browserInputToken);
if (surfaceId != null && browserInputToken != null) {
InputTransferHandler handler =
new InputTransferHandler(browserInputToken);
assert mSurfaceId == null;
mSurfaceId = surfaceId;
SurfaceInputTransferHandlerMap.getMap().put(mSurfaceId, handler);
}
ContentViewRenderViewJni.get()
.surfaceChanged(
mNativeContentViewRenderView,
ContentViewRenderView.this,
format,
width,
height,
holder.getSurface(),
hostInputToken);
if (mWebContents != null) {
ContentViewRenderViewJni.get()
.onPhysicalBackingSizeChanged(
@ -132,6 +145,10 @@ public class ContentViewRenderView extends FrameLayout {
ContentViewRenderViewJni.get()
.surfaceDestroyed(
mNativeContentViewRenderView, ContentViewRenderView.this);
if (mSurfaceId != null) {
SurfaceInputTransferHandlerMap.getMap().remove(mSurfaceId);
mSurfaceId = null;
}
}
};
mSurfaceBridge.connect(surfaceCallback);
@ -306,7 +323,8 @@ public class ContentViewRenderView extends FrameLayout {
void surfaceDestroyed(long nativeContentViewRenderView, ContentViewRenderView caller);
void surfaceChanged(
@JniType("std::optional<int>")
Integer surfaceChanged(
long nativeContentViewRenderView,
ContentViewRenderView caller,
int format,

@ -96,7 +96,7 @@ void ContentViewRenderView::SurfaceDestroyed(JNIEnv* env,
current_surface_format_ = 0;
}
void ContentViewRenderView::SurfaceChanged(
std::optional<int> ContentViewRenderView::SurfaceChanged(
JNIEnv* env,
const JavaParamRef<jobject>& obj,
jint format,
@ -104,13 +104,15 @@ void ContentViewRenderView::SurfaceChanged(
jint height,
const JavaParamRef<jobject>& surface,
const JavaParamRef<jobject>& browser_input_token) {
std::optional<int> surface_handle = std::nullopt;
if (current_surface_format_ != format) {
current_surface_format_ = format;
compositor_->SetSurface(surface,
true /* can_be_used_with_surface_control */,
browser_input_token);
surface_handle = compositor_->SetSurface(
surface, true /* can_be_used_with_surface_control */,
browser_input_token);
}
compositor_->SetWindowBounds(gfx::Size(width, height));
return surface_handle;
}
void ContentViewRenderView::SetOverlayVideoMode(

@ -7,6 +7,7 @@
#include <memory>
#include "base/android/jni_android.h"
#include "base/android/jni_weak_ref.h"
#include "content/public/browser/android/compositor_client.h"
#include "ui/gfx/native_widget_types.h"
@ -14,6 +15,20 @@
namespace content {
class Compositor;
} // namespace content
//
namespace jni_zero {
template <>
inline ScopedJavaLocalRef<jobject> ToJniType<int>(JNIEnv* env,
const int& input) {
ScopedJavaLocalRef<jclass> integer_class =
base::android::GetClass(env, "java/lang/Integer");
jmethodID constructor =
base::android::MethodID::Get<base::android::MethodID::TYPE_INSTANCE>(
env, integer_class.obj(), "<init>", "(I)V");
return ScopedJavaLocalRef<jobject>(
env, env->NewObject(integer_class.obj(), constructor, input));
}
} // namespace jni_zero
namespace embedder_support {
@ -42,7 +57,7 @@ class ContentViewRenderView : public content::CompositorClient {
const base::android::JavaParamRef<jobject>& obj);
void SurfaceDestroyed(JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj);
void SurfaceChanged(
std::optional<int> SurfaceChanged(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& obj,
jint format,

@ -103,6 +103,8 @@ component("input") {
}
if (is_android) {
sources += [
"android/input_token_forwarder.cc",
"android/input_token_forwarder.h",
"android/scoped_input_receiver.cc",
"android/scoped_input_receiver.h",
"android/scoped_input_receiver_callbacks.cc",

@ -0,0 +1,26 @@
// 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.
#include "components/input/android/input_token_forwarder.h"
#include "base/check.h"
namespace input {
namespace {
InputTokenForwarder* g_instance = nullptr;
} // namespace
// static
InputTokenForwarder* InputTokenForwarder::GetInstance() {
DCHECK(g_instance);
return g_instance;
}
// static
void InputTokenForwarder::SetInstance(InputTokenForwarder* instance) {
DCHECK(!g_instance || !instance);
g_instance = instance;
}
} // namespace input

@ -0,0 +1,32 @@
// 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 COMPONENTS_INPUT_ANDROID_INPUT_TOKEN_FORWARDER_H_
#define COMPONENTS_INPUT_ANDROID_INPUT_TOKEN_FORWARDER_H_
#include <jni.h>
#include "base/android/scoped_java_ref.h"
#include "base/component_export.h"
namespace input {
// Allows forwarding InputTransferToken for Viz's InputReceiver back to Browser
// so that it can be used to transfer touch sequence to Viz.
class COMPONENT_EXPORT(INPUT) InputTokenForwarder {
public:
static InputTokenForwarder* GetInstance();
static void SetInstance(InputTokenForwarder* instance);
virtual void ForwardVizInputTransferToken(
int surface_id,
base::android::ScopedJavaGlobalRef<jobject> viz_input_token) = 0;
protected:
virtual ~InputTokenForwarder() = default;
};
} // namespace input
#endif // COMPONENTS_INPUT_ANDROID_INPUT_TOKEN_FORWARDER_H_

@ -18,6 +18,13 @@ ScopedInputTransferToken::ScopedInputTransferToken(
.AInputTransferToken_fromJavaFn(env, input_transfer_token);
}
ScopedInputTransferToken::ScopedInputTransferToken(AInputReceiver* receiver) {
CHECK(base::AndroidInputReceiverCompat::IsSupportAvailable());
a_input_transfer_token_ =
base::AndroidInputReceiverCompat::GetInstance()
.AInputReceiver_getInputTransferTokenFn(receiver);
}
ScopedInputTransferToken::~ScopedInputTransferToken() {
DestroyIfNeeded();
}

@ -10,6 +10,7 @@
#include "base/component_export.h"
#include "base/memory/raw_ptr_exclusion.h"
struct AInputReceiver;
struct AInputTransferToken;
namespace input {
@ -21,6 +22,7 @@ namespace input {
class COMPONENT_EXPORT(INPUT) ScopedInputTransferToken {
public:
explicit ScopedInputTransferToken(const jobject& input_transfer_token);
explicit ScopedInputTransferToken(AInputReceiver* receiver);
~ScopedInputTransferToken();
ScopedInputTransferToken(ScopedInputTransferToken&& other);

@ -157,6 +157,42 @@ IN_PROC_BROWSER_TEST_P(AndroidInputBrowserTest, AndroidInputReceiverCreated) {
/*kSuccessfullyCreated*/ 0, expected_count);
}
IN_PROC_BROWSER_TEST_P(AndroidInputBrowserTest,
VizInputTransferTokenReceivedOnBrowser) {
base::test::TestTraceProcessor ttp;
ttp.StartTrace("input,Java");
content::RenderFrameSubmissionObserver render_frame_submission_observer(
shell()->web_contents());
EXPECT_TRUE(content::NavigateToURL(
shell(), GURL("data:text/html,<!doctype html>"
"<body style='background-color: magenta;'></body>")));
if (render_frame_submission_observer.render_frame_count() == 0) {
render_frame_submission_observer.WaitForAnyFrameSubmission();
}
// By this point a root compositor frame sink should have been created as
// well, and if an input receiver were to be created it should have been
// since it happens when root compositor frame sink is created.
absl::Status status = ttp.StopAndParseTrace();
EXPECT_TRUE(status.ok()) << status.message();
std::string query = R"(SELECT COUNT(*) as cnt
FROM slice
WHERE slice.name = 'Storing InputTransferToken')";
auto result = ttp.RunQuery(query);
EXPECT_TRUE(result.has_value());
// `result.value()` would look something like this: {{"cnt"}, {"<num>"}.
EXPECT_EQ(result.value().size(), 2u);
EXPECT_EQ(result.value()[1].size(), 1u);
const std::string slice_count = result.value()[1][0];
const std::string expected_count =
IsTransferInputToVizSupported() ? "1" : "0";
EXPECT_EQ(slice_count, expected_count);
}
INSTANTIATE_TEST_SUITE_P(All,
AndroidInputBrowserTest,
::testing::Bool(),

@ -15,6 +15,8 @@
#include "components/viz/service/input/render_input_router_delegate_impl.h"
#if BUILDFLAG(IS_ANDROID)
#include "base/android/android_input_receiver_compat.h"
#include "components/input/android/input_token_forwarder.h"
#include "components/input/android/scoped_input_receiver.h"
#include "components/input/android/scoped_input_receiver_callbacks.h"
#include "components/input/android/scoped_input_transfer_token.h"
@ -52,7 +54,8 @@ enum class CreateAndroidInputReceiverResult {
kFailedNullLooper = 3,
kFailedNullInputTransferToken = 4,
kFailedNullCallbacks = 5,
kMaxValue = kFailedNullCallbacks,
kSuccessfulButNullTransferToken = 6,
kMaxValue = kSuccessfulButNullTransferToken,
};
#endif // BUILDFLAG(IS_ANDROID)
@ -235,14 +238,32 @@ void InputManager::CreateAndroidInputReceiver(
looper, browser_input_token.a_input_transfer_token(), surface->surface(),
callbacks.a_input_receiver_callbacks());
if (receiver) {
UMA_HISTOGRAM_ENUMERATION(
kInputReceiverCreationResultHistogram,
CreateAndroidInputReceiverResult::kSuccessfullyCreated);
} else {
if (!receiver) {
UMA_HISTOGRAM_ENUMERATION(kInputReceiverCreationResultHistogram,
CreateAndroidInputReceiverResult::kFailedUnknown);
return;
}
input::ScopedInputTransferToken viz_input_token(receiver.a_input_receiver());
if (!viz_input_token) {
UMA_HISTOGRAM_ENUMERATION(
kInputReceiverCreationResultHistogram,
CreateAndroidInputReceiverResult::kSuccessfulButNullTransferToken);
return;
}
UMA_HISTOGRAM_ENUMERATION(
kInputReceiverCreationResultHistogram,
CreateAndroidInputReceiverResult::kSuccessfullyCreated);
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaGlobalRef<jobject> viz_input_token_java(
env, base::AndroidInputReceiverCompat::GetInstance()
.AInputTransferToken_toJavaFn(
env, viz_input_token.a_input_transfer_token()));
input::InputTokenForwarder::GetInstance()->ForwardVizInputTransferToken(
surface_handle, viz_input_token_java);
}
#endif // BUILDFLAG(IS_ANDROID)

@ -12,6 +12,7 @@
#include "base/command_line.h"
#include "base/lazy_instance.h"
#include "base/unguessable_token.h"
#include "components/input/android/input_token_forwarder.h"
#include "content/child/child_thread_impl.h"
#include "content/common/android/surface_wrapper.h"
#include "content/common/shared_file_util.h"
@ -37,7 +38,8 @@ namespace {
// TODO(sievers): Use two different implementations of this depending on if
// we're in a renderer or gpu process.
class ChildProcessSurfaceManager : public gpu::ScopedSurfaceRequestConduit,
public gpu::GpuSurfaceLookup {
public gpu::GpuSurfaceLookup,
public input::InputTokenForwarder {
public:
ChildProcessSurfaceManager() {}
@ -97,6 +99,15 @@ class ChildProcessSurfaceManager : public gpu::ScopedSurfaceRequestConduit,
}
}
// input::InputTokenForwarder overrides.
void ForwardVizInputTransferToken(
int surface_id,
base::android::ScopedJavaGlobalRef<jobject> viz_input_token) override {
JNIEnv* env = base::android::AttachCurrentThread();
content::Java_ContentChildProcessServiceDelegate_forwardInputTransferToken(
env, service_impl_, surface_id, viz_input_token);
}
private:
friend struct base::LazyInstanceTraitsBase<ChildProcessSurfaceManager>;
// The instance of org.chromium.content.app.ChildProcessService.
@ -122,6 +133,8 @@ void JNI_ContentChildProcessServiceDelegate_InternalInitChildProcess(
g_child_process_surface_manager.Pointer());
gpu::ScopedSurfaceRequestConduit::SetInstance(
g_child_process_surface_manager.Pointer());
input::InputTokenForwarder::SetInstance(
g_child_process_surface_manager.Pointer());
}
} // namespace

@ -304,7 +304,7 @@ void CompositorImpl::SetRootLayer(scoped_refptr<cc::slim::Layer> root_layer) {
}
}
void CompositorImpl::SetSurface(
std::optional<gpu::SurfaceHandle> CompositorImpl::SetSurface(
const base::android::JavaRef<jobject>& surface,
bool can_be_used_with_surface_control,
const base::android::JavaRef<jobject>& host_input_token) {
@ -321,14 +321,17 @@ void CompositorImpl::SetSurface(
gl::ScopedJavaSurface scoped_surface(surface, /*auto_release=*/false);
gl::ScopedANativeWindow window(scoped_surface);
if (window) {
window_ = std::move(window);
// Register first, SetVisible() might create a LayerTreeFrameSink.
surface_handle_ = tracker->AddSurfaceForNativeWidget(
gpu::SurfaceRecord(std::move(scoped_surface),
can_be_used_with_surface_control, host_input_token));
SetVisible(true);
if (!window) {
return std::nullopt;
}
window_ = std::move(window);
// Register first, SetVisible() might create a LayerTreeFrameSink.
surface_handle_ = tracker->AddSurfaceForNativeWidget(
gpu::SurfaceRecord(std::move(scoped_surface),
can_be_used_with_surface_control, host_input_token));
SetVisible(true);
return surface_handle_;
}
void CompositorImpl::SetBackgroundColor(int color) {

@ -109,7 +109,7 @@ class CONTENT_EXPORT CompositorImpl : public Compositor,
// Compositor implementation.
void SetRootWindow(gfx::NativeWindow root_window) override;
void SetRootLayer(scoped_refptr<cc::slim::Layer> root) override;
void SetSurface(
std::optional<gpu::SurfaceHandle> SetSurface(
const base::android::JavaRef<jobject>& surface,
bool can_be_used_with_surface_control,
const base::android::JavaRef<jobject>& host_input_token) override;

@ -129,6 +129,7 @@ android_library("content_main_dex_java") {
"java/src/org/chromium/content/browser/ServicificationStartupUma.java",
"java/src/org/chromium/content/browser/TracingControllerAndroidImpl.java",
"java/src/org/chromium/content/common/ContentSwitchUtils.java",
"java/src/org/chromium/content/common/InputTransferTokenWrapper.java",
"java/src/org/chromium/content/common/SurfaceWrapper.java",
"java/src/org/chromium/content_public/app/ChildProcessServiceFactory.java",
"java/src/org/chromium/content_public/app/ZygotePreload.java",
@ -378,6 +379,7 @@ android_library("content_full_java") {
"java/src/org/chromium/content_public/browser/ImeAdapter.java",
"java/src/org/chromium/content_public/browser/ImeEventObserver.java",
"java/src/org/chromium/content_public/browser/InputMethodManagerWrapper.java",
"java/src/org/chromium/content_public/browser/InputTransferHandler.java",
"java/src/org/chromium/content_public/browser/InterfaceRegistrar.java",
"java/src/org/chromium/content_public/browser/JavaScriptCallback.java",
"java/src/org/chromium/content_public/browser/JavascriptInjector.java",
@ -411,6 +413,7 @@ android_library("content_full_java") {
"java/src/org/chromium/content_public/browser/SpeechRecognition.java",
"java/src/org/chromium/content_public/browser/StylusWritingHandler.java",
"java/src/org/chromium/content_public/browser/StylusWritingImeCallback.java",
"java/src/org/chromium/content_public/browser/SurfaceInputTransferHandlerMap.java",
"java/src/org/chromium/content_public/browser/ViewEventSink.java",
"java/src/org/chromium/content_public/browser/WebContents.java",
"java/src/org/chromium/content_public/browser/WebContentsAccessibility.java",

@ -6,11 +6,15 @@ package org.chromium.content.app;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.SparseArray;
import android.view.Surface;
import android.window.InputTransferToken;
import androidx.annotation.RequiresApi;
import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
@ -26,6 +30,7 @@ import org.chromium.base.process_launcher.ChildProcessServiceDelegate;
import org.chromium.content.browser.ChildProcessCreationParamsImpl;
import org.chromium.content.browser.ContentChildProcessConstants;
import org.chromium.content.common.IGpuProcessCallback;
import org.chromium.content.common.InputTransferTokenWrapper;
import org.chromium.content.common.SurfaceWrapper;
import org.chromium.content_public.common.ContentProcessInfo;
@ -192,6 +197,21 @@ public class ContentChildProcessServiceDelegate implements ChildProcessServiceDe
}
}
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@CalledByNative
private void forwardInputTransferToken(int surfaceId, InputTransferToken vizInputToken) {
if (mGpuCallback == null) {
Log.e(TAG, "No callback interface has been provided.");
return;
}
try {
mGpuCallback.forwardInputTransferToken(
surfaceId, new InputTransferTokenWrapper(vizInputToken));
} catch (RemoteException e) {
Log.e(TAG, "Unable to call forwardInputTransferToken: %s", e);
}
}
@NativeMethods
interface Natives {
/**

@ -4,6 +4,7 @@
package org.chromium.content.browser;
import android.os.Build;
import android.view.Surface;
import org.jni_zero.JNINamespace;
@ -12,7 +13,10 @@ import org.jni_zero.NativeMethods;
import org.chromium.base.UnguessableToken;
import org.chromium.content.common.IGpuProcessCallback;
import org.chromium.content.common.InputTransferTokenWrapper;
import org.chromium.content.common.SurfaceWrapper;
import org.chromium.content_public.browser.InputTransferHandler;
import org.chromium.content_public.browser.SurfaceInputTransferHandlerMap;
@JNINamespace("content")
class GpuProcessCallback extends IGpuProcessCallback.Stub {
@ -28,6 +32,17 @@ class GpuProcessCallback extends IGpuProcessCallback.Stub {
return GpuProcessCallbackJni.get().getViewSurface(surfaceId);
}
@Override
public void forwardInputTransferToken(int surfaceId, InputTransferTokenWrapper wrapper) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
return;
}
InputTransferHandler handler = SurfaceInputTransferHandlerMap.getMap().get(surfaceId);
if (handler != null) {
handler.setVizToken(wrapper.getInputTransferToken());
}
}
@NativeMethods
interface Natives {
void completeScopedSurfaceRequest(

@ -4,8 +4,11 @@
package org.chromium.content.common;
import org.chromium.content.common.SurfaceWrapper;
import android.view.Surface;
import android.window.InputTransferToken;
import org.chromium.content.common.InputTransferTokenWrapper;
import org.chromium.content.common.SurfaceWrapper;
interface IGpuProcessCallback {
@ -13,4 +16,8 @@ interface IGpuProcessCallback {
in UnguessableToken requestToken, in Surface surface);
SurfaceWrapper getViewSurface(int surfaceId);
// Send the input transfer token from Viz to Browser so that the Browser can use it later
// to transfer touch sequence.
oneway void forwardInputTransferToken(int surfaceId, in InputTransferTokenWrapper vizInputToken);
}

@ -0,0 +1,7 @@
// 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.
package org.chromium.content.common;
parcelable InputTransferTokenWrapper;

@ -0,0 +1,64 @@
// 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.
package org.chromium.content.common;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.window.InputTransferToken;
/**
* A wrapper for sending InputTransferToken over Binder. InputTransferToken is parcelable itself but
* we can't directly pass it over since it was only introduced in SDK level 35 and the java compiler
* complains about "Field requires API level 35" in the stub generated for aidl file.
*/
public class InputTransferTokenWrapper implements Parcelable {
private InputTransferToken mToken;
public InputTransferTokenWrapper(InputTransferToken token) {
assert token != null;
mToken = token;
}
public InputTransferToken getInputTransferToken() {
return mToken;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(/* hasToken= */ 1);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
mToken.writeToParcel(out, flags);
}
}
@Override
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<InputTransferTokenWrapper> CREATOR =
new Parcelable.Creator<InputTransferTokenWrapper>() {
@Override
public InputTransferTokenWrapper createFromParcel(Parcel in) {
final boolean hasToken = (in.readInt() == 1);
if (hasToken
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
InputTransferToken token = InputTransferToken.CREATOR.createFromParcel(in);
return new InputTransferTokenWrapper(token);
} else {
// The only constructor of class assert's token is non-null and
// InputTransferToken can only be non-null on V+ devices. Since the object
// was already created it ensures this conditions shouldn't be reached.
throw new RuntimeException("not reached");
}
}
@Override
public InputTransferTokenWrapper[] newArray(int size) {
return new InputTransferTokenWrapper[size];
}
};
}

@ -0,0 +1,43 @@
// 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.
package org.chromium.content_public.browser;
import android.os.Build;
import android.view.WindowManager;
import android.window.InputTransferToken;
import androidx.annotation.RequiresApi;
import org.chromium.base.ContextUtils;
import org.chromium.base.TraceEvent;
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class InputTransferHandler {
private InputTransferToken mBrowserToken;
private InputTransferToken mVizToken;
public InputTransferHandler(InputTransferToken browserToken) {
mBrowserToken = browserToken;
}
private boolean canTransferInputToViz() {
// TODO(370506271): Implement logic for when can we transfer vs not.
return false;
}
public void setVizToken(InputTransferToken token) {
TraceEvent.instant("Storing InputTransferToken");
mVizToken = token;
}
public boolean maybeTransferInputToViz() {
if (!canTransferInputToViz()) {
return false;
}
WindowManager wm =
ContextUtils.getApplicationContext().getSystemService(WindowManager.class);
return wm.transferTouchGesture(mBrowserToken, mVizToken);
}
}

@ -0,0 +1,30 @@
// 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.
package org.chromium.content_public.browser;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class SurfaceInputTransferHandlerMap {
public final Map<Integer, InputTransferHandler> mInputTransferHandlerMap =
Collections.synchronizedMap(new HashMap<Integer, InputTransferHandler>());
private SurfaceInputTransferHandlerMap() {}
private static class LazyHolder {
private static final SurfaceInputTransferHandlerMap INSTANCE =
new SurfaceInputTransferHandlerMap();
}
public static Map<Integer, InputTransferHandler> getMap() {
return LazyHolder.INSTANCE.mInputTransferHandlerMap;
}
}

@ -5,6 +5,8 @@
#ifndef CONTENT_PUBLIC_BROWSER_ANDROID_COMPOSITOR_H_
#define CONTENT_PUBLIC_BROWSER_ANDROID_COMPOSITOR_H_
#include <optional>
#include "base/android/scoped_java_ref.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
@ -74,7 +76,7 @@ class CONTENT_EXPORT Compositor {
virtual const gfx::Size& GetWindowBounds() = 0;
// Set the output surface which the compositor renders into.
virtual void SetSurface(
virtual std::optional<gpu::SurfaceHandle> SetSurface(
const base::android::JavaRef<jobject>& surface,
bool can_be_used_with_surface_control,
const base::android::JavaRef<jobject>& host_input_token) = 0;

@ -794,6 +794,7 @@ chromium-metrics-reviews@google.com.
<int value="3" label="FailedNullLooper"/>
<int value="4" label="FailedNullInputTransferToken"/>
<int value="5" label="FailedNullCallbacks"/>
<int value="6" label="SuccessfulButNullTransferToken"/>
</enum>
<enum name="DebuggingEnabled">