Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d93fe23

Browse files
authored
[Android] Add support for text processing actions (#44579)
## Description This Android related PR adds a channel buffer and a plugin to interact with Android 'process text' feature. It makes it possible to query text processing actions and to run those actions (for instance 'calling' Google translate). Text actions that outputs a processed text are supported. The implementation is based on the great sample provided by @gualse , see flutter/flutter#107603 (comment). In order to return a non empty list of text actions, the implementation will require adding a section to the Android manifest file (see flutter/flutter#107603 (comment)). Adding this section automatically to new or existing Flutter apps is not part of this PR but will be tackled in a future PR. ## Related Issue Android engine side for flutter/flutter#107603 ## Tests Adds 3 tests.
1 parent d28fdb0 commit d93fe23

File tree

7 files changed

+566
-0
lines changed

7 files changed

+566
-0
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3091,6 +3091,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/syst
30913091
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java + ../../../flutter/LICENSE
30923092
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java + ../../../flutter/LICENSE
30933093
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java + ../../../flutter/LICENSE
3094+
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java + ../../../flutter/LICENSE
30943095
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java + ../../../flutter/LICENSE
30953096
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java + ../../../flutter/LICENSE
30963097
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java + ../../../flutter/LICENSE
@@ -3133,6 +3134,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platf
31333134
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/PlatformViewsController.java + ../../../flutter/LICENSE
31343135
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java + ../../../flutter/LICENSE
31353136
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java + ../../../flutter/LICENSE
3137+
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java + ../../../flutter/LICENSE
31363138
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/util/HandlerCompat.java + ../../../flutter/LICENSE
31373139
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java + ../../../flutter/LICENSE
31383140
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java + ../../../flutter/LICENSE
@@ -5861,6 +5863,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system
58615863
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/NavigationChannel.java
58625864
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformChannel.java
58635865
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java
5866+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java
58645867
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java
58655868
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SettingsChannel.java
58665869
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java
@@ -5908,6 +5911,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/Platfor
59085911
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SingleViewPresentation.java
59095912
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTarget.java
59105913
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/platform/VirtualDisplayController.java
5914+
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/text/ProcessTextPlugin.java
59115915
FILE: ../../../flutter/shell/platform/android/io/flutter/util/HandlerCompat.java
59125916
FILE: ../../../flutter/shell/platform/android/io/flutter/util/PathUtils.java
59135917
FILE: ../../../flutter/shell/platform/android/io/flutter/util/Preconditions.java

shell/platform/android/AndroidManifest.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,12 @@
2424
</intent-filter>
2525
</activity>
2626
</application>
27+
28+
<!-- Required for io.flutter.plugin.text.ProcessTextPlugin to query activities that can process text. -->
29+
<queries>
30+
<intent>
31+
<action android:name="android.intent.action.PROCESS_TEXT" />
32+
<data android:mimeType="text/plain" />
33+
</intent>
34+
</queries>
2735
</manifest>

shell/platform/android/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ android_java_sources = [
265265
"io/flutter/embedding/engine/systemchannels/NavigationChannel.java",
266266
"io/flutter/embedding/engine/systemchannels/PlatformChannel.java",
267267
"io/flutter/embedding/engine/systemchannels/PlatformViewsChannel.java",
268+
"io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java",
268269
"io/flutter/embedding/engine/systemchannels/RestorationChannel.java",
269270
"io/flutter/embedding/engine/systemchannels/SettingsChannel.java",
270271
"io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java",
@@ -312,6 +313,7 @@ android_java_sources = [
312313
"io/flutter/plugin/platform/SingleViewPresentation.java",
313314
"io/flutter/plugin/platform/SurfaceTexturePlatformViewRenderTarget.java",
314315
"io/flutter/plugin/platform/VirtualDisplayController.java",
316+
"io/flutter/plugin/text/ProcessTextPlugin.java",
315317
"io/flutter/util/HandlerCompat.java",
316318
"io/flutter/util/PathUtils.java",
317319
"io/flutter/util/Preconditions.java",

shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
3232
import io.flutter.embedding.engine.systemchannels.NavigationChannel;
3333
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
34+
import io.flutter.embedding.engine.systemchannels.ProcessTextChannel;
3435
import io.flutter.embedding.engine.systemchannels.RestorationChannel;
3536
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
3637
import io.flutter.embedding.engine.systemchannels.SpellCheckChannel;
3738
import io.flutter.embedding.engine.systemchannels.SystemChannel;
3839
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
3940
import io.flutter.plugin.localization.LocalizationPlugin;
4041
import io.flutter.plugin.platform.PlatformViewsController;
42+
import io.flutter.plugin.text.ProcessTextPlugin;
4143
import io.flutter.util.ViewUtils;
4244
import java.util.HashSet;
4345
import java.util.List;
@@ -95,6 +97,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
9597
@NonNull private final NavigationChannel navigationChannel;
9698
@NonNull private final RestorationChannel restorationChannel;
9799
@NonNull private final PlatformChannel platformChannel;
100+
@NonNull private final ProcessTextChannel processTextChannel;
98101
@NonNull private final SettingsChannel settingsChannel;
99102
@NonNull private final SpellCheckChannel spellCheckChannel;
100103
@NonNull private final SystemChannel systemChannel;
@@ -329,6 +332,7 @@ public FlutterEngine(
329332
mouseCursorChannel = new MouseCursorChannel(dartExecutor);
330333
navigationChannel = new NavigationChannel(dartExecutor);
331334
platformChannel = new PlatformChannel(dartExecutor);
335+
processTextChannel = new ProcessTextChannel(dartExecutor, context.getPackageManager());
332336
restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData);
333337
settingsChannel = new SettingsChannel(dartExecutor);
334338
spellCheckChannel = new SpellCheckChannel(dartExecutor);
@@ -384,6 +388,9 @@ public FlutterEngine(
384388
}
385389

386390
ViewUtils.calculateMaximumDisplayMetrics(context, this);
391+
392+
ProcessTextPlugin processTextPlugin = new ProcessTextPlugin(this.getProcessTextChannel());
393+
this.pluginRegistry.add(processTextPlugin);
387394
}
388395

389396
private void attachToJni() {
@@ -545,6 +552,12 @@ public PlatformChannel getPlatformChannel() {
545552
return platformChannel;
546553
}
547554

555+
/** System channel that sends text processing requests from Flutter to Android. */
556+
@NonNull
557+
public ProcessTextChannel getProcessTextChannel() {
558+
return processTextChannel;
559+
}
560+
548561
/**
549562
* System channel to exchange restoration data between framework and engine.
550563
*
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.embedding.engine.systemchannels;
6+
7+
import android.content.pm.PackageManager;
8+
import androidx.annotation.NonNull;
9+
import androidx.annotation.Nullable;
10+
import io.flutter.embedding.engine.dart.DartExecutor;
11+
import io.flutter.plugin.common.MethodCall;
12+
import io.flutter.plugin.common.MethodChannel;
13+
import io.flutter.plugin.common.StandardMethodCodec;
14+
import java.util.ArrayList;
15+
import java.util.Map;
16+
17+
/**
18+
* {@link ProcessTextChannel} is a platform channel that is used by the framework to initiate text
19+
* processing feature in the embedding and for the embedding to send back the results.
20+
*
21+
* <p>When the framework needs to query the list of text processing actions (for instance to expose
22+
* them in the selected text context menu), it will send to the embedding the message {@code
23+
* ProcessText.queryTextActions}. In response, the {@link io.flutter.plugin.text.ProcessTextPlugin}
24+
* will return a map of all activities that can process text. The map keys are generated IDs and the
25+
* values are the activities labels. On the first request, the {@link
26+
* io.flutter.plugin.text.ProcessTextPlugin} will make a call to Android's package manager to query
27+
* all activities that can be performed for the {@code Intent.ACTION_PROCESS_TEXT} intent.
28+
*
29+
* <p>When a text processing action has to be executed, the framework will send to the embedding the
30+
* message {@code ProcessText.processTextAction} with the {@code int id} of the choosen text action
31+
* and the {@code String} of text to process as arguments. In response, the {@link
32+
* io.flutter.plugin.text.ProcessTextPlugin} will make a call to the Android application activity to
33+
* start the activity exposing the text action. The {@link io.flutter.plugin.text.ProcessTextPlugin}
34+
* will return the processed text if there is one, or null if the activity did not return a
35+
* transformed text.
36+
*
37+
* <p>{@link io.flutter.plugin.text.ProcessTextPlugin} implements {@link ProcessTextMethodHandler}
38+
* that parses incoming messages from Flutter.
39+
*/
40+
public class ProcessTextChannel {
41+
private static final String TAG = "ProcessTextChannel";
42+
private static final String CHANNEL_NAME = "flutter/processtext";
43+
private static final String METHOD_QUERY_TEXT_ACTIONS = "ProcessText.queryTextActions";
44+
private static final String METHOD_PROCESS_TEXT_ACTION = "ProcessText.processTextAction";
45+
46+
public final MethodChannel channel;
47+
public final PackageManager packageManager;
48+
private ProcessTextMethodHandler processTextMethodHandler;
49+
50+
@NonNull
51+
public final MethodChannel.MethodCallHandler parsingMethodHandler =
52+
new MethodChannel.MethodCallHandler() {
53+
@Override
54+
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
55+
if (processTextMethodHandler == null) {
56+
return;
57+
}
58+
String method = call.method;
59+
Object args = call.arguments;
60+
switch (method) {
61+
case METHOD_QUERY_TEXT_ACTIONS:
62+
try {
63+
Map<String, String> actions = processTextMethodHandler.queryTextActions();
64+
result.success(actions);
65+
} catch (IllegalStateException exception) {
66+
result.error("error", exception.getMessage(), null);
67+
}
68+
break;
69+
case METHOD_PROCESS_TEXT_ACTION:
70+
try {
71+
final ArrayList<Object> argumentList = (ArrayList<Object>) args;
72+
String id = (String) (argumentList.get(0));
73+
String text = (String) (argumentList.get(1));
74+
boolean readOnly = (boolean) (argumentList.get(2));
75+
processTextMethodHandler.processTextAction(id, text, readOnly, result);
76+
} catch (IllegalStateException exception) {
77+
result.error("error", exception.getMessage(), null);
78+
}
79+
break;
80+
default:
81+
result.notImplemented();
82+
break;
83+
}
84+
}
85+
};
86+
87+
public ProcessTextChannel(
88+
@NonNull DartExecutor dartExecutor, @NonNull PackageManager packageManager) {
89+
this.packageManager = packageManager;
90+
channel = new MethodChannel(dartExecutor, CHANNEL_NAME, StandardMethodCodec.INSTANCE);
91+
channel.setMethodCallHandler(parsingMethodHandler);
92+
}
93+
94+
/**
95+
* Sets the {@link ProcessTextMethodHandler} which receives all requests to the text processing
96+
* feature sent through this channel.
97+
*/
98+
public void setMethodHandler(@Nullable ProcessTextMethodHandler processTextMethodHandler) {
99+
this.processTextMethodHandler = processTextMethodHandler;
100+
}
101+
102+
public interface ProcessTextMethodHandler {
103+
/** Requests the map of text actions. Each text action has a unique id and a localized label. */
104+
Map<String, String> queryTextActions();
105+
106+
/**
107+
* Requests to run a text action on a given input text.
108+
*
109+
* @param id The ID of the text action returned by {@code ProcessText.queryTextActions}.
110+
* @param input The text to be processed.
111+
* @param readOnly Indicates to the activity if the processed text will be used as read-only.
112+
* see
113+
* https://developer.android.com/reference/android/content/Intent#EXTRA_PROCESS_TEXT_READONLY
114+
* @param result The method channel result instance used to reply.
115+
*/
116+
void processTextAction(
117+
@NonNull String id,
118+
@NonNull String input,
119+
@NonNull boolean readOnly,
120+
@NonNull MethodChannel.Result result);
121+
}
122+
}

0 commit comments

Comments
 (0)