mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Scribe (Android stylus handwriting text input) (flutter/engine#52943)
The engine API for Android's Scribe stylus handwriting input. Just bare bones handwriting itself, does not support special gestures, which will come in subsequent PR(s).
This commit is contained in:
parent
b21f22cc50
commit
cd1cd27685
@ -44294,6 +44294,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/syst
|
||||
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/ScribeChannel.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
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/common/ActivityLifecycleListener.java + ../../../flutter/LICENSE
|
||||
@ -44317,6 +44318,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/Flutte
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ImeSyncDeferringInsetsCallback.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ScribePlugin.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java + ../../../flutter/LICENSE
|
||||
@ -47176,6 +47178,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/system
|
||||
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/ScribeChannel.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
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java
|
||||
@ -47202,6 +47205,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/FlutterT
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ImeSyncDeferringInsetsCallback.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ScribePlugin.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java
|
||||
FILE: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java
|
||||
|
||||
@ -294,6 +294,7 @@ android_java_sources = [
|
||||
"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/ScribeChannel.java",
|
||||
"io/flutter/embedding/engine/systemchannels/SettingsChannel.java",
|
||||
"io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java",
|
||||
"io/flutter/embedding/engine/systemchannels/SystemChannel.java",
|
||||
@ -320,6 +321,7 @@ android_java_sources = [
|
||||
"io/flutter/plugin/editing/ImeSyncDeferringInsetsCallback.java",
|
||||
"io/flutter/plugin/editing/InputConnectionAdaptor.java",
|
||||
"io/flutter/plugin/editing/ListenableEditingState.java",
|
||||
"io/flutter/plugin/editing/ScribePlugin.java",
|
||||
"io/flutter/plugin/editing/SpellCheckPlugin.java",
|
||||
"io/flutter/plugin/editing/TextEditingDelta.java",
|
||||
"io/flutter/plugin/editing/TextInputPlugin.java",
|
||||
|
||||
@ -63,6 +63,7 @@ import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
|
||||
import io.flutter.embedding.engine.renderer.RenderSurface;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.editing.ScribePlugin;
|
||||
import io.flutter.plugin.editing.SpellCheckPlugin;
|
||||
import io.flutter.plugin.editing.TextInputPlugin;
|
||||
import io.flutter.plugin.localization.LocalizationPlugin;
|
||||
@ -133,6 +134,7 @@ public class FlutterView extends FrameLayout
|
||||
@Nullable private MouseCursorPlugin mouseCursorPlugin;
|
||||
@Nullable private TextInputPlugin textInputPlugin;
|
||||
@Nullable private SpellCheckPlugin spellCheckPlugin;
|
||||
@Nullable private ScribePlugin scribePlugin;
|
||||
@Nullable private LocalizationPlugin localizationPlugin;
|
||||
@Nullable private KeyboardManager keyboardManager;
|
||||
@Nullable private AndroidTouchProcessor androidTouchProcessor;
|
||||
@ -1120,10 +1122,12 @@ public class FlutterView extends FrameLayout
|
||||
if (Build.VERSION.SDK_INT >= API_LEVELS.API_24) {
|
||||
mouseCursorPlugin = new MouseCursorPlugin(this, this.flutterEngine.getMouseCursorChannel());
|
||||
}
|
||||
|
||||
textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
this,
|
||||
this.flutterEngine.getTextInputChannel(),
|
||||
this.flutterEngine.getScribeChannel(),
|
||||
this.flutterEngine.getPlatformViewsController());
|
||||
|
||||
try {
|
||||
@ -1136,6 +1140,10 @@ public class FlutterView extends FrameLayout
|
||||
Log.e(TAG, "TextServicesManager not supported by device, spell check disabled.");
|
||||
}
|
||||
|
||||
scribePlugin =
|
||||
new ScribePlugin(
|
||||
this, textInputPlugin.getInputMethodManager(), this.flutterEngine.getScribeChannel());
|
||||
|
||||
localizationPlugin = this.flutterEngine.getLocalizationPlugin();
|
||||
|
||||
keyboardManager = new KeyboardManager(this);
|
||||
|
||||
@ -34,6 +34,7 @@ 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.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SpellCheckChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SystemChannel;
|
||||
@ -100,6 +101,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
@NonNull private final RestorationChannel restorationChannel;
|
||||
@NonNull private final PlatformChannel platformChannel;
|
||||
@NonNull private final ProcessTextChannel processTextChannel;
|
||||
@NonNull private final ScribeChannel scribeChannel;
|
||||
@NonNull private final SettingsChannel settingsChannel;
|
||||
@NonNull private final SpellCheckChannel spellCheckChannel;
|
||||
@NonNull private final SystemChannel systemChannel;
|
||||
@ -337,6 +339,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
platformChannel = new PlatformChannel(dartExecutor);
|
||||
processTextChannel = new ProcessTextChannel(dartExecutor, context.getPackageManager());
|
||||
restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData);
|
||||
scribeChannel = new ScribeChannel(dartExecutor);
|
||||
settingsChannel = new SettingsChannel(dartExecutor);
|
||||
spellCheckChannel = new SpellCheckChannel(dartExecutor);
|
||||
systemChannel = new SystemChannel(dartExecutor);
|
||||
@ -610,6 +613,12 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
return textInputChannel;
|
||||
}
|
||||
|
||||
/** System channel that sends and receives Scribe requests and results. */
|
||||
@NonNull
|
||||
public ScribeChannel getScribeChannel() {
|
||||
return scribeChannel;
|
||||
}
|
||||
|
||||
/** System channel that sends and receives spell check requests and results. */
|
||||
@NonNull
|
||||
public SpellCheckChannel getSpellCheckChannel() {
|
||||
|
||||
@ -0,0 +1,147 @@
|
||||
// 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 static io.flutter.Build.API_LEVELS;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import io.flutter.Log;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.plugin.common.JSONMethodCodec;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
/**
|
||||
* {@link ScribeChannel} is a platform channel that is used by the framework to facilitate the
|
||||
* Scribe handwriting text input feature.
|
||||
*/
|
||||
public class ScribeChannel {
|
||||
private static final String TAG = "ScribeChannel";
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String METHOD_IS_FEATURE_AVAILABLE = "Scribe.isFeatureAvailable";
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String METHOD_IS_STYLUS_HANDWRITING_AVAILABLE =
|
||||
"Scribe.isStylusHandwritingAvailable";
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String METHOD_START_STYLUS_HANDWRITING = "Scribe.startStylusHandwriting";
|
||||
|
||||
public final MethodChannel channel;
|
||||
private ScribeMethodHandler scribeMethodHandler;
|
||||
|
||||
@NonNull
|
||||
public final MethodChannel.MethodCallHandler parsingMethodHandler =
|
||||
new MethodChannel.MethodCallHandler() {
|
||||
@Override
|
||||
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
if (scribeMethodHandler == null) {
|
||||
Log.v(TAG, "No ScribeMethodHandler registered. Scribe call not handled.");
|
||||
return;
|
||||
}
|
||||
String method = call.method;
|
||||
Log.v(TAG, "Received '" + method + "' message.");
|
||||
switch (method) {
|
||||
case METHOD_IS_FEATURE_AVAILABLE:
|
||||
isFeatureAvailable(call, result);
|
||||
break;
|
||||
case METHOD_IS_STYLUS_HANDWRITING_AVAILABLE:
|
||||
isStylusHandwritingAvailable(call, result);
|
||||
break;
|
||||
case METHOD_START_STYLUS_HANDWRITING:
|
||||
startStylusHandwriting(call, result);
|
||||
break;
|
||||
default:
|
||||
result.notImplemented();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void isFeatureAvailable(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
try {
|
||||
final boolean isAvailable = scribeMethodHandler.isFeatureAvailable();
|
||||
result.success(isAvailable);
|
||||
} catch (IllegalStateException exception) {
|
||||
result.error("error", exception.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void isStylusHandwritingAvailable(
|
||||
@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
if (Build.VERSION.SDK_INT < API_LEVELS.API_34) {
|
||||
result.error("error", "Requires API level 34 or higher.", null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final boolean isAvailable = scribeMethodHandler.isStylusHandwritingAvailable();
|
||||
result.success(isAvailable);
|
||||
} catch (IllegalStateException exception) {
|
||||
result.error("error", exception.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
private void startStylusHandwriting(
|
||||
@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
|
||||
if (Build.VERSION.SDK_INT < API_LEVELS.API_33) {
|
||||
result.error("error", "Requires API level 33 or higher.", null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
scribeMethodHandler.startStylusHandwriting();
|
||||
result.success(null);
|
||||
} catch (IllegalStateException exception) {
|
||||
result.error("error", exception.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
public ScribeChannel(@NonNull DartExecutor dartExecutor) {
|
||||
channel = new MethodChannel(dartExecutor, "flutter/scribe", JSONMethodCodec.INSTANCE);
|
||||
channel.setMethodCallHandler(parsingMethodHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link ScribeMethodHandler} which receives all requests for scribe sent through this
|
||||
* channel.
|
||||
*/
|
||||
public void setScribeMethodHandler(@Nullable ScribeMethodHandler scribeMethodHandler) {
|
||||
this.scribeMethodHandler = scribeMethodHandler;
|
||||
}
|
||||
|
||||
public interface ScribeMethodHandler {
|
||||
/**
|
||||
* Responds to the {@code result} with success and a boolean indicating whether or not stylus
|
||||
* handwriting is available.
|
||||
*/
|
||||
boolean isFeatureAvailable();
|
||||
|
||||
/**
|
||||
* Responds to the {@code result} with success and a boolean indicating whether or not stylus
|
||||
* handwriting is available.
|
||||
*/
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@RequiresApi(API_LEVELS.API_34)
|
||||
boolean isStylusHandwritingAvailable();
|
||||
|
||||
/**
|
||||
* Requests to start Scribe stylus handwriting, which will respond to the {@code result} with
|
||||
* either success if handwriting input has started or error otherwise.
|
||||
*/
|
||||
@TargetApi(API_LEVELS.API_33)
|
||||
@RequiresApi(API_LEVELS.API_33)
|
||||
void startStylusHandwriting();
|
||||
}
|
||||
|
||||
// TODO(justinmc): Scribe stylus gestures should be supported here.
|
||||
// https://github.com/flutter/flutter/issues/156018
|
||||
}
|
||||
@ -33,6 +33,7 @@ import androidx.annotation.RequiresApi;
|
||||
import androidx.core.view.inputmethod.InputConnectionCompat;
|
||||
import io.flutter.Log;
|
||||
import io.flutter.embedding.engine.FlutterJNI;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
@ -51,6 +52,7 @@ public class InputConnectionAdaptor extends BaseInputConnection
|
||||
|
||||
private final View mFlutterView;
|
||||
private final int mClient;
|
||||
private final ScribeChannel scribeChannel;
|
||||
private final TextInputChannel textInputChannel;
|
||||
private final ListenableEditingState mEditable;
|
||||
private final EditorInfo mEditorInfo;
|
||||
@ -69,6 +71,7 @@ public class InputConnectionAdaptor extends BaseInputConnection
|
||||
View view,
|
||||
int client,
|
||||
TextInputChannel textInputChannel,
|
||||
ScribeChannel scribeChannel,
|
||||
KeyboardDelegate keyboardDelegate,
|
||||
ListenableEditingState editable,
|
||||
EditorInfo editorInfo,
|
||||
@ -77,6 +80,7 @@ public class InputConnectionAdaptor extends BaseInputConnection
|
||||
mFlutterView = view;
|
||||
mClient = client;
|
||||
this.textInputChannel = textInputChannel;
|
||||
this.scribeChannel = scribeChannel;
|
||||
mEditable = editable;
|
||||
mEditable.addEditingStateListener(this);
|
||||
mEditorInfo = editorInfo;
|
||||
@ -100,10 +104,19 @@ public class InputConnectionAdaptor extends BaseInputConnection
|
||||
View view,
|
||||
int client,
|
||||
TextInputChannel textInputChannel,
|
||||
ScribeChannel scribeChannel,
|
||||
KeyboardDelegate keyboardDelegate,
|
||||
ListenableEditingState editable,
|
||||
EditorInfo editorInfo) {
|
||||
this(view, client, textInputChannel, keyboardDelegate, editable, editorInfo, new FlutterJNI());
|
||||
this(
|
||||
view,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
keyboardDelegate,
|
||||
editable,
|
||||
editorInfo,
|
||||
new FlutterJNI());
|
||||
}
|
||||
|
||||
private ExtractedText getExtractedText(ExtractedTextRequest request) {
|
||||
@ -262,6 +275,10 @@ public class InputConnectionAdaptor extends BaseInputConnection
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO(justinmc): Scribe stylus gestures should be supported here via
|
||||
// performHandwritingGesture.
|
||||
// https://github.com/flutter/flutter/issues/156018
|
||||
|
||||
// Sanitizes the index to ensure the index is within the range of the
|
||||
// contents of editable.
|
||||
private static int clampIndexToEditable(int index, Editable editable) {
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
// 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.editing;
|
||||
|
||||
import static io.flutter.Build.API_LEVELS;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
|
||||
/**
|
||||
* {@link ScribePlugin} is the implementation of all functionality needed for handwriting stylus
|
||||
* text input.
|
||||
*
|
||||
* <p>The plugin handles requests for scribe sent by the {@link
|
||||
* io.flutter.embedding.engine.systemchannels.ScribeChannel}.
|
||||
*
|
||||
* <p>On API versions below 33, the plugin does nothing.
|
||||
*/
|
||||
public class ScribePlugin implements ScribeChannel.ScribeMethodHandler {
|
||||
|
||||
@NonNull private final ScribeChannel mScribeChannel;
|
||||
@NonNull private final InputMethodManager mInputMethodManager;
|
||||
@NonNull private View mView;
|
||||
|
||||
public ScribePlugin(
|
||||
@NonNull View view, @NonNull InputMethodManager imm, @NonNull ScribeChannel scribeChannel) {
|
||||
if (Build.VERSION.SDK_INT >= API_LEVELS.API_33) {
|
||||
view.setAutoHandwritingEnabled(false);
|
||||
}
|
||||
|
||||
mView = view;
|
||||
mInputMethodManager = imm;
|
||||
mScribeChannel = scribeChannel;
|
||||
|
||||
mScribeChannel.setScribeMethodHandler(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the View in which Scribe input is handled.
|
||||
*
|
||||
* <p>Only one View can be set at any given time.
|
||||
*/
|
||||
public void setView(@NonNull View view) {
|
||||
if (view == mView) {
|
||||
return;
|
||||
}
|
||||
mView = view;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this {@code ScribePlugin} as the {@code ScribeChannel.ScribeMethodHandler}, for the
|
||||
* {@link io.flutter.embedding.engine.systemchannels.ScribeChannel}.
|
||||
*
|
||||
* <p>Do not invoke any methods on a {@code ScribePlugin} after invoking this method.
|
||||
*/
|
||||
public void destroy() {
|
||||
mScribeChannel.setScribeMethodHandler(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the InputMethodManager supports Scribe stylus handwriting input.
|
||||
*
|
||||
* <p>Call this or isFeatureAvailable before calling startStylusHandwriting to make sure it's
|
||||
* available.
|
||||
*/
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@RequiresApi(API_LEVELS.API_34)
|
||||
@Override
|
||||
public boolean isStylusHandwritingAvailable() {
|
||||
return mInputMethodManager.isStylusHandwritingAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts stylus handwriting input.
|
||||
*
|
||||
* <p>Typically isStylusHandwritingAvailable should be called first to determine whether this is
|
||||
* supported by the IME.
|
||||
*/
|
||||
@TargetApi(API_LEVELS.API_33)
|
||||
@RequiresApi(API_LEVELS.API_33)
|
||||
@Override
|
||||
public void startStylusHandwriting() {
|
||||
mInputMethodManager.startStylusHandwriting(mView);
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenience method to check if Scribe is available.
|
||||
*
|
||||
* <p>Differs from isStylusHandwritingAvailable in that it can be called from any API level
|
||||
* without throwing an error.
|
||||
*
|
||||
* <p>Call this or isStylusHandwritingAvailable before calling startStylusHandwriting to make sure
|
||||
* it's available.
|
||||
*/
|
||||
@Override
|
||||
public boolean isFeatureAvailable() {
|
||||
return Build.VERSION.SDK_INT >= API_LEVELS.API_34 && isStylusHandwritingAvailable();
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.view.inputmethod.EditorInfoCompat;
|
||||
import io.flutter.Log;
|
||||
import io.flutter.embedding.android.KeyboardManager;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel.TextEditState;
|
||||
import io.flutter.plugin.platform.PlatformViewsController;
|
||||
@ -42,6 +43,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
@NonNull private final View mView;
|
||||
@NonNull private final InputMethodManager mImm;
|
||||
@NonNull private final AutofillManager afm;
|
||||
@NonNull private final ScribeChannel scribeChannel;
|
||||
@NonNull private final TextInputChannel textInputChannel;
|
||||
@NonNull private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
|
||||
@Nullable private TextInputChannel.Configuration configuration;
|
||||
@ -66,6 +68,7 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
public TextInputPlugin(
|
||||
@NonNull View view,
|
||||
@NonNull TextInputChannel textInputChannel,
|
||||
@NonNull ScribeChannel scribeChannel,
|
||||
@NonNull PlatformViewsController platformViewsController) {
|
||||
mView = view;
|
||||
// Create a default object.
|
||||
@ -153,6 +156,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
|
||||
textInputChannel.requestExistingInputState();
|
||||
|
||||
this.scribeChannel = scribeChannel;
|
||||
|
||||
this.platformViewsController = platformViewsController;
|
||||
this.platformViewsController.attachTextInputPlugin(this);
|
||||
}
|
||||
@ -341,9 +346,23 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
|
||||
EditorInfoCompat.setContentMimeTypes(outAttrs, imgTypeString);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= API_LEVELS.API_34) {
|
||||
EditorInfoCompat.setStylusHandwritingEnabled(outAttrs, true);
|
||||
}
|
||||
// TODO(justinmc): Scribe stylus gestures should be supported here via
|
||||
// outAttrs.setSupportedHandwritingGestures and
|
||||
// outAttrs.setSupportedHandwritingGesturePreviews.
|
||||
// https://github.com/flutter/flutter/issues/156018
|
||||
|
||||
InputConnectionAdaptor connection =
|
||||
new InputConnectionAdaptor(
|
||||
view, inputTarget.id, textInputChannel, keyboardManager, mEditable, outAttrs);
|
||||
view,
|
||||
inputTarget.id,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
keyboardManager,
|
||||
mEditable,
|
||||
outAttrs);
|
||||
outAttrs.initialSelStart = mEditable.getSelectionStart();
|
||||
outAttrs.initialSelEnd = mEditable.getSelectionEnd();
|
||||
|
||||
|
||||
@ -56,6 +56,7 @@ import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
|
||||
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.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SystemChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
@ -237,7 +238,11 @@ public class FlutterView extends SurfaceView
|
||||
PlatformViewsController platformViewsController =
|
||||
mNativeView.getPluginRegistry().getPlatformViewsController();
|
||||
mTextInputPlugin =
|
||||
new TextInputPlugin(this, new TextInputChannel(dartExecutor), platformViewsController);
|
||||
new TextInputPlugin(
|
||||
this,
|
||||
new TextInputChannel(dartExecutor),
|
||||
new ScribeChannel(dartExecutor),
|
||||
platformViewsController);
|
||||
mKeyboardManager = new KeyboardManager(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= API_LEVELS.API_24) {
|
||||
|
||||
@ -49,6 +49,7 @@ import io.flutter.embedding.engine.systemchannels.LifecycleChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.LocalizationChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.NavigationChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SystemChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
@ -1481,6 +1482,7 @@ public class FlutterActivityAndFragmentDelegateTest {
|
||||
when(engine.getSettingsChannel()).thenReturn(fakeSettingsChannel);
|
||||
when(engine.getSystemChannel()).thenReturn(mock(SystemChannel.class));
|
||||
when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class));
|
||||
when(engine.getScribeChannel()).thenReturn(mock(ScribeChannel.class));
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
@ -0,0 +1,202 @@
|
||||
// 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 static io.flutter.Build.API_LEVELS;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.argThat;
|
||||
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 android.annotation.TargetApi;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
import io.flutter.plugin.common.FlutterException;
|
||||
import io.flutter.plugin.common.JSONMethodCodec;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import java.nio.ByteBuffer;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ScribeChannelTest {
|
||||
private static BinaryMessenger.BinaryReply sendToBinaryMessageHandler(
|
||||
BinaryMessenger.BinaryMessageHandler binaryMessageHandler, String method) {
|
||||
MethodCall methodCall = new MethodCall(method, null);
|
||||
ByteBuffer encodedMethodCall = JSONMethodCodec.INSTANCE.encodeMethodCall(methodCall);
|
||||
BinaryMessenger.BinaryReply mockReply = mock(BinaryMessenger.BinaryReply.class);
|
||||
binaryMessageHandler.onMessage((ByteBuffer) encodedMethodCall.flip(), mockReply);
|
||||
return mockReply;
|
||||
}
|
||||
|
||||
ScribeChannel.ScribeMethodHandler mockHandler;
|
||||
BinaryMessenger.BinaryMessageHandler binaryMessageHandler;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ArgumentCaptor<BinaryMessenger.BinaryMessageHandler> binaryMessageHandlerCaptor =
|
||||
ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
|
||||
DartExecutor mockBinaryMessenger = mock(DartExecutor.class);
|
||||
mockHandler = mock(ScribeChannel.ScribeMethodHandler.class);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mockBinaryMessenger);
|
||||
|
||||
scribeChannel.setScribeMethodHandler(mockHandler);
|
||||
|
||||
verify((BinaryMessenger) mockBinaryMessenger, times(1))
|
||||
.setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
|
||||
|
||||
binaryMessageHandler = binaryMessageHandlerCaptor.getValue();
|
||||
}
|
||||
|
||||
@Config(minSdk = API_LEVELS.API_34)
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@Test
|
||||
public void respondsToStartStylusHandwriting() {
|
||||
BinaryMessenger.BinaryReply mockReply =
|
||||
sendToBinaryMessageHandler(
|
||||
binaryMessageHandler, ScribeChannel.METHOD_START_STYLUS_HANDWRITING);
|
||||
|
||||
verify(mockReply)
|
||||
.reply(
|
||||
argThat(
|
||||
(ByteBuffer reply) -> {
|
||||
reply.flip();
|
||||
try {
|
||||
final Object decodedReply = JSONMethodCodec.INSTANCE.decodeEnvelope(reply);
|
||||
return decodedReply == null;
|
||||
} catch (FlutterException e) {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
verify(mockHandler).startStylusHandwriting();
|
||||
}
|
||||
|
||||
@Config(minSdk = API_LEVELS.API_34)
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@Test
|
||||
public void respondsToIsFeatureAvailable() {
|
||||
BinaryMessenger.BinaryReply mockReply =
|
||||
sendToBinaryMessageHandler(binaryMessageHandler, ScribeChannel.METHOD_IS_FEATURE_AVAILABLE);
|
||||
|
||||
verify(mockReply)
|
||||
.reply(
|
||||
argThat(
|
||||
(ByteBuffer reply) -> {
|
||||
reply.flip();
|
||||
try {
|
||||
final Object decodedReply = JSONMethodCodec.INSTANCE.decodeEnvelope(reply);
|
||||
// Should succeed and should tell whether or not Scribe is available by
|
||||
// using a boolean.
|
||||
return decodedReply.getClass() == java.lang.Boolean.class;
|
||||
} catch (FlutterException e) {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
verify(mockHandler).isFeatureAvailable();
|
||||
}
|
||||
|
||||
@Config(minSdk = API_LEVELS.API_34)
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@Test
|
||||
public void respondsToIsStylusHandwritingAvailable() {
|
||||
BinaryMessenger.BinaryReply mockReply =
|
||||
sendToBinaryMessageHandler(
|
||||
binaryMessageHandler, ScribeChannel.METHOD_IS_STYLUS_HANDWRITING_AVAILABLE);
|
||||
|
||||
verify(mockReply)
|
||||
.reply(
|
||||
argThat(
|
||||
(ByteBuffer reply) -> {
|
||||
reply.flip();
|
||||
try {
|
||||
final Object decodedReply = JSONMethodCodec.INSTANCE.decodeEnvelope(reply);
|
||||
// Should succeed and should tell whether or not Scribe is available by
|
||||
// using a boolean.
|
||||
return decodedReply.getClass() == java.lang.Boolean.class;
|
||||
} catch (FlutterException e) {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
verify(mockHandler).isStylusHandwritingAvailable();
|
||||
}
|
||||
|
||||
@Config(sdk = API_LEVELS.API_32)
|
||||
@TargetApi(API_LEVELS.API_32)
|
||||
@Test
|
||||
public void respondsToStartStylusHandwritingWhenAPILevelUnsupported() {
|
||||
BinaryMessenger.BinaryReply mockReply =
|
||||
sendToBinaryMessageHandler(
|
||||
binaryMessageHandler, ScribeChannel.METHOD_START_STYLUS_HANDWRITING);
|
||||
|
||||
verify(mockReply)
|
||||
.reply(
|
||||
argThat(
|
||||
(ByteBuffer reply) -> {
|
||||
reply.flip();
|
||||
try {
|
||||
final Object decodedReply = JSONMethodCodec.INSTANCE.decodeEnvelope(reply);
|
||||
return false;
|
||||
} catch (FlutterException e) {
|
||||
// Should fail because the API version is too low.
|
||||
return true;
|
||||
}
|
||||
}));
|
||||
verify(mockHandler, never()).startStylusHandwriting();
|
||||
}
|
||||
|
||||
@Config(sdk = API_LEVELS.API_33)
|
||||
@TargetApi(API_LEVELS.API_33)
|
||||
@Test
|
||||
public void respondsToIsFeatureAvailableWhenAPILevelUnsupported() {
|
||||
BinaryMessenger.BinaryReply mockReply =
|
||||
sendToBinaryMessageHandler(binaryMessageHandler, ScribeChannel.METHOD_IS_FEATURE_AVAILABLE);
|
||||
|
||||
verify(mockReply)
|
||||
.reply(
|
||||
argThat(
|
||||
(ByteBuffer reply) -> {
|
||||
reply.flip();
|
||||
try {
|
||||
final Object decodedReply = JSONMethodCodec.INSTANCE.decodeEnvelope(reply);
|
||||
// Should succeed and indicate that Scribe is not available.
|
||||
return decodedReply.getClass() == java.lang.Boolean.class
|
||||
&& !((boolean) decodedReply);
|
||||
} catch (FlutterException e) {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
verify(mockHandler).isFeatureAvailable();
|
||||
}
|
||||
|
||||
@Config(sdk = API_LEVELS.API_33)
|
||||
@TargetApi(API_LEVELS.API_33)
|
||||
@Test
|
||||
public void respondsToIsStylusHandwritingAvailableWhenAPILevelUnsupported() {
|
||||
BinaryMessenger.BinaryReply mockReply =
|
||||
sendToBinaryMessageHandler(
|
||||
binaryMessageHandler, ScribeChannel.METHOD_IS_STYLUS_HANDWRITING_AVAILABLE);
|
||||
|
||||
verify(mockReply)
|
||||
.reply(
|
||||
argThat(
|
||||
(ByteBuffer reply) -> {
|
||||
reply.flip();
|
||||
try {
|
||||
final Object decodedReply = JSONMethodCodec.INSTANCE.decodeEnvelope(reply);
|
||||
return false;
|
||||
} catch (FlutterException e) {
|
||||
// Should fail because the API version is too low.
|
||||
return true;
|
||||
}
|
||||
}));
|
||||
verify(mockHandler, never()).isStylusHandwritingAvailable();
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,7 @@ import com.ibm.icu.lang.UProperty;
|
||||
import io.flutter.embedding.android.KeyboardManager;
|
||||
import io.flutter.embedding.engine.FlutterJNI;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.plugin.common.JSONMethodCodec;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
@ -105,6 +106,7 @@ public class InputConnectionAdaptorTest {
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class)));
|
||||
int inputTargetId = 0;
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState mEditable = new ListenableEditingState(null, testView);
|
||||
Selection.setSelection(mEditable, 0, 0);
|
||||
ListenableEditingState spyEditable = spy(mEditable);
|
||||
@ -113,7 +115,13 @@ public class InputConnectionAdaptorTest {
|
||||
|
||||
InputConnectionAdaptor inputConnectionAdaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView, inputTargetId, textInputChannel, mockKeyboardManager, spyEditable, outAttrs);
|
||||
testView,
|
||||
inputTargetId,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
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');
|
||||
@ -199,12 +207,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -260,12 +270,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -291,12 +303,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -328,12 +342,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -363,12 +379,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -401,12 +419,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -436,12 +456,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -475,12 +497,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -512,12 +536,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -547,12 +573,14 @@ public class InputConnectionAdaptorTest {
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJNI, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(dartExecutor);
|
||||
ListenableEditingState editable = sampleEditable(0, 0);
|
||||
InputConnectionAdaptor adaptor =
|
||||
new InputConnectionAdaptor(
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
@ -1080,6 +1108,7 @@ public class InputConnectionAdaptorTest {
|
||||
testView,
|
||||
1,
|
||||
mock(TextInputChannel.class),
|
||||
mock(ScribeChannel.class),
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
new EditorInfo());
|
||||
@ -1131,6 +1160,7 @@ public class InputConnectionAdaptorTest {
|
||||
testView,
|
||||
1,
|
||||
mock(TextInputChannel.class),
|
||||
mock(ScribeChannel.class),
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
new EditorInfo());
|
||||
@ -1277,6 +1307,7 @@ public class InputConnectionAdaptorTest {
|
||||
View testView = new View(ApplicationProvider.getApplicationContext());
|
||||
int client = 0;
|
||||
TextInputChannel textInputChannel = mock(TextInputChannel.class);
|
||||
ScribeChannel scribeChannel = mock(ScribeChannel.class);
|
||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||
when(mockFlutterJNI.isCodePointEmoji(anyInt()))
|
||||
.thenAnswer((invocation) -> Emoji.isEmoji((int) invocation.getArguments()[0]));
|
||||
@ -1290,7 +1321,14 @@ public class InputConnectionAdaptorTest {
|
||||
.thenAnswer(
|
||||
(invocation) -> Emoji.isRegionalIndicatorSymbol((int) invocation.getArguments()[0]));
|
||||
return new InputConnectionAdaptor(
|
||||
testView, client, textInputChannel, mockKeyboardManager, editable, null, mockFlutterJNI);
|
||||
testView,
|
||||
client,
|
||||
textInputChannel,
|
||||
scribeChannel,
|
||||
mockKeyboardManager,
|
||||
editable,
|
||||
null,
|
||||
mockFlutterJNI);
|
||||
}
|
||||
|
||||
private static class Emoji {
|
||||
|
||||
@ -14,6 +14,7 @@ import android.view.inputmethod.EditorInfo;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.embedding.android.KeyboardManager;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import java.util.ArrayList;
|
||||
import org.junit.Before;
|
||||
@ -280,6 +281,7 @@ public class ListenableEditingStateTest {
|
||||
testView,
|
||||
0,
|
||||
mock(TextInputChannel.class),
|
||||
mock(ScribeChannel.class),
|
||||
mockKeyboardManager,
|
||||
editingState,
|
||||
new EditorInfo());
|
||||
@ -305,6 +307,7 @@ public class ListenableEditingStateTest {
|
||||
testView,
|
||||
0,
|
||||
mock(TextInputChannel.class),
|
||||
mock(ScribeChannel.class),
|
||||
mockKeyboardManager,
|
||||
editingState,
|
||||
new EditorInfo());
|
||||
@ -339,6 +342,7 @@ public class ListenableEditingStateTest {
|
||||
testView,
|
||||
0,
|
||||
mock(TextInputChannel.class),
|
||||
mock(ScribeChannel.class),
|
||||
mockKeyboardManager,
|
||||
editingState,
|
||||
new EditorInfo());
|
||||
@ -399,6 +403,7 @@ public class ListenableEditingStateTest {
|
||||
testView,
|
||||
0,
|
||||
mock(TextInputChannel.class),
|
||||
mock(ScribeChannel.class),
|
||||
mockKeyboardManager,
|
||||
editingState,
|
||||
new EditorInfo());
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
// 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.editing;
|
||||
|
||||
import static io.flutter.Build.API_LEVELS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ScribePluginTest {
|
||||
private final Context ctx = ApplicationProvider.getApplicationContext();
|
||||
|
||||
ScribePlugin scribePlugin;
|
||||
InputMethodManager mockImm;
|
||||
View testView;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
ScribeChannel mockScribeChannel = mock(ScribeChannel.class);
|
||||
testView = new View(ctx);
|
||||
mockImm = mock(InputMethodManager.class);
|
||||
if (Build.VERSION.SDK_INT >= API_LEVELS.API_34) {
|
||||
when(mockImm.isStylusHandwritingAvailable()).thenReturn(true);
|
||||
}
|
||||
scribePlugin = new ScribePlugin(testView, mockImm, mockScribeChannel);
|
||||
}
|
||||
|
||||
@Config(minSdk = API_LEVELS.API_34)
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@Test
|
||||
public void scribePluginIsFeatureAvailable() {
|
||||
assertEquals(scribePlugin.isFeatureAvailable(), true);
|
||||
|
||||
verify(mockImm).isStylusHandwritingAvailable();
|
||||
}
|
||||
|
||||
@Config(minSdk = API_LEVELS.API_34)
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@Test
|
||||
public void scribePluginIsStylusHandwritingAvailable() {
|
||||
assertEquals(scribePlugin.isStylusHandwritingAvailable(), true);
|
||||
|
||||
verify(mockImm).isStylusHandwritingAvailable();
|
||||
}
|
||||
|
||||
@Config(minSdk = API_LEVELS.API_34)
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@Test
|
||||
public void scribePluginStartStylusHandwriting() {
|
||||
scribePlugin.startStylusHandwriting();
|
||||
|
||||
verify(mockImm).startStylusHandwriting(testView);
|
||||
}
|
||||
|
||||
@Config(sdk = API_LEVELS.API_32)
|
||||
@TargetApi(API_LEVELS.API_32)
|
||||
@Test
|
||||
public void scribePluginStartStylusHandwritingWhenAPILevelUnsupported() {
|
||||
assertNotNull(scribePlugin);
|
||||
|
||||
assertThrows(
|
||||
NoSuchMethodError.class,
|
||||
() -> {
|
||||
scribePlugin.startStylusHandwriting();
|
||||
});
|
||||
}
|
||||
|
||||
@Config(sdk = API_LEVELS.API_33)
|
||||
@TargetApi(API_LEVELS.API_33)
|
||||
@Test
|
||||
public void scribePluginIsFeatureAvailableWhenAPILevelUnsupported() {
|
||||
assertEquals(scribePlugin.isFeatureAvailable(), false);
|
||||
}
|
||||
|
||||
@Config(sdk = API_LEVELS.API_33)
|
||||
@TargetApi(API_LEVELS.API_33)
|
||||
@Test
|
||||
public void scribePluginIsStylusHandwritingAvailableWhenAPILevelUnsupported() {
|
||||
assertNotNull(scribePlugin);
|
||||
|
||||
assertThrows(
|
||||
NoSuchMethodError.class,
|
||||
() -> {
|
||||
scribePlugin.isStylusHandwritingAvailable();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@ package io.flutter.plugin.editing;
|
||||
|
||||
import static io.flutter.Build.API_LEVELS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@ -45,6 +46,7 @@ import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
import androidx.core.view.inputmethod.EditorInfoCompat;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import io.flutter.embedding.android.FlutterView;
|
||||
@ -54,6 +56,7 @@ import io.flutter.embedding.engine.FlutterJNI;
|
||||
import io.flutter.embedding.engine.dart.DartExecutor;
|
||||
import io.flutter.embedding.engine.loader.FlutterLoader;
|
||||
import io.flutter.embedding.engine.renderer.FlutterRenderer;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel.TextEditState;
|
||||
import io.flutter.plugin.common.BinaryMessenger;
|
||||
@ -133,8 +136,10 @@ public class TextInputPluginTest {
|
||||
FlutterJNI mockFlutterJni = mock(FlutterJNI.class);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
|
||||
ArgumentCaptor<String> channelCaptor = ArgumentCaptor.forClass(String.class);
|
||||
ArgumentCaptor<ByteBuffer> bufferCaptor = ArgumentCaptor.forClass(ByteBuffer.class);
|
||||
@ -152,8 +157,10 @@ public class TextInputPluginTest {
|
||||
testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
|
||||
View testView = new View(ctx);
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -194,8 +201,10 @@ public class TextInputPluginTest {
|
||||
testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
|
||||
View testView = new View(ctx);
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
|
||||
// Here's no textInputPlugin.setTextInputClient()
|
||||
textInputPlugin.setTextInputEditingState(
|
||||
@ -211,8 +220,10 @@ public class TextInputPluginTest {
|
||||
testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
|
||||
View testView = new View(ctx);
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -261,8 +272,10 @@ public class TextInputPluginTest {
|
||||
EditorInfo outAttrs = new EditorInfo();
|
||||
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||
|
||||
// Change InputTarget to FRAMEWORK_CLIENT.
|
||||
@ -374,8 +387,10 @@ public class TextInputPluginTest {
|
||||
EditorInfo outAttrs = new EditorInfo();
|
||||
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||
final TextEditingDelta expectedDelta =
|
||||
new TextEditingDelta("", 0, 0, newText, newText.length(), newText.length(), 0, 49);
|
||||
@ -503,8 +518,10 @@ public class TextInputPluginTest {
|
||||
EditorInfo outAttrs = new EditorInfo();
|
||||
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||
final TextEditingDelta expectedDelta =
|
||||
new TextEditingDelta("", 0, 0, newText, newText.length(), newText.length(), 0, 49);
|
||||
@ -612,8 +629,10 @@ public class TextInputPluginTest {
|
||||
EditorInfo outAttrs = new EditorInfo();
|
||||
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
CharSequence newText = "I do not fear computers. I fear the lack of them.";
|
||||
final TextEditingDelta expectedDelta =
|
||||
new TextEditingDelta(
|
||||
@ -722,8 +741,10 @@ public class TextInputPluginTest {
|
||||
EditorInfo outAttrs = new EditorInfo();
|
||||
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
CharSequence newText = "helfo";
|
||||
final TextEditingDelta expectedDelta = new TextEditingDelta(newText, 0, 5, "hello", 5, 5, 0, 5);
|
||||
|
||||
@ -829,8 +850,10 @@ public class TextInputPluginTest {
|
||||
EditorInfo outAttrs = new EditorInfo();
|
||||
outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE;
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
|
||||
// Change InputTarget to FRAMEWORK_CLIENT.
|
||||
textInputPlugin.setTextInputClient(
|
||||
@ -920,8 +943,10 @@ public class TextInputPluginTest {
|
||||
testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
|
||||
View testView = new View(ctx);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -958,8 +983,10 @@ public class TextInputPluginTest {
|
||||
testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
|
||||
View testView = new View(ctx);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1006,8 +1033,10 @@ public class TextInputPluginTest {
|
||||
testImm.setCurrentInputMethodSubtype(inputMethodSubtype);
|
||||
View testView = new View(ctx);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1107,8 +1136,10 @@ public class TextInputPluginTest {
|
||||
|
||||
View testView = new View(ctx);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1134,8 +1165,10 @@ public class TextInputPluginTest {
|
||||
public void destroy_clearTextInputMethodHandler() {
|
||||
View testView = new View(ctx);
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
verify(textInputChannel, times(1)).setTextInputMethodHandler(isNotNull());
|
||||
textInputPlugin.destroy();
|
||||
verify(textInputChannel, times(1)).setTextInputMethodHandler(isNull());
|
||||
@ -1150,8 +1183,10 @@ public class TextInputPluginTest {
|
||||
View testView = new View(ctx);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1225,8 +1260,10 @@ public class TextInputPluginTest {
|
||||
View testView = new View(ctx);
|
||||
DartExecutor dartExecutor = spy(new DartExecutor(mockFlutterJni, mock(AssetManager.class)));
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1263,8 +1300,10 @@ public class TextInputPluginTest {
|
||||
View testView = new View(ctx);
|
||||
DartExecutor dartExecutor = mock(DartExecutor.class);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1293,8 +1332,10 @@ public class TextInputPluginTest {
|
||||
View testView = new View(ctx);
|
||||
DartExecutor dartExecutor = mock(DartExecutor.class);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1321,8 +1362,10 @@ public class TextInputPluginTest {
|
||||
View testView = new View(ctx);
|
||||
DartExecutor dartExecutor = mock(DartExecutor.class);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
@ -1351,6 +1394,74 @@ public class TextInputPluginTest {
|
||||
| InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
|
||||
}
|
||||
|
||||
@Config(minSdk = API_LEVELS.API_34)
|
||||
@TargetApi(API_LEVELS.API_34)
|
||||
@Test
|
||||
public void inputConnection_setsStylusHandwritingAvailable() {
|
||||
View testView = new View(ctx);
|
||||
DartExecutor dartExecutor = mock(DartExecutor.class);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
TextInputChannel.TextCapitalization.NONE,
|
||||
new TextInputChannel.InputType(TextInputChannel.TextInputType.MULTILINE, false, false),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null));
|
||||
|
||||
EditorInfo editorInfo = new EditorInfo();
|
||||
InputConnection connection =
|
||||
textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), editorInfo);
|
||||
|
||||
assertTrue(EditorInfoCompat.isStylusHandwritingEnabled(editorInfo));
|
||||
}
|
||||
|
||||
@Config(sdk = API_LEVELS.API_32)
|
||||
@TargetApi(API_LEVELS.API_32)
|
||||
@Test
|
||||
public void inputConnection_doesNotcallSetsStylusHandwritingAvailableWhenAPILevelUnsupported() {
|
||||
View testView = new View(ctx);
|
||||
DartExecutor dartExecutor = mock(DartExecutor.class);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(dartExecutor);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
textInputPlugin.setTextInputClient(
|
||||
0,
|
||||
new TextInputChannel.Configuration(
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
TextInputChannel.TextCapitalization.NONE,
|
||||
new TextInputChannel.InputType(TextInputChannel.TextInputType.MULTILINE, false, false),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null));
|
||||
|
||||
EditorInfo editorInfo = new EditorInfo();
|
||||
InputConnection connection =
|
||||
textInputPlugin.createInputConnection(testView, mock(KeyboardManager.class), editorInfo);
|
||||
|
||||
assertFalse(EditorInfoCompat.isStylusHandwritingEnabled(editorInfo));
|
||||
}
|
||||
|
||||
// -------- Start: Autofill Tests -------
|
||||
@Test
|
||||
public void autofill_enabledByDefault() {
|
||||
@ -1359,8 +1470,10 @@ public class TextInputPluginTest {
|
||||
}
|
||||
FlutterView testView = new FlutterView(ctx);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
|
||||
@ -1419,8 +1532,10 @@ public class TextInputPluginTest {
|
||||
}
|
||||
FlutterView testView = new FlutterView(ctx);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
|
||||
@ -1456,8 +1571,10 @@ public class TextInputPluginTest {
|
||||
}
|
||||
FlutterView testView = new FlutterView(ctx);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1",
|
||||
@ -1501,8 +1618,10 @@ public class TextInputPluginTest {
|
||||
}
|
||||
FlutterView testView = getTestView();
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1",
|
||||
@ -1593,8 +1712,10 @@ public class TextInputPluginTest {
|
||||
// Migrate to ActivityScenario by following https://github.com/robolectric/robolectric/pull/4736
|
||||
FlutterView testView = getTestView();
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
final TextInputChannel.Configuration.Autofill autofill =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
"1",
|
||||
@ -1646,8 +1767,10 @@ public class TextInputPluginTest {
|
||||
TestAfm testAfm = Shadow.extract(ctx.getSystemService(AutofillManager.class));
|
||||
FlutterView testView = getTestView();
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
|
||||
// Set up an autofill scenario with 2 fields.
|
||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||
@ -1728,6 +1851,7 @@ public class TextInputPluginTest {
|
||||
testView,
|
||||
0,
|
||||
mock(TextInputChannel.class),
|
||||
mock(ScribeChannel.class),
|
||||
mockKeyboardManager,
|
||||
(ListenableEditingState) textInputPlugin.getEditable(),
|
||||
new EditorInfo());
|
||||
@ -1782,8 +1906,10 @@ public class TextInputPluginTest {
|
||||
TestAfm testAfm = Shadow.extract(ctx.getSystemService(AutofillManager.class));
|
||||
FlutterView testView = getTestView();
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
|
||||
// Set up an autofill scenario with 2 fields.
|
||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||
@ -1877,8 +2003,10 @@ public class TextInputPluginTest {
|
||||
}
|
||||
FlutterView testView = new FlutterView(ctx);
|
||||
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
// Set up an autofill scenario with 2 fields.
|
||||
final TextInputChannel.Configuration.Autofill autofillConfig =
|
||||
new TextInputChannel.Configuration.Autofill(
|
||||
@ -1928,8 +2056,10 @@ public class TextInputPluginTest {
|
||||
TestAfm testAfm = Shadow.extract(ctx.getSystemService(AutofillManager.class));
|
||||
FlutterView testView = getTestView();
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
|
||||
// Set up an autofill scenario with 2 fields.
|
||||
final TextInputChannel.Configuration.Autofill autofill1 =
|
||||
@ -2041,6 +2171,7 @@ public class TextInputPluginTest {
|
||||
ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
|
||||
DartExecutor mockBinaryMessenger = mock(DartExecutor.class);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mockBinaryMessenger);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
|
||||
EventHandler mockEventHandler = mock(EventHandler.class);
|
||||
TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
|
||||
@ -2048,7 +2179,8 @@ public class TextInputPluginTest {
|
||||
|
||||
View testView = new View(ctx);
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
|
||||
verify(mockBinaryMessenger, times(1))
|
||||
.setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
|
||||
@ -2072,6 +2204,7 @@ public class TextInputPluginTest {
|
||||
ArgumentCaptor.forClass(BinaryMessenger.BinaryMessageHandler.class);
|
||||
DartExecutor mockBinaryMessenger = mock(DartExecutor.class);
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mockBinaryMessenger);
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
|
||||
EventHandler mockEventHandler = mock(EventHandler.class);
|
||||
TestImm testImm = Shadow.extract(ctx.getSystemService(Context.INPUT_METHOD_SERVICE));
|
||||
@ -2079,7 +2212,8 @@ public class TextInputPluginTest {
|
||||
|
||||
View testView = new View(ctx);
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
|
||||
verify(mockBinaryMessenger, times(1))
|
||||
.setMessageHandler(any(String.class), binaryMessageHandlerCaptor.capture());
|
||||
@ -2108,8 +2242,10 @@ public class TextInputPluginTest {
|
||||
when(testView.getWindowSystemUiVisibility()).thenReturn(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
@ -2189,8 +2325,10 @@ public class TextInputPluginTest {
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
|
||||
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
@ -2268,8 +2406,10 @@ public class TextInputPluginTest {
|
||||
when(testView.getWindowSystemUiVisibility()).thenReturn(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
|
||||
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
|
||||
ScribeChannel scribeChannel = new ScribeChannel(mock(DartExecutor.class));
|
||||
TextInputPlugin textInputPlugin =
|
||||
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
|
||||
new TextInputPlugin(
|
||||
testView, textInputChannel, scribeChannel, mock(PlatformViewsController.class));
|
||||
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
|
||||
FlutterEngine flutterEngine = spy(new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJni));
|
||||
FlutterRenderer flutterRenderer = spy(new FlutterRenderer(mockFlutterJni));
|
||||
|
||||
@ -44,6 +44,7 @@ import io.flutter.embedding.engine.systemchannels.AccessibilityChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.MouseCursorChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.PlatformViewsChannel.PlatformViewTouch;
|
||||
import io.flutter.embedding.engine.systemchannels.ScribeChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
|
||||
import io.flutter.embedding.engine.systemchannels.TextInputChannel;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
@ -1668,6 +1669,7 @@ public class PlatformViewsControllerTest {
|
||||
when(engine.getMouseCursorChannel()).thenReturn(mock(MouseCursorChannel.class));
|
||||
when(engine.getTextInputChannel()).thenReturn(mock(TextInputChannel.class));
|
||||
when(engine.getSettingsChannel()).thenReturn(new SettingsChannel(executor));
|
||||
when(engine.getScribeChannel()).thenReturn(mock(ScribeChannel.class));
|
||||
when(engine.getPlatformViewsController()).thenReturn(platformViewsController);
|
||||
when(engine.getLocalizationPlugin()).thenReturn(mock(LocalizationPlugin.class));
|
||||
when(engine.getAccessibilityChannel()).thenReturn(mock(AccessibilityChannel.class));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user