From df7e128583b469ecff84fef11ee1222dcc75b0b2 Mon Sep 17 00:00:00 2001 From: Gray Mackall Date: Wed, 11 Oct 2023 11:49:19 -0700 Subject: [PATCH] Revert "[Android] Add support for text processing actions (#44579)" This reverts commit d93fe2367d21fcbec268d57d0a1a3dd6090c14a8. --- ci/licenses_golden/licenses_flutter | 4 - shell/platform/android/AndroidManifest.xml | 8 - shell/platform/android/BUILD.gn | 2 - .../embedding/engine/FlutterEngine.java | 13 - .../systemchannels/ProcessTextChannel.java | 122 ---------- .../plugin/text/ProcessTextPlugin.java | 192 --------------- .../plugin/text/ProcessTextPluginTest.java | 225 ------------------ 7 files changed, 566 deletions(-) delete mode 100644 shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java delete mode 100644 shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java delete mode 100644 shell/platform/android/test/io/flutter/plugin/text/ProcessTextPluginTest.java diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b8e32aa31564f..3a336880ef306 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -3091,7 +3091,6 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/syst ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java + ../../../flutter/LICENSE @@ -3134,7 +3133,6 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platf ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/util/HandlerCompat.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java + ../../../flutter/LICENSE @@ -5863,7 +5861,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java @@ -5911,7 +5908,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platfor FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTarget.java FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java -FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java FILE: ../../../flutter/shell/platform/android/io/flutter/util/HandlerCompat.java FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java FILE: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java diff --git a/shell/platform/android/AndroidManifest.xml b/shell/platform/android/AndroidManifest.xml index 97c77ee7ef57f..5fbd0d8553c2b 100644 --- a/shell/platform/android/AndroidManifest.xml +++ b/shell/platform/android/AndroidManifest.xml @@ -24,12 +24,4 @@ - - - - - - - - diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 1f1068db7e703..06e4c9de02985 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -265,7 +265,6 @@ android_java_sources = [ "io/flutter/embedding/engine/systemchannels/NavigationChannel.java", "io/flutter/embedding/engine/systemchannels/PlatformChannel.java", "io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java", - "io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java", "io/flutter/embedding/engine/systemchannels/RestorationChannel.java", "io/flutter/embedding/engine/systemchannels/SettingsChannel.java", "io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java", @@ -313,7 +312,6 @@ android_java_sources = [ "io/flutter/plugin/platform/SingleViewPresentation.java", "io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTarget.java", "io/flutter/plugin/platform/VirtualDisplayController.java", - "io/flutter/plugin/text/ProcessTextPlugin.java", "io/flutter/util/HandlerCompat.java", "io/flutter/util/PathUtils.java", "io/flutter/util/Preconditions.java", diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index cb00ec42b363c..2bfa379f39c1e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -31,7 +31,6 @@ import io.flutter.embedding.engine.systemchannels.MouseCursorChannel; import io.flutter.embedding.engine.systemchannels.NavigationChannel; import io.flutter.embedding.engine.systemchannels.PlatformChannel; -import io.flutter.embedding.engine.systemchannels.ProcessTextChannel; import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.embedding.engine.systemchannels.SettingsChannel; import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; @@ -39,7 +38,6 @@ import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.plugin.localization.LocalizationPlugin; import io.flutter.plugin.platform.PlatformViewsController; -import io.flutter.plugin.text.ProcessTextPlugin; import io.flutter.util.ViewUtils; import java.util.HashSet; import java.util.List; @@ -97,7 +95,6 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater { @NonNull private final NavigationChannel navigationChannel; @NonNull private final RestorationChannel restorationChannel; @NonNull private final PlatformChannel platformChannel; - @NonNull private final ProcessTextChannel processTextChannel; @NonNull private final SettingsChannel settingsChannel; @NonNull private final SpellCheckChannel spellCheckChannel; @NonNull private final SystemChannel systemChannel; @@ -332,7 +329,6 @@ public FlutterEngine( mouseCursorChannel = new MouseCursorChannel(dartExecutor); navigationChannel = new NavigationChannel(dartExecutor); platformChannel = new PlatformChannel(dartExecutor); - processTextChannel = new ProcessTextChannel(dartExecutor, context.getPackageManager()); restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); settingsChannel = new SettingsChannel(dartExecutor); spellCheckChannel = new SpellCheckChannel(dartExecutor); @@ -388,9 +384,6 @@ public FlutterEngine( } ViewUtils.calculateMaximumDisplayMetrics(context, this); - - ProcessTextPlugin processTextPlugin = new ProcessTextPlugin(this.getProcessTextChannel()); - this.pluginRegistry.add(processTextPlugin); } private void attachToJni() { @@ -552,12 +545,6 @@ public PlatformChannel getPlatformChannel() { return platformChannel; } - /** System channel that sends text processing requests from Flutter to Android. */ - @NonNull - public ProcessTextChannel getProcessTextChannel() { - return processTextChannel; - } - /** * System channel to exchange restoration data between framework and engine. * diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java deleted file mode 100644 index f1d12bc681111..0000000000000 --- a/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.embedding.engine.systemchannels; - -import android.content.pm.PackageManager; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import io.flutter.embedding.engine.dart.DartExecutor; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.StandardMethodCodec; -import java.util.ArrayList; -import java.util.Map; - -/** - * {@link ProcessTextChannel} is a platform channel that is used by the framework to initiate text - * processing feature in the embedding and for the embedding to send back the results. - * - *

When the framework needs to query the list of text processing actions (for instance to expose - * them in the selected text context menu), it will send to the embedding the message {@code - * ProcessText.queryTextActions}. In response, the {@link io.flutter.plugin.text.ProcessTextPlugin} - * will return a map of all activities that can process text. The map keys are generated IDs and the - * values are the activities labels. On the first request, the {@link - * io.flutter.plugin.text.ProcessTextPlugin} will make a call to Android's package manager to query - * all activities that can be performed for the {@code Intent.ACTION_PROCESS_TEXT} intent. - * - *

When a text processing action has to be executed, the framework will send to the embedding the - * message {@code ProcessText.processTextAction} with the {@code int id} of the choosen text action - * and the {@code String} of text to process as arguments. In response, the {@link - * io.flutter.plugin.text.ProcessTextPlugin} will make a call to the Android application activity to - * start the activity exposing the text action. The {@link io.flutter.plugin.text.ProcessTextPlugin} - * will return the processed text if there is one, or null if the activity did not return a - * transformed text. - * - *

{@link io.flutter.plugin.text.ProcessTextPlugin} implements {@link ProcessTextMethodHandler} - * that parses incoming messages from Flutter. - */ -public class ProcessTextChannel { - private static final String TAG = "ProcessTextChannel"; - private static final String CHANNEL_NAME = "flutter/processtext"; - private static final String METHOD_QUERY_TEXT_ACTIONS = "ProcessText.queryTextActions"; - private static final String METHOD_PROCESS_TEXT_ACTION = "ProcessText.processTextAction"; - - public final MethodChannel channel; - public final PackageManager packageManager; - private ProcessTextMethodHandler processTextMethodHandler; - - @NonNull - public final MethodChannel.MethodCallHandler parsingMethodHandler = - new MethodChannel.MethodCallHandler() { - @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { - if (processTextMethodHandler == null) { - return; - } - String method = call.method; - Object args = call.arguments; - switch (method) { - case METHOD_QUERY_TEXT_ACTIONS: - try { - Map actions = processTextMethodHandler.queryTextActions(); - result.success(actions); - } catch (IllegalStateException exception) { - result.error("error", exception.getMessage(), null); - } - break; - case METHOD_PROCESS_TEXT_ACTION: - try { - final ArrayList argumentList = (ArrayList) args; - String id = (String) (argumentList.get(0)); - String text = (String) (argumentList.get(1)); - boolean readOnly = (boolean) (argumentList.get(2)); - processTextMethodHandler.processTextAction(id, text, readOnly, result); - } catch (IllegalStateException exception) { - result.error("error", exception.getMessage(), null); - } - break; - default: - result.notImplemented(); - break; - } - } - }; - - public ProcessTextChannel( - @NonNull DartExecutor dartExecutor, @NonNull PackageManager packageManager) { - this.packageManager = packageManager; - channel = new MethodChannel(dartExecutor, CHANNEL_NAME, StandardMethodCodec.INSTANCE); - channel.setMethodCallHandler(parsingMethodHandler); - } - - /** - * Sets the {@link ProcessTextMethodHandler} which receives all requests to the text processing - * feature sent through this channel. - */ - public void setMethodHandler(@Nullable ProcessTextMethodHandler processTextMethodHandler) { - this.processTextMethodHandler = processTextMethodHandler; - } - - public interface ProcessTextMethodHandler { - /** Requests the map of text actions. Each text action has a unique id and a localized label. */ - Map queryTextActions(); - - /** - * Requests to run a text action on a given input text. - * - * @param id The ID of the text action returned by {@code ProcessText.queryTextActions}. - * @param input The text to be processed. - * @param readOnly Indicates to the activity if the processed text will be used as read-only. - * see - * https://developer.android.com/reference/android/content/Intent#EXTRA_PROCESS_TEXT_READONLY - * @param result The method channel result instance used to reply. - */ - void processTextAction( - @NonNull String id, - @NonNull String input, - @NonNull boolean readOnly, - @NonNull MethodChannel.Result result); - } -} diff --git a/shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java b/shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java deleted file mode 100644 index d04716e4cb410..0000000000000 --- a/shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugin.text; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Build; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.embedding.engine.systemchannels.ProcessTextChannel; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.PluginRegistry.ActivityResultListener; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class ProcessTextPlugin - implements FlutterPlugin, - ActivityAware, - ActivityResultListener, - ProcessTextChannel.ProcessTextMethodHandler { - private static final String TAG = "ProcessTextPlugin"; - - @NonNull private final ProcessTextChannel processTextChannel; - @NonNull private final PackageManager packageManager; - @Nullable private ActivityPluginBinding activityBinding; - private Map resolveInfosById; - - @NonNull - private Map requestsByCode = - new HashMap(); - - public ProcessTextPlugin(@NonNull ProcessTextChannel processTextChannel) { - this.processTextChannel = processTextChannel; - this.packageManager = processTextChannel.packageManager; - - processTextChannel.setMethodHandler(this); - } - - @Override - public Map queryTextActions() { - if (resolveInfosById == null) { - cacheResolveInfos(); - } - Map result = new HashMap(); - for (String id : resolveInfosById.keySet()) { - final ResolveInfo info = resolveInfosById.get(id); - result.put(id, info.loadLabel(packageManager).toString()); - } - return result; - } - - @Override - public void processTextAction( - @NonNull String id, - @NonNull String text, - @NonNull boolean readOnly, - @NonNull MethodChannel.Result result) { - if (activityBinding == null) { - result.error("error", "Plugin not bound to an Activity", null); - return; - } - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - result.error("error", "Android version not supported", null); - return; - } - - if (resolveInfosById == null) { - result.error("error", "Can not process text actions before calling queryTextActions", null); - return; - } - - final ResolveInfo info = resolveInfosById.get(id); - if (info == null) { - result.error("error", "Text processing activity not found", null); - return; - } - - Integer requestCode = result.hashCode(); - requestsByCode.put(requestCode, result); - - Intent intent = new Intent(); - intent.setClassName(info.activityInfo.packageName, info.activityInfo.name); - intent.setAction(Intent.ACTION_PROCESS_TEXT); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_PROCESS_TEXT, text); - intent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, readOnly); - - // Start the text processing activity. When the activity completes, the onActivityResult - // callback - // is called. - activityBinding.getActivity().startActivityForResult(intent, requestCode); - } - - private void cacheResolveInfos() { - resolveInfosById = new HashMap(); - - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - return; - } - - Intent intent = new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain"); - - List infos; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - infos = packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(0)); - } else { - infos = packageManager.queryIntentActivities(intent, 0); - } - - for (ResolveInfo info : infos) { - final String id = info.activityInfo.name; - final String label = info.loadLabel(packageManager).toString(); - resolveInfosById.put(id, info); - } - } - - /** - * Executed when a text processing activity terminates. - * - *

When an activity returns a value, the request is completed successfully and returns the - * processed text. - * - *

When an activity does not return a value. the request is completed successfully and returns - * null. - */ - @TargetApi(Build.VERSION_CODES.M) - @RequiresApi(Build.VERSION_CODES.M) - public boolean onActivityResult(int requestCode, int resultCode, @Nullable Intent intent) { - String result = null; - if (resultCode == Activity.RESULT_OK) { - result = intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT); - } - requestsByCode.remove(requestCode).success(result); - return true; - } - - /** - * Unregisters this {@code ProcessTextPlugin} as the {@code - * ProcessTextChannel.ProcessTextMethodHandler}, for the {@link - * io.flutter.embedding.engine.systemchannels.ProcessTextChannel}. - * - *

Do not invoke any methods on a {@code ProcessTextPlugin} after invoking this method. - */ - public void destroy() { - processTextChannel.setMethodHandler(null); - } - - // FlutterPlugin interface implementation. - - public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { - // Nothing to do because this plugin is instantiated by the engine. - } - - public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { - // Nothing to do because this plugin is instantiated by the engine. - } - - // ActivityAware interface implementation. - // - // Store the binding and manage the activity result listener. - - public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { - this.activityBinding = binding; - this.activityBinding.addActivityResultListener(this); - }; - - public void onDetachedFromActivityForConfigChanges() { - this.activityBinding.removeActivityResultListener(this); - this.activityBinding = null; - } - - public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { - this.activityBinding = binding; - this.activityBinding.addActivityResultListener(this); - } - - public void onDetachedFromActivity() { - this.activityBinding.removeActivityResultListener(this); - this.activityBinding = null; - } -} diff --git a/shell/platform/android/test/io/flutter/plugin/text/ProcessTextPluginTest.java b/shell/platform/android/test/io/flutter/plugin/text/ProcessTextPluginTest.java deleted file mode 100644 index 47448fd9f9f65..0000000000000 --- a/shell/platform/android/test/io/flutter/plugin/text/ProcessTextPluginTest.java +++ /dev/null @@ -1,225 +0,0 @@ -package io.flutter.plugin.text; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.annotation.TargetApi; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageItemInfo; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.os.Build; -import androidx.annotation.RequiresApi; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import io.flutter.embedding.engine.dart.DartExecutor; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.embedding.engine.systemchannels.ProcessTextChannel; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.StandardMethodCodec; -import java.lang.reflect.Field; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; - -@RunWith(AndroidJUnit4.class) -@TargetApi(Build.VERSION_CODES.N) -@RequiresApi(Build.VERSION_CODES.N) -public class ProcessTextPluginTest { - - private static void sendToBinaryMessageHandler( - BinaryMessenger.BinaryMessageHandler binaryMessageHandler, String method, Object args) { - MethodCall methodCall = new MethodCall(method, args); - ByteBuffer encodedMethodCall = StandardMethodCodec.INSTANCE.encodeMethodCall(methodCall); - binaryMessageHandler.onMessage( - (ByteBuffer) encodedMethodCall.flip(), mock(BinaryMessenger.BinaryReply.class)); - } - - @SuppressWarnings("deprecation") - // setMessageHandler is deprecated. - @Test - public void respondsToProcessTextChannelMessage() { - ArgumentCaptor binaryMessageHandlerCaptor = - ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class); - DartExecutor mockBinaryMessenger = mock(DartExecutor.class); - ProcessTextChannel.ProcessTextMethodHandler mockHandler = - mock(ProcessTextChannel.ProcessTextMethodHandler.class); - PackageManager mockPackageManager = mock(PackageManager.class); - ProcessTextChannel processTextChannel = - new ProcessTextChannel(mockBinaryMessenger, mockPackageManager); - - processTextChannel.setMethodHandler(mockHandler); - - verify(mockBinaryMessenger, times(1)) - .setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture()); - - BinaryMessenger.BinaryMessageHandler binaryMessageHandler = - binaryMessageHandlerCaptor.getValue(); - - sendToBinaryMessageHandler(binaryMessageHandler, "ProcessText.queryTextActions", null); - - verify(mockHandler).queryTextActions(); - } - - @SuppressWarnings("deprecation") - // setMessageHandler is deprecated. - @Test - public void performQueryTextActions() { - DartExecutor mockBinaryMessenger = mock(DartExecutor.class); - PackageManager mockPackageManager = mock(PackageManager.class); - ProcessTextChannel processTextChannel = - new ProcessTextChannel(mockBinaryMessenger, mockPackageManager); - - // Set up mocked result for PackageManager.queryIntentActivities. - ResolveInfo action1 = createFakeResolveInfo("Action1", mockPackageManager); - ResolveInfo action2 = createFakeResolveInfo("Action2", mockPackageManager); - List infos = new ArrayList(Arrays.asList(action1, action2)); - Intent intent = new Intent().setAction(Intent.ACTION_PROCESS_TEXT).setType("text/plain"); - when(mockPackageManager.queryIntentActivities( - any(Intent.class), any(PackageManager.ResolveInfoFlags.class))) - .thenReturn(infos); - - // ProcessTextPlugin should retrieve the mocked text actions. - ProcessTextPlugin processTextPlugin = new ProcessTextPlugin(processTextChannel); - Map textActions = processTextPlugin.queryTextActions(); - final String action1Id = "mockActivityName.Action1"; - final String action2Id = "mockActivityName.Action2"; - assertEquals(textActions, Map.of(action1Id, "Action1", action2Id, "Action2")); - } - - @SuppressWarnings("deprecation") - // setMessageHandler is deprecated. - @Test - public void performProcessTextActionWithNoReturnedValue() { - DartExecutor mockBinaryMessenger = mock(DartExecutor.class); - PackageManager mockPackageManager = mock(PackageManager.class); - ProcessTextChannel processTextChannel = - new ProcessTextChannel(mockBinaryMessenger, mockPackageManager); - - // Set up mocked result for PackageManager.queryIntentActivities. - ResolveInfo action1 = createFakeResolveInfo("Action1", mockPackageManager); - ResolveInfo action2 = createFakeResolveInfo("Action2", mockPackageManager); - List infos = new ArrayList(Arrays.asList(action1, action2)); - when(mockPackageManager.queryIntentActivities( - any(Intent.class), any(PackageManager.ResolveInfoFlags.class))) - .thenReturn(infos); - - // ProcessTextPlugin should retrieve the mocked text actions. - ProcessTextPlugin processTextPlugin = new ProcessTextPlugin(processTextChannel); - Map textActions = processTextPlugin.queryTextActions(); - final String action1Id = "mockActivityName.Action1"; - final String action2Id = "mockActivityName.Action2"; - assertEquals(textActions, Map.of(action1Id, "Action1", action2Id, "Action2")); - - // Set up the activity binding. - ActivityPluginBinding mockActivityPluginBinding = mock(ActivityPluginBinding.class); - Activity mockActivity = mock(Activity.class); - when(mockActivityPluginBinding.getActivity()).thenReturn(mockActivity); - processTextPlugin.onAttachedToActivity(mockActivityPluginBinding); - - // Execute th first action. - String textToBeProcessed = "Flutter!"; - MethodChannel.Result result = mock(MethodChannel.Result.class); - processTextPlugin.processTextAction(action1Id, textToBeProcessed, false, result); - - // Activity.startActivityForResult should have been called. - ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(mockActivity, times(1)).startActivityForResult(intentCaptor.capture(), anyInt()); - Intent intent = intentCaptor.getValue(); - assertEquals(intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT), textToBeProcessed); - - // Simulate an Android activity answer which does not return a value. - Intent resultIntent = new Intent(); - processTextPlugin.onActivityResult(result.hashCode(), Activity.RESULT_OK, resultIntent); - - // Success with no returned value is expected. - verify(result).success(null); - } - - @SuppressWarnings("deprecation") - // setMessageHandler is deprecated. - @Test - public void performProcessTextActionWithReturnedValue() { - DartExecutor mockBinaryMessenger = mock(DartExecutor.class); - PackageManager mockPackageManager = mock(PackageManager.class); - ProcessTextChannel processTextChannel = - new ProcessTextChannel(mockBinaryMessenger, mockPackageManager); - - // Set up mocked result for PackageManager.queryIntentActivities. - ResolveInfo action1 = createFakeResolveInfo("Action1", mockPackageManager); - ResolveInfo action2 = createFakeResolveInfo("Action2", mockPackageManager); - List infos = new ArrayList(Arrays.asList(action1, action2)); - when(mockPackageManager.queryIntentActivities( - any(Intent.class), any(PackageManager.ResolveInfoFlags.class))) - .thenReturn(infos); - - // ProcessTextPlugin should retrieve the mocked text actions. - ProcessTextPlugin processTextPlugin = new ProcessTextPlugin(processTextChannel); - Map textActions = processTextPlugin.queryTextActions(); - final String action1Id = "mockActivityName.Action1"; - final String action2Id = "mockActivityName.Action2"; - assertEquals(textActions, Map.of(action1Id, "Action1", action2Id, "Action2")); - - // Set up the activity binding. - ActivityPluginBinding mockActivityPluginBinding = mock(ActivityPluginBinding.class); - Activity mockActivity = mock(Activity.class); - when(mockActivityPluginBinding.getActivity()).thenReturn(mockActivity); - processTextPlugin.onAttachedToActivity(mockActivityPluginBinding); - - // Execute the first action. - String textToBeProcessed = "Flutter!"; - MethodChannel.Result result = mock(MethodChannel.Result.class); - processTextPlugin.processTextAction(action1Id, textToBeProcessed, false, result); - - // Activity.startActivityForResult should have been called. - ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); - verify(mockActivity, times(1)).startActivityForResult(intentCaptor.capture(), anyInt()); - Intent intent = intentCaptor.getValue(); - assertEquals(intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT), textToBeProcessed); - - // Simulate an Android activity answer which returns a transformed text. - String processedText = "Flutter!!!"; - Intent resultIntent = new Intent(); - resultIntent.putExtra(Intent.EXTRA_PROCESS_TEXT, processedText); - processTextPlugin.onActivityResult(result.hashCode(), Activity.RESULT_OK, resultIntent); - - // Success with the transformed text is expected. - verify(result).success(processedText); - } - - private ResolveInfo createFakeResolveInfo(String label, PackageManager mockPackageManager) { - ResolveInfo resolveInfo = mock(ResolveInfo.class); - ActivityInfo activityInfo = new ActivityInfo(); - when(resolveInfo.loadLabel(mockPackageManager)).thenReturn(label); - - // Use Java reflection to set required member variables. - try { - Field activityField = ResolveInfo.class.getDeclaredField("activityInfo"); - activityField.setAccessible(true); - activityField.set(resolveInfo, activityInfo); - Field packageNameField = PackageItemInfo.class.getDeclaredField("packageName"); - packageNameField.setAccessible(true); - packageNameField.set(activityInfo, "mockActivityPackageName"); - Field nameField = PackageItemInfo.class.getDeclaredField("name"); - nameField.setAccessible(true); - nameField.set(activityInfo, "mockActivityName." + label); - } catch (Exception ex) { - // Test will failed if reflection APIs throw. - } - - return resolveInfo; - } -}