From ddbddb812237ad89f535222e49de805ac1f2ab29 Mon Sep 17 00:00:00 2001 From: Gary Qian Date: Tue, 31 May 2022 15:29:28 -0700 Subject: [PATCH] Ignore LambdaLast --- .../FlutterEngineConnectionRegistry.java | 131 ++++++++++------- .../embedding/engine/dart/DartExecutor.java | 28 ++-- .../embedding/engine/dart/DartMessenger.java | 19 +-- .../engine/plugins/FlutterPlugin.java | 8 +- .../plugin/common/BasicMessageChannel.java | 2 +- .../plugin/common/BinaryMessenger.java | 9 +- .../flutter/plugin/common/EventChannel.java | 2 +- .../flutter/plugin/common/MethodChannel.java | 22 +-- .../editing/InputConnectionAdaptor.java | 24 ++-- .../plugin/editing/TextInputPlugin.java | 124 +++++++++++++--- .../io/flutter/view/FlutterNativeView.java | 4 +- .../android/io/flutter/view/FlutterView.java | 59 +++----- .../engine/dart/DartMessengerTest.java | 22 +-- .../editing/InputConnectionAdaptorTest.java | 134 ++++++++++-------- tools/android_lint/lint.xml | 2 + tools/android_lint/project.xml | 4 + 16 files changed, 360 insertions(+), 234 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java index 5261e0a2f02eb..cae465a363da0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java @@ -14,6 +14,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.Lifecycle; +import androidx.tracing.Trace; import io.flutter.Log; import io.flutter.embedding.android.ExclusiveAppComponent; import io.flutter.embedding.engine.loader.FlutterLoader; @@ -32,7 +33,6 @@ import io.flutter.embedding.engine.plugins.service.ServiceAware; import io.flutter.embedding.engine.plugins.service.ServiceControlSurface; import io.flutter.embedding.engine.plugins.service.ServicePluginBinding; -import io.flutter.util.TraceSection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -105,8 +105,8 @@ flutterEngine, flutterEngine.getDartExecutor(), flutterEngine.getRenderer(), - new DefaultFlutterAssets(flutterLoader), - flutterEngine.getPlatformViewsController().getRegistry()); + flutterEngine.getPlatformViewsController().getRegistry(), + new DefaultFlutterAssets(flutterLoader)); } public void destroy() { @@ -123,7 +123,8 @@ public void destroy() { @Override public void add(@NonNull FlutterPlugin plugin) { - TraceSection.begin("FlutterEngineConnectionRegistry#add " + plugin.getClass().getSimpleName()); + Trace.beginSection("FlutterEngineConnectionRegistry#add " + plugin.getClass().getSimpleName()); + try { if (has(plugin.getClass())) { Log.w( @@ -191,7 +192,7 @@ public void add(@NonNull FlutterPlugin plugin) { } } } finally { - TraceSection.end(); + Trace.endSection(); } } @@ -219,8 +220,10 @@ public void remove(@NonNull Class pluginClass) { return; } - TraceSection.begin("FlutterEngineConnectionRegistry#remove " + pluginClass.getSimpleName()); + Trace.beginSection("FlutterEngineConnectionRegistry#remove " + pluginClass.getSimpleName()); + try { + Log.v(TAG, "Removing plugin: " + plugin); // For ActivityAware plugins, notify the plugin that it is detached from // an Activity if an Activity is currently attached to this engine. Then // remove the plugin from our set of ActivityAware plugins. @@ -270,7 +273,7 @@ public void remove(@NonNull Class pluginClass) { plugin.onDetachedFromEngine(pluginBinding); plugins.remove(pluginClass); } finally { - TraceSection.end(); + Trace.endSection(); } } @@ -313,8 +316,16 @@ private Activity attachedActivity() { @Override public void attachToActivity( @NonNull ExclusiveAppComponent exclusiveActivity, @NonNull Lifecycle lifecycle) { - TraceSection.begin("FlutterEngineConnectionRegistry#attachToActivity"); + Trace.beginSection("FlutterEngineConnectionRegistry#attachToActivity"); + try { + Log.v( + TAG, + "Attaching to an exclusive Activity: " + + exclusiveActivity.getAppComponent() + + (isAttachedToActivity() ? " evicting previous activity " + attachedActivity() : "") + + "." + + (isWaitingForActivityReattachment ? " This is after a config change." : "")); if (this.exclusiveActivity != null) { this.exclusiveActivity.detachFromFlutterEngine(); } @@ -323,19 +334,13 @@ public void attachToActivity( this.exclusiveActivity = exclusiveActivity; attachToActivityInternal(exclusiveActivity.getAppComponent(), lifecycle); } finally { - TraceSection.end(); + Trace.endSection(); } } private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifecycle lifecycle) { this.activityPluginBinding = new FlutterEngineActivityPluginBinding(activity, lifecycle); - final boolean useSoftwareRendering = - activity - .getIntent() - .getBooleanExtra(FlutterShellArgs.ARG_KEY_ENABLE_SOFTWARE_RENDERING, false); - flutterEngine.getPlatformViewsController().setSoftwareRendering(useSoftwareRendering); - // Activate the PlatformViewsController. This must happen before any plugins attempt // to use it, otherwise an error stack trace will appear that says there is no // flutter/platform_views channel. @@ -357,7 +362,9 @@ private void attachToActivityInternal(@NonNull Activity activity, @NonNull Lifec @Override public void detachFromActivityForConfigChanges() { if (isAttachedToActivity()) { - TraceSection.begin("FlutterEngineConnectionRegistry#detachFromActivityForConfigChanges"); + Trace.beginSection("FlutterEngineConnectionRegistry#detachFromActivityForConfigChanges"); + Log.v(TAG, "Detaching from an Activity for config changes: " + attachedActivity()); + try { isWaitingForActivityReattachment = true; @@ -367,7 +374,7 @@ public void detachFromActivityForConfigChanges() { detachFromActivityInternal(); } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e(TAG, "Attempted to detach plugins from an Activity when no Activity was attached."); @@ -377,15 +384,17 @@ public void detachFromActivityForConfigChanges() { @Override public void detachFromActivity() { if (isAttachedToActivity()) { - TraceSection.begin("FlutterEngineConnectionRegistry#detachFromActivity"); + Trace.beginSection("FlutterEngineConnectionRegistry#detachFromActivity"); + try { + Log.v(TAG, "Detaching from an Activity: " + attachedActivity()); for (ActivityAware activityAware : activityAwarePlugins.values()) { activityAware.onDetachedFromActivity(); } detachFromActivityInternal(); } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e(TAG, "Attempted to detach plugins from an Activity when no Activity was attached."); @@ -403,13 +412,15 @@ private void detachFromActivityInternal() { @Override public boolean onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResult) { + Log.v(TAG, "Forwarding onRequestPermissionsResult() to plugins."); if (isAttachedToActivity()) { - TraceSection.begin("FlutterEngineConnectionRegistry#onRequestPermissionsResult"); + Trace.beginSection("FlutterEngineConnectionRegistry#onRequestPermissionsResult"); + try { return activityPluginBinding.onRequestPermissionsResult( requestCode, permissions, grantResult); } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e( @@ -422,12 +433,14 @@ public boolean onRequestPermissionsResult( @Override public boolean onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + Log.v(TAG, "Forwarding onActivityResult() to plugins."); if (isAttachedToActivity()) { - TraceSection.begin("FlutterEngineConnectionRegistry#onActivityResult"); + Trace.beginSection("FlutterEngineConnectionRegistry#onActivityResult"); + try { return activityPluginBinding.onActivityResult(requestCode, resultCode, data); } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e( @@ -440,12 +453,14 @@ public boolean onActivityResult(int requestCode, int resultCode, @Nullable Inten @Override public void onNewIntent(@NonNull Intent intent) { + Log.v(TAG, "Forwarding onNewIntent() to plugins."); if (isAttachedToActivity()) { - TraceSection.begin("FlutterEngineConnectionRegistry#onNewIntent"); + Trace.beginSection("FlutterEngineConnectionRegistry#onNewIntent"); + try { activityPluginBinding.onNewIntent(intent); } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e( @@ -457,12 +472,14 @@ public void onNewIntent(@NonNull Intent intent) { @Override public void onUserLeaveHint() { + Log.v(TAG, "Forwarding onUserLeaveHint() to plugins."); if (isAttachedToActivity()) { - TraceSection.begin("FlutterEngineConnectionRegistry#onUserLeaveHint"); + Trace.beginSection("FlutterEngineConnectionRegistry#onUserLeaveHint"); + try { activityPluginBinding.onUserLeaveHint(); } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e( @@ -474,12 +491,14 @@ public void onUserLeaveHint() { @Override public void onSaveInstanceState(@NonNull Bundle bundle) { + Log.v(TAG, "Forwarding onSaveInstanceState() to plugins."); if (isAttachedToActivity()) { - TraceSection.begin("FlutterEngineConnectionRegistry#onSaveInstanceState"); + Trace.beginSection("FlutterEngineConnectionRegistry#onSaveInstanceState"); + try { activityPluginBinding.onSaveInstanceState(bundle); } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e( @@ -491,12 +510,14 @@ public void onSaveInstanceState(@NonNull Bundle bundle) { @Override public void onRestoreInstanceState(@Nullable Bundle bundle) { + Log.v(TAG, "Forwarding onRestoreInstanceState() to plugins."); if (isAttachedToActivity()) { - TraceSection.begin("FlutterEngineConnectionRegistry#onRestoreInstanceState"); + Trace.beginSection("FlutterEngineConnectionRegistry#onRestoreInstanceState"); + try { activityPluginBinding.onRestoreInstanceState(bundle); } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e( @@ -515,7 +536,9 @@ private boolean isAttachedToService() { @Override public void attachToService( @NonNull Service service, @Nullable Lifecycle lifecycle, boolean isForeground) { - TraceSection.begin("FlutterEngineConnectionRegistry#attachToService"); + Trace.beginSection("FlutterEngineConnectionRegistry#attachToService"); + Log.v(TAG, "Attaching to a Service: " + service); + try { // If we were already attached to an Android component, detach from it. detachFromAppComponent(); @@ -528,14 +551,16 @@ public void attachToService( serviceAware.onAttachedToService(servicePluginBinding); } } finally { - TraceSection.end(); + Trace.endSection(); } } @Override public void detachFromService() { if (isAttachedToService()) { - TraceSection.begin("FlutterEngineConnectionRegistry#detachFromService"); + Trace.beginSection("FlutterEngineConnectionRegistry#detachFromService"); + Log.v(TAG, "Detaching from a Service: " + service); + try { // Notify all ServiceAware plugins that they are no longer attached to a Service. for (ServiceAware serviceAware : serviceAwarePlugins.values()) { @@ -545,7 +570,7 @@ public void detachFromService() { service = null; servicePluginBinding = null; } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e(TAG, "Attempted to detach plugins from a Service when no Service was attached."); @@ -555,11 +580,13 @@ public void detachFromService() { @Override public void onMoveToForeground() { if (isAttachedToService()) { - TraceSection.begin("FlutterEngineConnectionRegistry#onMoveToForeground"); + Trace.beginSection("FlutterEngineConnectionRegistry#onMoveToForeground"); + try { + Log.v(TAG, "Attached Service moved to foreground."); servicePluginBinding.onMoveToForeground(); } finally { - TraceSection.end(); + Trace.endSection(); } } } @@ -567,12 +594,13 @@ public void onMoveToForeground() { @Override public void onMoveToBackground() { if (isAttachedToService()) { - TraceSection.begin("FlutterEngineConnectionRegistry#onMoveToBackground"); - ; + Trace.beginSection("FlutterEngineConnectionRegistry#onMoveToBackground"); + Log.v(TAG, "Attached Service moved to background."); + try { servicePluginBinding.onMoveToBackground(); } finally { - TraceSection.end(); + Trace.endSection(); } } } @@ -586,7 +614,9 @@ private boolean isAttachedToBroadcastReceiver() { @Override public void attachToBroadcastReceiver( @NonNull BroadcastReceiver broadcastReceiver, @NonNull Lifecycle lifecycle) { - TraceSection.begin("FlutterEngineConnectionRegistry#attachToBroadcastReceiver"); + Trace.beginSection("FlutterEngineConnectionRegistry#attachToBroadcastReceiver"); + Log.v(TAG, "Attaching to BroadcastReceiver: " + broadcastReceiver); + try { // If we were already attached to an Android component, detach from it. detachFromAppComponent(); @@ -603,14 +633,16 @@ public void attachToBroadcastReceiver( broadcastReceiverAware.onAttachedToBroadcastReceiver(broadcastReceiverPluginBinding); } } finally { - TraceSection.end(); + Trace.endSection(); } } @Override public void detachFromBroadcastReceiver() { if (isAttachedToBroadcastReceiver()) { - TraceSection.begin("FlutterEngineConnectionRegistry#detachFromBroadcastReceiver"); + Trace.beginSection("FlutterEngineConnectionRegistry#detachFromBroadcastReceiver"); + Log.v(TAG, "Detaching from BroadcastReceiver: " + broadcastReceiver); + try { // Notify all BroadcastReceiverAware plugins that they are no longer attached to a // BroadcastReceiver. @@ -619,7 +651,7 @@ public void detachFromBroadcastReceiver() { broadcastReceiverAware.onDetachedFromBroadcastReceiver(); } } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e( @@ -638,8 +670,9 @@ private boolean isAttachedToContentProvider() { @Override public void attachToContentProvider( @NonNull ContentProvider contentProvider, @NonNull Lifecycle lifecycle) { + Trace.beginSection("FlutterEngineConnectionRegistry#attachToContentProvider"); + Log.v(TAG, "Attaching to ContentProvider: " + contentProvider); - TraceSection.begin("FlutterEngineConnectionRegistry#attachToContentProvider"); try { // If we were already attached to an Android component, detach from it. detachFromAppComponent(); @@ -656,14 +689,16 @@ public void attachToContentProvider( contentProviderAware.onAttachedToContentProvider(contentProviderPluginBinding); } } finally { - TraceSection.end(); + Trace.endSection(); } } @Override public void detachFromContentProvider() { if (isAttachedToContentProvider()) { - TraceSection.begin("FlutterEngineConnectionRegistry#detachFromContentProvider"); + Trace.beginSection("FlutterEngineConnectionRegistry#detachFromContentProvider"); + Log.v(TAG, "Detaching from ContentProvider: " + contentProvider); + try { // Notify all ContentProviderAware plugins that they are no longer attached to a // ContentProvider. @@ -671,7 +706,7 @@ public void detachFromContentProvider() { contentProviderAware.onDetachedFromContentProvider(); } } finally { - TraceSection.end(); + Trace.endSection(); } } else { Log.e( diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java index db4d6cf70e96d..2d9e71c68dede 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java @@ -8,13 +8,13 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import androidx.tracing.Trace; import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StringCodec; -import io.flutter.util.TraceSection; import io.flutter.view.FlutterCallbackInformation; import java.nio.ByteBuffer; import java.util.List; @@ -141,9 +141,10 @@ public void executeDartEntrypoint( return; } - TraceSection.begin("DartExecutor#executeDartEntrypoint"); + Trace.beginSection("DartExecutor#executeDartEntrypoint"); + Log.v(TAG, "Executing Dart entrypoint: " + dartEntrypoint); + try { - Log.v(TAG, "Executing Dart entrypoint: " + dartEntrypoint); flutterJNI.runBundleAndSnapshotFromLibrary( dartEntrypoint.pathToBundle, dartEntrypoint.dartEntrypointFunctionName, @@ -153,7 +154,7 @@ public void executeDartEntrypoint( isApplicationRunning = true; } finally { - TraceSection.end(); + Trace.endSection(); } } @@ -170,9 +171,10 @@ public void executeDartCallback(@NonNull DartCallback dartCallback) { return; } - TraceSection.begin("DartExecutor#executeDartCallback"); + Trace.beginSection("DartExecutor#executeDartCallback"); + Log.v(TAG, "Executing Dart callback: " + dartCallback); + try { - Log.v(TAG, "Executing Dart callback: " + dartCallback); flutterJNI.runBundleAndSnapshotFromLibrary( dartCallback.pathToBundle, dartCallback.callbackHandle.callbackName, @@ -182,7 +184,7 @@ public void executeDartCallback(@NonNull DartCallback dartCallback) { isApplicationRunning = true; } finally { - TraceSection.end(); + Trace.endSection(); } } @@ -238,9 +240,9 @@ public void setMessageHandler( @UiThread public void setMessageHandler( @NonNull String channel, - @Nullable TaskQueue taskQueue, - @Nullable BinaryMessenger.BinaryMessageHandler handler) { - binaryMessenger.setMessageHandler(channel, taskQueue, handler); + @Nullable BinaryMessenger.BinaryMessageHandler handler, + @Nullable TaskQueue taskQueue) { + binaryMessenger.setMessageHandler(channel, handler, taskQueue); } /** @deprecated Use {@link #getBinaryMessenger()} instead. */ @@ -489,9 +491,9 @@ public void setMessageHandler( @UiThread public void setMessageHandler( @NonNull String channel, - @Nullable TaskQueue taskQueue, - @Nullable BinaryMessenger.BinaryMessageHandler handler) { - messenger.setMessageHandler(channel, taskQueue, handler); + @Nullable BinaryMessenger.BinaryMessageHandler handler, + @Nullable TaskQueue taskQueue) { + messenger.setMessageHandler(channel, handler, taskQueue); } @Override diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java index 3900f7e672cfe..9a7658d069a4a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java @@ -7,11 +7,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import androidx.tracing.Trace; import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.util.TraceSection; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.LinkedList; @@ -197,14 +197,14 @@ public TaskQueue makeBackgroundTaskQueue(TaskQueueOptions options) { @Override public void setMessageHandler( @NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) { - setMessageHandler(channel, null, handler); + setMessageHandler(channel, handler, null); } @Override public void setMessageHandler( @NonNull String channel, - @Nullable TaskQueue taskQueue, - @Nullable BinaryMessenger.BinaryMessageHandler handler) { + @Nullable BinaryMessenger.BinaryMessageHandler handler, + @Nullable TaskQueue taskQueue) { if (handler == null) { Log.v(TAG, "Removing handler for channel '" + channel + "'"); synchronized (handlersLock) { @@ -269,9 +269,10 @@ public void send( @NonNull String channel, @Nullable ByteBuffer message, @Nullable BinaryMessenger.BinaryReply callback) { - TraceSection.begin("DartMessenger#send on " + channel); + Trace.beginSection("DartMessenger#send on " + channel); + Log.v(TAG, "Sending message with callback over channel '" + channel + "'"); + try { - Log.v(TAG, "Sending message with callback over channel '" + channel + "'"); int replyId = nextReplyId++; if (callback != null) { pendingReplies.put(replyId, callback); @@ -282,7 +283,7 @@ public void send( flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId); } } finally { - TraceSection.end(); + Trace.endSection(); } } @@ -314,7 +315,7 @@ private void dispatchMessageToQueue( final DartMessengerTaskQueue taskQueue = (handlerInfo != null) ? handlerInfo.taskQueue : null; Runnable myRunnable = () -> { - TraceSection.begin("DartMessenger#handleMessageFromDart on " + channel); + Trace.beginSection("DartMessenger#handleMessageFromDart on " + channel); try { invokeHandler(handlerInfo, message, replyId); if (message != null && message.isDirect()) { @@ -325,7 +326,7 @@ private void dispatchMessageToQueue( } finally { // This is deleting the data underneath the message object. flutterJNI.cleanupMessageData(messageData); - TraceSection.end(); + Trace.endSection(); } }; final DartMessengerTaskQueue nonnullTaskQueue = diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java b/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java index ae83242fcb1c3..c9ed009a44ef1 100644 --- a/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java +++ b/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java @@ -105,22 +105,22 @@ class FlutterPluginBinding { private final FlutterEngine flutterEngine; private final BinaryMessenger binaryMessenger; private final TextureRegistry textureRegistry; - private final FlutterAssets flutterAssets; private final PlatformViewRegistry platformViewRegistry; + private final FlutterAssets flutterAssets; public FlutterPluginBinding( @NonNull Context applicationContext, @NonNull FlutterEngine flutterEngine, @NonNull BinaryMessenger binaryMessenger, @NonNull TextureRegistry textureRegistry, - @NonNull FlutterAssets flutterAssets, - @NonNull PlatformViewRegistry platformViewRegistry) { + @NonNull PlatformViewRegistry platformViewRegistry, + @NonNull FlutterAssets flutterAssets) { this.applicationContext = applicationContext; this.flutterEngine = flutterEngine; this.binaryMessenger = binaryMessenger; this.textureRegistry = textureRegistry; - this.flutterAssets = flutterAssets; this.platformViewRegistry = platformViewRegistry; + this.flutterAssets = flutterAssets; } @NonNull diff --git a/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java b/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java index 8077180fe5513..284df4f03fe48 100644 --- a/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/BasicMessageChannel.java @@ -127,7 +127,7 @@ public void setMessageHandler(@Nullable final MessageHandler handler) { // See https://github.com/flutter/flutter/issues/92582. if (taskQueue != null) { messenger.setMessageHandler( - name, taskQueue, handler == null ? null : new IncomingMessageHandler(handler)); + name, handler == null ? null : new IncomingMessageHandler(handler), taskQueue); } else { messenger.setMessageHandler( name, handler == null ? null : new IncomingMessageHandler(handler)); diff --git a/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java b/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java index 264d7626db06d..2466a01ade303 100644 --- a/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java +++ b/shell/platform/android/io/flutter/plugin/common/BinaryMessenger.java @@ -71,9 +71,8 @@ default TaskQueue makeBackgroundTaskQueue() { /** * Creates a TaskQueue that executes the tasks serially on a background thread. * - *

{@link TaskQueueOptions} can be used to configure the task queue to execute tasks - * concurrently. Doing so can be more performant, though users need to ensure that the task - * handlers are thread-safe. + *

There is no guarantee that the tasks will execute on the same thread, just that execution is + * serial. */ @UiThread default TaskQueue makeBackgroundTaskQueue(TaskQueueOptions options) { @@ -139,8 +138,8 @@ default TaskQueue makeBackgroundTaskQueue(TaskQueueOptions options) { @UiThread default void setMessageHandler( @NonNull String channel, - @Nullable TaskQueue taskQueue, - @Nullable BinaryMessageHandler handler) { + @Nullable BinaryMessageHandler handler, + @Nullable TaskQueue taskQueue) { // TODO(92582): Remove default implementation when it is safe for Google Flutter users. if (taskQueue != null) { throw new UnsupportedOperationException( diff --git a/shell/platform/android/io/flutter/plugin/common/EventChannel.java b/shell/platform/android/io/flutter/plugin/common/EventChannel.java index 265b6781845d3..f8835f90904c4 100644 --- a/shell/platform/android/io/flutter/plugin/common/EventChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/EventChannel.java @@ -110,7 +110,7 @@ public void setStreamHandler(final StreamHandler handler) { // See https://github.com/flutter/flutter/issues/92582. if (taskQueue != null) { messenger.setMessageHandler( - name, taskQueue, handler == null ? null : new IncomingStreamRequestHandler(handler)); + name, handler == null ? null : new IncomingStreamRequestHandler(handler), taskQueue); } else { messenger.setMessageHandler( name, handler == null ? null : new IncomingStreamRequestHandler(handler)); diff --git a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java index e2ebfc6b6733d..795bd70ba091d 100644 --- a/shell/platform/android/io/flutter/plugin/common/MethodChannel.java +++ b/shell/platform/android/io/flutter/plugin/common/MethodChannel.java @@ -44,7 +44,7 @@ public class MethodChannel { * @param messenger a {@link BinaryMessenger}. * @param name a channel name String. */ - public MethodChannel(@NonNull BinaryMessenger messenger, @NonNull String name) { + public MethodChannel(BinaryMessenger messenger, String name) { this(messenger, name, StandardMethodCodec.INSTANCE); } @@ -56,8 +56,7 @@ public MethodChannel(@NonNull BinaryMessenger messenger, @NonNull String name) { * @param name a channel name String. * @param codec a {@link MessageCodec}. */ - public MethodChannel( - @NonNull BinaryMessenger messenger, @NonNull String name, @NonNull MethodCodec codec) { + public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec) { this(messenger, name, codec, null); } @@ -73,9 +72,9 @@ public MethodChannel( * BinaryMessenger#makeBackgroundTaskQueue()}. */ public MethodChannel( - @NonNull BinaryMessenger messenger, - @NonNull String name, - @NonNull MethodCodec codec, + BinaryMessenger messenger, + String name, + MethodCodec codec, @Nullable BinaryMessenger.TaskQueue taskQueue) { if (BuildConfig.DEBUG) { if (messenger == null) { @@ -115,8 +114,7 @@ public void invokeMethod(@NonNull String method, @Nullable Object arguments) { * @param callback a {@link Result} callback for the invocation result, or null. */ @UiThread - public void invokeMethod( - @NonNull String method, @Nullable Object arguments, @Nullable Result callback) { + public void invokeMethod(String method, @Nullable Object arguments, @Nullable Result callback) { messenger.send( name, codec.encodeMethodCall(new MethodCall(method, arguments)), @@ -144,7 +142,7 @@ public void setMethodCallHandler(final @Nullable MethodCallHandler handler) { // See https://github.com/flutter/flutter/issues/92582. if (taskQueue != null) { messenger.setMessageHandler( - name, taskQueue, handler == null ? null : new IncomingMethodCallHandler(handler)); + name, handler == null ? null : new IncomingMethodCallHandler(handler), taskQueue); } else { messenger.setMessageHandler( name, handler == null ? null : new IncomingMethodCallHandler(handler)); @@ -203,6 +201,7 @@ public interface Result { * codec. For instance, if you are using {@link StandardMessageCodec} (default), please see * its documentation on what types are supported. */ + @UiThread void success(@Nullable Object result); /** @@ -214,10 +213,11 @@ public interface Result { * supported by the codec. For instance, if you are using {@link StandardMessageCodec} * (default), please see its documentation on what types are supported. */ - void error( - @NonNull String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails); + @UiThread + void error(String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails); /** Handles a call to an unimplemented method. */ + @UiThread void notImplemented(); } diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index ca303651350e0..d3528a9ccc79d 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -23,19 +23,15 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputMethodManager; -import androidx.annotation.NonNull; import io.flutter.Log; +import io.flutter.embedding.android.KeyboardManager; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.systemchannels.TextInputChannel; -public class InputConnectionAdaptor extends BaseInputConnection +class InputConnectionAdaptor extends BaseInputConnection implements ListenableEditingState.EditingStateWatcher { private static final String TAG = "InputConnectionAdaptor"; - public interface KeyboardDelegate { - public boolean handleEvent(@NonNull KeyEvent keyEvent); - } - private final View mFlutterView; private final int mClient; private final TextInputChannel textInputChannel; @@ -48,7 +44,7 @@ public interface KeyboardDelegate { private InputMethodManager mImm; private final Layout mLayout; private FlutterTextUtils flutterTextUtils; - private final KeyboardDelegate keyboardDelegate; + private final KeyboardManager keyboardManager; private int batchEditNestDepth = 0; @SuppressWarnings("deprecation") @@ -56,10 +52,10 @@ public InputConnectionAdaptor( View view, int client, TextInputChannel textInputChannel, + KeyboardManager keyboardManager, ListenableEditingState editable, EditorInfo editorInfo, - FlutterJNI flutterJNI, - KeyboardDelegate keyboardDelegate) { + FlutterJNI flutterJNI) { super(view, true); mFlutterView = view; mClient = client; @@ -67,7 +63,7 @@ public InputConnectionAdaptor( mEditable = editable; mEditable.addEditingStateListener(this); mEditorInfo = editorInfo; - this.keyboardDelegate = keyboardDelegate; + this.keyboardManager = keyboardManager; this.flutterTextUtils = new FlutterTextUtils(flutterJNI); // We create a dummy Layout with max width so that the selection // shifting acts as if all text were in one line. @@ -87,10 +83,10 @@ public InputConnectionAdaptor( View view, int client, TextInputChannel textInputChannel, + KeyboardManager keyboardManager, ListenableEditingState editable, - EditorInfo editorInfo, - KeyboardDelegate keyboardDelegate) { - this(view, client, textInputChannel, editable, editorInfo, new FlutterJNI(), keyboardDelegate); + EditorInfo editorInfo) { + this(view, client, textInputChannel, keyboardManager, editable, editorInfo, new FlutterJNI()); } private ExtractedText getExtractedText(ExtractedTextRequest request) { @@ -276,7 +272,7 @@ private static int clampIndexToEditable(int index, Editable editable) { // occur, and need a chance to be handled by the framework. @Override public boolean sendKeyEvent(KeyEvent event) { - return keyboardDelegate.handleEvent(event); + return keyboardManager.handleEvent(event); } public boolean handleKeyEvent(KeyEvent event) { diff --git a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java index 82db6a62c934f..7ca0febb19c39 100644 --- a/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java +++ b/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java @@ -54,9 +54,15 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch // Initialize the "last seen" text editing values to a non-null value. private TextEditState mLastKnownFrameworkTextEditingState; + // When true following calls to createInputConnection will return the cached lastInputConnection + // if the input + // target is a platform view. See the comments on lockPlatformViewInputConnection for more + // details. + private boolean isInputConnectionLocked; + @SuppressLint("NewApi") public TextInputPlugin( - @NonNull View view, + View view, @NonNull TextInputChannel textInputChannel, @NonNull PlatformViewsController platformViewsController) { mView = view; @@ -99,7 +105,7 @@ public void show() { @Override public void hide() { - if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { + if (inputTarget.type == InputTarget.Type.HC_PLATFORM_VIEW) { notifyViewExited(); } else { hideTextInput(mView); @@ -130,8 +136,8 @@ public void setClient( } @Override - public void setPlatformViewClient(int platformViewId) { - setPlatformViewTextInputClient(platformViewId); + public void setPlatformViewClient(int platformViewId, boolean usesVirtualDisplay) { + setPlatformViewTextInputClient(platformViewId, usesVirtualDisplay); } @Override @@ -176,6 +182,34 @@ ImeSyncDeferringInsetsCallback getImeSyncCallback() { return imeSyncCallback; } + /** + * Use the current platform view input connection until unlockPlatformViewInputConnection is + * called. + * + *

The current input connection instance is cached and any following call to @{link + * createInputConnection} returns the cached connection until unlockPlatformViewInputConnection is + * called. + * + *

This is a no-op if the current input target isn't a platform view. + * + *

This is used to preserve an input connection when moving a platform view from one virtual + * display to another. + */ + public void lockPlatformViewInputConnection() { + if (inputTarget.type == InputTarget.Type.VD_PLATFORM_VIEW) { + isInputConnectionLocked = true; + } + } + + /** + * Unlocks the input connection. + * + *

See also: @{link lockPlatformViewInputConnection}. + */ + public void unlockPlatformViewInputConnection() { + isInputConnectionLocked = false; + } + /** * Detaches the text input plugin from the platform views controller. * @@ -251,18 +285,28 @@ private static int inputTypeFromTextInputType( return textType; } - @Nullable public InputConnection createInputConnection( - @NonNull View view, @NonNull KeyboardManager keyboardManager, @NonNull EditorInfo outAttrs) { + View view, KeyboardManager keyboardManager, EditorInfo outAttrs) { if (inputTarget.type == InputTarget.Type.NO_TARGET) { lastInputConnection = null; return null; } - if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) { + if (inputTarget.type == InputTarget.Type.HC_PLATFORM_VIEW) { return null; } + if (inputTarget.type == InputTarget.Type.VD_PLATFORM_VIEW) { + if (isInputConnectionLocked) { + return lastInputConnection; + } + lastInputConnection = + platformViewsController + .getPlatformViewById(inputTarget.id) + .onCreateInputConnection(outAttrs); + return lastInputConnection; + } + outAttrs.inputType = inputTypeFromTextInputType( configuration.inputType, @@ -297,7 +341,7 @@ public InputConnection createInputConnection( InputConnectionAdaptor connection = new InputConnectionAdaptor( - view, inputTarget.id, textInputChannel, mEditable, outAttrs, keyboardManager); + view, inputTarget.id, textInputChannel, keyboardManager, mEditable, outAttrs); outAttrs.initialSelStart = mEditable.getSelectionStart(); outAttrs.initialSelEnd = mEditable.getSelectionEnd(); @@ -317,7 +361,9 @@ public InputConnection getLastInputConnection() { * input connection. */ public void clearPlatformViewClient(int platformViewId) { - if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW && inputTarget.id == platformViewId) { + if ((inputTarget.type == InputTarget.Type.VD_PLATFORM_VIEW + || inputTarget.type == InputTarget.Type.HC_PLATFORM_VIEW) + && inputTarget.id == platformViewId) { inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); notifyViewExited(); mImm.hideSoftInputFromWindow(mView.getApplicationWindowToken(), 0); @@ -326,7 +372,7 @@ public void clearPlatformViewClient(int platformViewId) { } } - public void sendTextInputAppPrivateCommand(@NonNull String action, @NonNull Bundle data) { + public void sendTextInputAppPrivateCommand(String action, Bundle data) { mImm.sendAppPrivateCommand(mView, action, data); } @@ -378,13 +424,25 @@ void setTextInputClient(int client, TextInputChannel.Configuration configuration // setTextInputClient will be followed by a call to setTextInputEditingState. // Do a restartInput at that time. mRestartInputPending = true; + unlockPlatformViewInputConnection(); lastClientRect = null; mEditable.addEditingStateListener(this); } - private void setPlatformViewTextInputClient(int platformViewId) { - inputTarget = new InputTarget(InputTarget.Type.PLATFORM_VIEW, platformViewId); - lastInputConnection = null; + private void setPlatformViewTextInputClient(int platformViewId, boolean usesVirtualDisplay) { + if (usesVirtualDisplay) { + // We need to make sure that the Flutter view is focused so that no imm operations get short + // circuited. + // Not asking for focus here specifically manifested in a but on API 28 devices where the + // platform view's request to show a keyboard was ignored. + mView.requestFocus(); + inputTarget = new InputTarget(InputTarget.Type.VD_PLATFORM_VIEW, platformViewId); + mImm.restartInput(mView); + mRestartInputPending = false; + } else { + inputTarget = new InputTarget(InputTarget.Type.HC_PLATFORM_VIEW, platformViewId); + lastInputConnection = null; + } } private static boolean composingChanged( @@ -475,10 +533,35 @@ public void inspect(double x, double y) { @VisibleForTesting void clearTextInputClient() { + if (inputTarget.type == InputTarget.Type.VD_PLATFORM_VIEW) { + // This only applies to platform views that use a virtual display. + // Focus changes in the framework tree have no guarantees on the order focus nodes are + // notified. A node + // that lost focus may be notified before or after a node that gained focus. + // When moving the focus from a Flutter text field to an AndroidView, it is possible that the + // Flutter text + // field's focus node will be notified that it lost focus after the AndroidView was notified + // that it gained + // focus. When this happens the text field will send a clearTextInput command which we ignore. + // By doing this we prevent the framework from clearing a platform view input client (the only + // way to do so + // is to set a new framework text client). I don't see an obvious use case for "clearing" a + // platform view's + // text input client, and it may be error prone as we don't know how the platform view manages + // the input + // connection and we probably shouldn't interfere. + // If we ever want to allow the framework to clear a platform view text client we should + // probably consider + // changing the focus manager such that focus nodes that lost focus are notified before focus + // nodes that + // gained focus as part of the same focus event. + return; + } mEditable.removeEditingStateListener(this); notifyViewExited(); updateAutofillConfigurationIfNeeded(null); inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0); + unlockPlatformViewInputConnection(); lastClientRect = null; } @@ -488,9 +571,12 @@ enum Type { // InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter // framework. FRAMEWORK_CLIENT, - // InputConnection is managed by a platform view that is embeded in the Android view - // hierarchy. - PLATFORM_VIEW, + // InputConnection is managed by an embedded platform view that is backed by a virtual + // display (VD). + VD_PLATFORM_VIEW, + // InputConnection is managed by an embedded platform view that is embeded in the Android view + // hierarchy, and uses hybrid composition (HC). + HC_PLATFORM_VIEW, } public InputTarget(@NonNull Type type, int id) { @@ -507,7 +593,7 @@ public InputTarget(@NonNull Type type, int id) { } // -------- Start: KeyboardManager Synchronous Responder ------- - public boolean handleKeyEvent(@NonNull KeyEvent keyEvent) { + public boolean handleKeyEvent(KeyEvent keyEvent) { if (!getInputMethodManager().isAcceptingText() || lastInputConnection == null) { return false; } @@ -665,7 +751,7 @@ private void updateAutofillConfigurationIfNeeded(TextInputChannel.Configuration } } - public void onProvideAutofillVirtualStructure(@NonNull ViewStructure structure, int flags) { + public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !needsAutofill()) { return; } @@ -709,7 +795,7 @@ public void onProvideAutofillVirtualStructure(@NonNull ViewStructure structure, } } - public void autofill(@NonNull SparseArray values) { + public void autofill(SparseArray values) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { return; } diff --git a/shell/platform/android/io/flutter/view/FlutterNativeView.java b/shell/platform/android/io/flutter/view/FlutterNativeView.java index bfebce7560d35..190a9ecbb160e 100644 --- a/shell/platform/android/io/flutter/view/FlutterNativeView.java +++ b/shell/platform/android/io/flutter/view/FlutterNativeView.java @@ -159,8 +159,8 @@ public void setMessageHandler(String channel, BinaryMessageHandler handler) { @Override @UiThread - public void setMessageHandler(String channel, TaskQueue taskQueue, BinaryMessageHandler handler) { - dartExecutor.getBinaryMessenger().setMessageHandler(channel, taskQueue, handler); + public void setMessageHandler(String channel, BinaryMessageHandler handler, TaskQueue taskQueue) { + dartExecutor.getBinaryMessenger().setMessageHandler(channel, handler, taskQueue); } @Override diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index 01fe20a762fb5..9b86ed08193fe 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -26,6 +26,7 @@ import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; +import android.view.View; import android.view.ViewConfiguration; import android.view.ViewStructure; import android.view.WindowInsets; @@ -42,11 +43,13 @@ import io.flutter.Log; import io.flutter.app.FlutterPluginRegistry; import io.flutter.embedding.android.AndroidTouchProcessor; +import io.flutter.embedding.android.KeyChannelResponder; import io.flutter.embedding.android.KeyboardManager; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.SurfaceTextureWrapper; import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; +import io.flutter.embedding.engine.systemchannels.KeyEventChannel; import io.flutter.embedding.engine.systemchannels.LifecycleChannel; import io.flutter.embedding.engine.systemchannels.LocalizationChannel; import io.flutter.embedding.engine.systemchannels.MouseCursorChannel; @@ -76,10 +79,7 @@ */ @Deprecated public class FlutterView extends SurfaceView - implements BinaryMessenger, - TextureRegistry, - MouseCursorPlugin.MouseCursorViewDelegate, - KeyboardManager.ViewDelegate { + implements BinaryMessenger, TextureRegistry, MouseCursorPlugin.MouseCursorViewDelegate { /** * Interface for those objects that maintain and expose a reference to a {@code FlutterView} (such * as a full-screen Flutter activity). @@ -122,6 +122,7 @@ static final class ViewportMetrics { private final DartExecutor dartExecutor; private final FlutterRenderer flutterRenderer; private final NavigationChannel navigationChannel; + private final KeyEventChannel keyEventChannel; private final LifecycleChannel lifecycleChannel; private final LocalizationChannel localizationChannel; private final PlatformChannel platformChannel; @@ -212,6 +213,7 @@ public void surfaceDestroyed(SurfaceHolder holder) { // Create all platform channels navigationChannel = new NavigationChannel(dartExecutor); + keyEventChannel = new KeyEventChannel(dartExecutor); lifecycleChannel = new LifecycleChannel(dartExecutor); localizationChannel = new LocalizationChannel(dartExecutor); platformChannel = new PlatformChannel(dartExecutor); @@ -232,7 +234,11 @@ public void onPostResume() { mNativeView.getPluginRegistry().getPlatformViewsController(); mTextInputPlugin = new TextInputPlugin(this, new TextInputChannel(dartExecutor), platformViewsController); - mKeyboardManager = new KeyboardManager(this); + mKeyboardManager = + new KeyboardManager( + this, + mTextInputPlugin, + new KeyChannelResponder[] {new KeyChannelResponder(keyEventChannel)}); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { mMouseCursorPlugin = new MouseCursorPlugin(this, new MouseCursorChannel(dartExecutor)); @@ -421,6 +427,14 @@ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return mTextInputPlugin.createInputConnection(this, mKeyboardManager, outAttrs); } + @Override + public boolean checkInputConnectionProxy(View view) { + return mNativeView + .getPluginRegistry() + .getPlatformViewsController() + .checkInputConnectionProxy(view); + } + @Override public void onProvideAutofillVirtualStructure(ViewStructure structure, int flags) { super.onProvideAutofillVirtualStructure(structure, flags); @@ -809,8 +823,6 @@ private void releaseAccessibilityNodeProvider() { } } - // -------- Start: Mouse ------- - @Override @TargetApi(Build.VERSION_CODES.N) @RequiresApi(Build.VERSION_CODES.N) @@ -819,27 +831,6 @@ public PointerIcon getSystemPointerIcon(int type) { return PointerIcon.getSystemIcon(getContext(), type); } - // -------- End: Mouse ------- - - // -------- Start: Keyboard ------- - - @Override - public BinaryMessenger getBinaryMessenger() { - return this; - } - - @Override - public boolean onTextInputKeyEvent(@NonNull KeyEvent keyEvent) { - return mTextInputPlugin.handleKeyEvent(keyEvent); - } - - @Override - public void redispatch(@NonNull KeyEvent keyEvent) { - getRootView().dispatchKeyEvent(keyEvent); - } - - // -------- End: Keyboard ------- - @Override @UiThread public TaskQueue makeBackgroundTaskQueue(TaskQueueOptions options) { @@ -864,17 +855,14 @@ public void send(String channel, ByteBuffer message, BinaryReply callback) { @Override @UiThread - public void setMessageHandler(@NonNull String channel, @NonNull BinaryMessageHandler handler) { + public void setMessageHandler(String channel, BinaryMessageHandler handler) { mNativeView.setMessageHandler(channel, handler); } @Override @UiThread - public void setMessageHandler( - @NonNull String channel, - @NonNull TaskQueue taskQueue, - @NonNull BinaryMessageHandler handler) { - mNativeView.setMessageHandler(channel, taskQueue, handler); + public void setMessageHandler(String channel, BinaryMessageHandler handler, TaskQueue taskQueue) { + mNativeView.setMessageHandler(channel, handler, taskQueue); } /** Listener will be called on the Android UI thread once when Flutter renders the first frame. */ @@ -883,14 +871,12 @@ public interface FirstFrameListener { } @Override - @NonNull public TextureRegistry.SurfaceTextureEntry createSurfaceTexture() { final SurfaceTexture surfaceTexture = new SurfaceTexture(0); return registerSurfaceTexture(surfaceTexture); } @Override - @NonNull public TextureRegistry.SurfaceTextureEntry registerSurfaceTexture( @NonNull SurfaceTexture surfaceTexture) { surfaceTexture.detachFromGLContext(); @@ -935,7 +921,6 @@ public void onFrameAvailable(SurfaceTexture texture) { // still be called by a stale reference after released==true and mNativeView==null. return; } - mNativeView .getFlutterJNI() .markTextureFrameAvailable(SurfaceTextureRegistryEntry.this.id); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java index 34783f93f4a63..1585aaa0b6d70 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartMessengerTest.java @@ -5,16 +5,15 @@ import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertArrayEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.robolectric.Shadows.shadowOf; -import androidx.test.ext.junit.runners.AndroidJUnit4; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.dart.DartMessenger.DartMessengerTaskQueue; import io.flutter.plugin.common.BinaryMessenger; @@ -29,10 +28,11 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; +import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) -@RunWith(AndroidJUnit4.class) +@RunWith(RobolectricTestRunner.class) public class DartMessengerTest { SynchronousTaskQueue synchronousTaskQueue = new SynchronousTaskQueue(); @@ -71,7 +71,7 @@ public void itHandlesErrors() { .when(throwingHandler) .onMessage(any(ByteBuffer.class), any(DartMessenger.Reply.class)); BinaryMessenger.TaskQueue taskQueue = messenger.makeBackgroundTaskQueue(); - messenger.setMessageHandler("test", taskQueue, throwingHandler); + messenger.setMessageHandler("test", throwingHandler, taskQueue); messenger.handleMessageFromDart("test", ByteBuffer.allocate(0), 0, 0); assertNotNull(reportingHandler.latestException); assertTrue(reportingHandler.latestException instanceof AssertionError); @@ -91,7 +91,7 @@ public void givesDirectByteBuffer() { wasDirect[0] = message.isDirect(); }; BinaryMessenger.TaskQueue taskQueue = messenger.makeBackgroundTaskQueue(); - messenger.setMessageHandler(channel, taskQueue, handler); + messenger.setMessageHandler(channel, handler, taskQueue); final ByteBuffer message = ByteBuffer.allocateDirect(4 * 2); message.rewind(); message.putChar('a'); @@ -117,7 +117,7 @@ public void directByteBufferLimitZeroAfterUsage() { assertEquals(bufferSize, byteBuffers[0].limit()); }; BinaryMessenger.TaskQueue taskQueue = messenger.makeBackgroundTaskQueue(); - messenger.setMessageHandler(channel, taskQueue, handler); + messenger.setMessageHandler(channel, handler, taskQueue); final ByteBuffer message = ByteBuffer.allocateDirect(bufferSize); message.rewind(); message.putChar('a'); @@ -195,7 +195,7 @@ public void cleansUpMessageDataOnError() throws InterruptedException { (ByteBuffer message, BinaryMessenger.BinaryReply reply) -> { throw new RuntimeException("hello"); }; - messenger.setMessageHandler(channel, taskQueue, handler); + messenger.setMessageHandler(channel, handler, taskQueue); final ByteBuffer message = ByteBuffer.allocateDirect(4 * 2); final int replyId = 1; final long messageData = 1234; @@ -240,7 +240,7 @@ public void buffersResponseWhenHandlerIsNotSet() throws InterruptedException { (ByteBuffer msg, BinaryMessenger.BinaryReply reply) -> { reply.reply(ByteBuffer.wrap("done".getBytes())); }; - messenger.setMessageHandler(channel, taskQueue, handler); + messenger.setMessageHandler(channel, handler, taskQueue); shadowOf(getMainLooper()).idle(); verify(fakeFlutterJni, never()).invokePlatformMessageEmptyResponseCallback(eq(replyId)); @@ -293,7 +293,7 @@ public void emptyResponseWhenHandlerIsUnregistered() throws InterruptedException (ByteBuffer msg, BinaryMessenger.BinaryReply reply) -> { reply.reply(ByteBuffer.wrap("done".getBytes())); }; - messenger.setMessageHandler(channel, taskQueue, handler); + messenger.setMessageHandler(channel, handler, taskQueue); shadowOf(getMainLooper()).idle(); verify(fakeFlutterJni, never()).invokePlatformMessageEmptyResponseCallback(eq(replyId)); diff --git a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java index db8c82c3c57cd..37a73cbf65978 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java @@ -4,9 +4,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; @@ -32,8 +32,6 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; import com.ibm.icu.lang.UCharacter; import com.ibm.icu.lang.UProperty; import io.flutter.embedding.android.KeyboardManager; @@ -52,6 +50,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -61,9 +61,8 @@ @Config( manifest = Config.NONE, shadows = {InputConnectionAdaptorTest.TestImm.class}) -@RunWith(AndroidJUnit4.class) +@RunWith(RobolectricTestRunner.class) public class InputConnectionAdaptorTest { - private final Context ctx = ApplicationProvider.getApplicationContext(); @Mock KeyboardManager mockKeyboardManager; // Verifies the method and arguments for a captured method call. private void verifyMethodCall(ByteBuffer buffer, String methodName, String[] expectedArgs) @@ -82,12 +81,12 @@ private void verifyMethodCall(ByteBuffer buffer, String methodName, String[] exp @Before public void setUp() { - MockitoAnnotations.openMocks(this); + MockitoAnnotations.initMocks(this); } @Test public void inputConnectionAdaptor_ReceivesEnter() throws NullPointerException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); FlutterJNI mockFlutterJni = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class))); int inputTargetId = 0; @@ -100,10 +99,10 @@ public void inputConnectionAdaptor_ReceivesEnter() throws NullPointerException { InputConnectionAdaptor inputConnectionAdaptor = new InputConnectionAdaptor( - testView, inputTargetId, textInputChannel, spyEditable, outAttrs, mockKeyboardManager); + testView, inputTargetId, textInputChannel, mockKeyboardManager, spyEditable, outAttrs); // Send an enter key and make sure the Editable received it. - FakeKeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, '\n'); + FakeKeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER); inputConnectionAdaptor.handleKeyEvent(keyEvent); verify(spyEditable, times(1)).insert(eq(0), anyString()); } @@ -123,7 +122,8 @@ public void testPerformContextMenuAction_selectAll() { @Test public void testPerformContextMenuAction_cut() { - ClipboardManager clipboardManager = ctx.getSystemService(ClipboardManager.class); + ClipboardManager clipboardManager = + RuntimeEnvironment.application.getSystemService(ClipboardManager.class); int selStart = 6; int selEnd = 11; ListenableEditingState editable = sampleEditable(selStart, selEnd); @@ -140,7 +140,8 @@ public void testPerformContextMenuAction_cut() { @Test public void testPerformContextMenuAction_copy() { - ClipboardManager clipboardManager = ctx.getSystemService(ClipboardManager.class); + ClipboardManager clipboardManager = + RuntimeEnvironment.application.getSystemService(ClipboardManager.class); int selStart = 6; int selEnd = 11; ListenableEditingState editable = sampleEditable(selStart, selEnd); @@ -159,7 +160,8 @@ public void testPerformContextMenuAction_copy() { @Test public void testPerformContextMenuAction_paste() { - ClipboardManager clipboardManager = ctx.getSystemService(ClipboardManager.class); + ClipboardManager clipboardManager = + RuntimeEnvironment.application.getSystemService(ClipboardManager.class); String textToBePasted = "deadbeef"; clipboardManager.setText(textToBePasted); ListenableEditingState editable = sampleEditable(0, 0); @@ -173,7 +175,7 @@ public void testPerformContextMenuAction_paste() { @Test public void testPerformPrivateCommand_dataIsNull() throws JSONException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); int client = 0; FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); @@ -184,10 +186,10 @@ public void testPerformPrivateCommand_dataIsNull() throws JSONException { testView, client, textInputChannel, + mockKeyboardManager, editable, null, - mockFlutterJNI, - mockKeyboardManager); + mockFlutterJNI); adaptor.performPrivateCommand("actionCommand", null); ArgumentCaptor channelCaptor = ArgumentCaptor.forClass(String.class); @@ -202,7 +204,7 @@ public void testPerformPrivateCommand_dataIsNull() throws JSONException { @Test public void testPerformPrivateCommand_dataIsByteArray() throws JSONException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); int client = 0; FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); @@ -213,10 +215,10 @@ public void testPerformPrivateCommand_dataIsByteArray() throws JSONException { testView, client, textInputChannel, + mockKeyboardManager, editable, null, - mockFlutterJNI, - mockKeyboardManager); + mockFlutterJNI); Bundle bundle = new Bundle(); byte[] buffer = new byte[] {'a', 'b', 'c', 'd'}; @@ -237,7 +239,7 @@ public void testPerformPrivateCommand_dataIsByteArray() throws JSONException { @Test public void testPerformPrivateCommand_dataIsByte() throws JSONException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); int client = 0; FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); @@ -248,10 +250,10 @@ public void testPerformPrivateCommand_dataIsByte() throws JSONException { testView, client, textInputChannel, + mockKeyboardManager, editable, null, - mockFlutterJNI, - mockKeyboardManager); + mockFlutterJNI); Bundle bundle = new Bundle(); byte b = 3; @@ -270,7 +272,7 @@ public void testPerformPrivateCommand_dataIsByte() throws JSONException { @Test public void testPerformPrivateCommand_dataIsCharArray() throws JSONException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); int client = 0; FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); @@ -281,10 +283,10 @@ public void testPerformPrivateCommand_dataIsCharArray() throws JSONException { testView, client, textInputChannel, + mockKeyboardManager, editable, null, - mockFlutterJNI, - mockKeyboardManager); + mockFlutterJNI); Bundle bundle = new Bundle(); char[] buffer = new char[] {'a', 'b', 'c', 'd'}; @@ -306,7 +308,7 @@ public void testPerformPrivateCommand_dataIsCharArray() throws JSONException { @Test public void testPerformPrivateCommand_dataIsChar() throws JSONException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); int client = 0; FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); @@ -317,10 +319,10 @@ public void testPerformPrivateCommand_dataIsChar() throws JSONException { testView, client, textInputChannel, + mockKeyboardManager, editable, null, - mockFlutterJNI, - mockKeyboardManager); + mockFlutterJNI); Bundle bundle = new Bundle(); char b = 'a'; @@ -339,7 +341,7 @@ public void testPerformPrivateCommand_dataIsChar() throws JSONException { @Test public void testPerformPrivateCommand_dataIsCharSequenceArray() throws JSONException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); int client = 0; FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); @@ -350,10 +352,10 @@ public void testPerformPrivateCommand_dataIsCharSequenceArray() throws JSONExcep testView, client, textInputChannel, + mockKeyboardManager, editable, null, - mockFlutterJNI, - mockKeyboardManager); + mockFlutterJNI); Bundle bundle = new Bundle(); CharSequence charSequence1 = new StringBuffer("abc"); @@ -376,7 +378,7 @@ public void testPerformPrivateCommand_dataIsCharSequenceArray() throws JSONExcep @Test public void testPerformPrivateCommand_dataIsCharSequence() throws JSONException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); int client = 0; FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); @@ -387,10 +389,10 @@ public void testPerformPrivateCommand_dataIsCharSequence() throws JSONException testView, client, textInputChannel, + mockKeyboardManager, editable, null, - mockFlutterJNI, - mockKeyboardManager); + mockFlutterJNI); Bundle bundle = new Bundle(); CharSequence charSequence = new StringBuffer("abc"); @@ -411,7 +413,7 @@ public void testPerformPrivateCommand_dataIsCharSequence() throws JSONException @Test public void testPerformPrivateCommand_dataIsFloat() throws JSONException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); int client = 0; FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); @@ -422,10 +424,10 @@ public void testPerformPrivateCommand_dataIsFloat() throws JSONException { testView, client, textInputChannel, + mockKeyboardManager, editable, null, - mockFlutterJNI, - mockKeyboardManager); + mockFlutterJNI); Bundle bundle = new Bundle(); float value = 0.5f; @@ -444,7 +446,7 @@ public void testPerformPrivateCommand_dataIsFloat() throws JSONException { @Test public void testPerformPrivateCommand_dataIsFloatArray() throws JSONException { - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); int client = 0; FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class))); @@ -455,10 +457,10 @@ public void testPerformPrivateCommand_dataIsFloatArray() throws JSONException { testView, client, textInputChannel, + mockKeyboardManager, editable, null, - mockFlutterJNI, - mockKeyboardManager); + mockFlutterJNI); Bundle bundle = new Bundle(); float[] value = {0.5f, 0.6f}; @@ -978,16 +980,18 @@ public void testExtractedText_monitoring() { return; } ListenableEditingState editable = sampleEditable(5, 5); - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( testView, 1, mock(TextInputChannel.class), + mockKeyboardManager, editable, - new EditorInfo(), - mockKeyboardManager); - TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE)); + new EditorInfo()); + TestImm testImm = + Shadow.extract( + RuntimeEnvironment.application.getSystemService(Context.INPUT_METHOD_SERVICE)); testImm.resetStates(); @@ -1033,16 +1037,18 @@ public void testCursorAnchorInfo() { } ListenableEditingState editable = sampleEditable(5, 5); - View testView = new View(ctx); + View testView = new View(RuntimeEnvironment.application); InputConnectionAdaptor adaptor = new InputConnectionAdaptor( testView, 1, mock(TextInputChannel.class), + mockKeyboardManager, editable, - new EditorInfo(), - mockKeyboardManager); - TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE)); + new EditorInfo()); + TestImm testImm = + Shadow.extract( + RuntimeEnvironment.application.getSystemService(Context.INPUT_METHOD_SERVICE)); testImm.resetStates(); @@ -1115,7 +1121,7 @@ public void testDoesNotConsumeBackButton() { ListenableEditingState editable = sampleEditable(0, 0); InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable); - FakeKeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, '\b'); + FakeKeyEvent keyEvent = new FakeKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); boolean didConsume = adaptor.handleKeyEvent(keyEvent); assertFalse(didConsume); @@ -1161,7 +1167,7 @@ public void testCleanUpBatchEndsOnCloseConnection() { private static ListenableEditingState sampleEditable(int selStart, int selEnd) { ListenableEditingState sample = - new ListenableEditingState(null, new View(ApplicationProvider.getApplicationContext())); + new ListenableEditingState(null, new View(RuntimeEnvironment.application)); sample.replace(0, 0, SAMPLE_TEXT); Selection.setSelection(sample, selStart, selEnd); return sample; @@ -1169,7 +1175,7 @@ private static ListenableEditingState sampleEditable(int selStart, int selEnd) { private static ListenableEditingState sampleEditable(int selStart, int selEnd, String text) { ListenableEditingState sample = - new ListenableEditingState(null, new View(ApplicationProvider.getApplicationContext())); + new ListenableEditingState(null, new View(RuntimeEnvironment.application)); sample.replace(0, 0, text); Selection.setSelection(sample, selStart, selEnd); return sample; @@ -1182,23 +1188,23 @@ private static InputConnectionAdaptor sampleInputConnectionAdaptor( private static InputConnectionAdaptor sampleInputConnectionAdaptor( ListenableEditingState editable, KeyboardManager mockKeyboardManager) { - View testView = new View(ApplicationProvider.getApplicationContext()); + View testView = new View(RuntimeEnvironment.application); int client = 0; TextInputChannel textInputChannel = mock(TextInputChannel.class); FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - when(mockFlutterJNI.isCodePointEmoji(anyInt())) + when(mockFlutterJNI.nativeFlutterTextUtilsIsEmoji(anyInt())) .thenAnswer((invocation) -> Emoji.isEmoji((int) invocation.getArguments()[0])); - when(mockFlutterJNI.isCodePointEmojiModifier(anyInt())) + when(mockFlutterJNI.nativeFlutterTextUtilsIsEmojiModifier(anyInt())) .thenAnswer((invocation) -> Emoji.isEmojiModifier((int) invocation.getArguments()[0])); - when(mockFlutterJNI.isCodePointEmojiModifierBase(anyInt())) + when(mockFlutterJNI.nativeFlutterTextUtilsIsEmojiModifierBase(anyInt())) .thenAnswer((invocation) -> Emoji.isEmojiModifierBase((int) invocation.getArguments()[0])); - when(mockFlutterJNI.isCodePointVariantSelector(anyInt())) + when(mockFlutterJNI.nativeFlutterTextUtilsIsVariationSelector(anyInt())) .thenAnswer((invocation) -> Emoji.isVariationSelector((int) invocation.getArguments()[0])); - when(mockFlutterJNI.isCodePointRegionalIndicator(anyInt())) + when(mockFlutterJNI.nativeFlutterTextUtilsIsRegionalIndicator(anyInt())) .thenAnswer( (invocation) -> Emoji.isRegionalIndicatorSymbol((int) invocation.getArguments()[0])); return new InputConnectionAdaptor( - testView, client, textInputChannel, editable, null, mockFlutterJNI, mockKeyboardManager); + testView, client, textInputChannel, mockKeyboardManager, editable, null, mockFlutterJNI); } private static class Emoji { @@ -1257,6 +1263,7 @@ public void updateEditingState( @Implements(InputMethodManager.class) public static class TestImm extends ShadowInputMethodManager { public static int empty = -999; + // private InputMethodSubtype currentInputMethodSubtype; CursorAnchorInfo lastCursorAnchorInfo; int lastExtractedTextToken = empty; ExtractedText lastExtractedText; @@ -1268,6 +1275,15 @@ public static class TestImm extends ShadowInputMethodManager { public TestImm() {} + // @Implementation + // public InputMethodSubtype getCurrentInputMethodSubtype() { + // return currentInputMethodSubtype; + // } + + // public void setCurrentInputMethodSubtype(InputMethodSubtype inputMethodSubtype) { + // this.currentInputMethodSubtype = inputMethodSubtype; + // } + @Implementation public void updateCursorAnchorInfo(View view, CursorAnchorInfo cursorAnchorInfo) { lastCursorAnchorInfo = cursorAnchorInfo; diff --git a/tools/android_lint/lint.xml b/tools/android_lint/lint.xml index bde5802122fbd..c2f3729fa9fd8 100644 --- a/tools/android_lint/lint.xml +++ b/tools/android_lint/lint.xml @@ -14,4 +14,6 @@ + + \ No newline at end of file diff --git a/tools/android_lint/project.xml b/tools/android_lint/project.xml index 23600fc85252d..38b9f65eebcb8 100644 --- a/tools/android_lint/project.xml +++ b/tools/android_lint/project.xml @@ -4,6 +4,7 @@ + @@ -22,6 +23,7 @@ + @@ -33,6 +35,7 @@ + @@ -44,6 +47,7 @@ +