Commit 368ae69d authored by yangwu.jia's avatar yangwu.jia

Boost 1.9升级,TextField键盘呼出问题解决

parent c7fe899c
package com.idlefish.flutterboost; package com.idlefish.flutterboost;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
//import android.graphics.Insets;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.LocaleList; import android.os.LocaleList;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.support.annotation.VisibleForTesting;
import android.support.v4.view.ViewCompat;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeProvider; import android.view.accessibility.AccessibilityNodeProvider;
...@@ -23,23 +26,43 @@ import android.view.inputmethod.InputConnection; ...@@ -23,23 +26,43 @@ import android.view.inputmethod.InputConnection;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set;
import io.flutter.embedding.android.AndroidKeyProcessor; import io.flutter.Log;
import io.flutter.embedding.android.AndroidTouchProcessor; import io.flutter.embedding.android.*;
import io.flutter.embedding.android.FlutterSurfaceView;
import io.flutter.embedding.android.FlutterTextureView;
import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterRenderer;
import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener; import io.flutter.embedding.engine.renderer.OnFirstFrameRenderedListener;
import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.plugin.platform.PlatformViewsController;
import io.flutter.view.AccessibilityBridge; import io.flutter.view.AccessibilityBridge;
/**
* Displays a Flutter UI on an Android device.
* <p>
* A {@code FlutterView}'s UI is painted by a corresponding {@link FlutterEngine}.
* <p>
* A {@code FlutterView} can operate in 2 different {@link RenderMode}s:
* <ol>
* <li>{@link RenderMode#surface}, which paints a Flutter UI to a {@link android.view.SurfaceView}.
* This mode has the best performance, but a {@code FlutterView} in this mode cannot be positioned
* between 2 other Android {@code View}s in the z-index, nor can it be animated/transformed.
* Unless the special capabilities of a {@link android.graphics.SurfaceTexture} are required,
* developers should strongly prefer this render mode.</li>
* <li>{@link RenderMode#texture}, which paints a Flutter UI to a {@link android.graphics.SurfaceTexture}.
* This mode is not as performant as {@link RenderMode#surface}, but a {@code FlutterView} in this
* mode can be animated and transformed, as well as positioned in the z-index between 2+ other
* Android {@code Views}. Unless the special capabilities of a {@link android.graphics.SurfaceTexture}
* are required, developers should strongly prefer the {@link RenderMode#surface} render mode.</li>
* </ol>
* See <a>https://source.android.com/devices/graphics/arch-tv#surface_or_texture</a> for more
* information comparing {@link android.view.SurfaceView} and {@link android.view.TextureView}.
*/
public class XFlutterView extends FrameLayout { public class XFlutterView extends FrameLayout {
private static final String TAG = "XFlutterView"; private static final String TAG = "FlutterView";
// Behavior configuration of this FlutterView. // Behavior configuration of this FlutterView.
@NonNull @NonNull
...@@ -50,10 +73,14 @@ public class XFlutterView extends FrameLayout { ...@@ -50,10 +73,14 @@ public class XFlutterView extends FrameLayout {
// Internal view hierarchy references. // Internal view hierarchy references.
@Nullable @Nullable
private FlutterRenderer.RenderSurface renderSurface; private FlutterRenderer.RenderSurface renderSurface;
private final Set<OnFirstFrameRenderedListener> onFirstFrameRenderedListeners = new HashSet<>();
private boolean didRenderFirstFrame;
// Connections to a Flutter execution context. // Connections to a Flutter execution context.
@Nullable @Nullable
private FlutterEngine flutterEngine; private FlutterEngine flutterEngine;
@NonNull
private final Set<FlutterView.FlutterEngineAttachmentListener> flutterEngineAttachmentListeners = new HashSet<>();
// Components that process various types of Android View input and events, // Components that process various types of Android View input and events,
// possibly storing intermediate state, and communicating those events to Flutter. // possibly storing intermediate state, and communicating those events to Flutter.
...@@ -61,9 +88,9 @@ public class XFlutterView extends FrameLayout { ...@@ -61,9 +88,9 @@ public class XFlutterView extends FrameLayout {
// These components essentially add some additional behavioral logic on top of // These components essentially add some additional behavioral logic on top of
// existing, stateless system channels, e.g., KeyEventChannel, TextInputChannel, etc. // existing, stateless system channels, e.g., KeyEventChannel, TextInputChannel, etc.
@Nullable @Nullable
private XTextInputPlugin textInputPlugin; private TextInputPlugin textInputPlugin;
@Nullable @Nullable
private XAndroidKeyProcessor androidKeyProcessor; private AndroidKeyProcessor androidKeyProcessor;
@Nullable @Nullable
private AndroidTouchProcessor androidTouchProcessor; private AndroidTouchProcessor androidTouchProcessor;
@Nullable @Nullable
...@@ -79,17 +106,51 @@ public class XFlutterView extends FrameLayout { ...@@ -79,17 +106,51 @@ public class XFlutterView extends FrameLayout {
} }
}; };
private final OnFirstFrameRenderedListener onFirstFrameRenderedListener = new OnFirstFrameRenderedListener() {
@Override
public void onFirstFrameRendered() {
didRenderFirstFrame = true;
for (OnFirstFrameRenderedListener listener : onFirstFrameRenderedListeners) {
listener.onFirstFrameRendered();
}
}
};
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes.
* <p>
* <ul>
* <li>{@link #renderMode} defaults to {@link RenderMode#surface}.</li>
* <li>{@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.</li>
* </ul>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context) { public XFlutterView(@NonNull Context context) {
this(context, null, null, null); this(context, null, null, null);
} }
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes,
* and allows selection of a {@link #renderMode}.
* <p>
* {@link #transparencyMode} defaults to {@link TransparencyMode#opaque}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode) { public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode) {
this(context, null, renderMode, null); this(context, null, renderMode, null);
} }
/**
* Constructs a {@code FlutterView} programmatically, without any XML attributes,
* assumes the use of {@link RenderMode#surface}, and allows selection of a {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/
public XFlutterView(@NonNull Context context, @NonNull FlutterView.TransparencyMode transparencyMode) { public XFlutterView(@NonNull Context context, @NonNull FlutterView.TransparencyMode transparencyMode) {
this(context, null, FlutterView.RenderMode.surface, transparencyMode); this(context, null, FlutterView.RenderMode.surface, transparencyMode);
} }
...@@ -97,6 +158,9 @@ public class XFlutterView extends FrameLayout { ...@@ -97,6 +158,9 @@ public class XFlutterView extends FrameLayout {
/** /**
* Constructs a {@code FlutterView} programmatically, without any XML attributes, and allows * Constructs a {@code FlutterView} programmatically, without any XML attributes, and allows
* a selection of {@link #renderMode} and {@link #transparencyMode}. * a selection of {@link #renderMode} and {@link #transparencyMode}.
* <p>
* {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/ */
public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode, @NonNull FlutterView.TransparencyMode transparencyMode) { public XFlutterView(@NonNull Context context, @NonNull FlutterView.RenderMode renderMode, @NonNull FlutterView.TransparencyMode transparencyMode) {
this(context, null, renderMode, transparencyMode); this(context, null, renderMode, transparencyMode);
...@@ -104,9 +168,11 @@ public class XFlutterView extends FrameLayout { ...@@ -104,9 +168,11 @@ public class XFlutterView extends FrameLayout {
/** /**
* Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner. * Constructs a {@code FlutterSurfaceView} in an XML-inflation-compliant manner.
* * <p>
* // TODO(mattcarroll): expose renderMode in XML when build system supports R.attr * {@code FlutterView} requires an {@code Activity} instead of a generic {@code Context}
* to be compatible with {@link PlatformViewsController}.
*/ */
// TODO(mattcarroll): expose renderMode in XML when build system supports R.attr
public XFlutterView(@NonNull Context context, @Nullable AttributeSet attrs) { public XFlutterView(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, null, null); this(context, attrs, null, null);
} }
...@@ -121,17 +187,17 @@ public class XFlutterView extends FrameLayout { ...@@ -121,17 +187,17 @@ public class XFlutterView extends FrameLayout {
} }
private void init() { private void init() {
Log.d(TAG, "Initializing FlutterView"); Log.v(TAG, "Initializing FlutterView");
switch (renderMode) { switch (renderMode) {
case surface: case surface:
Log.d(TAG, "Internally creating a FlutterSurfaceView."); Log.v(TAG, "Internally using a FlutterSurfaceView.");
FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(getContext(), transparencyMode == FlutterView.TransparencyMode.transparent); FlutterSurfaceView flutterSurfaceView = new FlutterSurfaceView(getContext(), transparencyMode == FlutterView.TransparencyMode.transparent);
renderSurface = flutterSurfaceView; renderSurface = flutterSurfaceView;
addView(flutterSurfaceView); addView(flutterSurfaceView);
break; break;
case texture: case texture:
Log.d(TAG, "Internally creating a FlutterTextureView."); Log.v(TAG, "Internally using a FlutterTextureView.");
FlutterTextureView flutterTextureView = new FlutterTextureView(getContext()); FlutterTextureView flutterTextureView = new FlutterTextureView(getContext());
renderSurface = flutterTextureView; renderSurface = flutterTextureView;
addView(flutterTextureView); addView(flutterTextureView);
...@@ -143,12 +209,32 @@ public class XFlutterView extends FrameLayout { ...@@ -143,12 +209,32 @@ public class XFlutterView extends FrameLayout {
setFocusableInTouchMode(true); setFocusableInTouchMode(true);
} }
/**
* Returns true if an attached {@link FlutterEngine} has rendered at least 1 frame to this
* {@code FlutterView}.
* <p>
* Returns false if no {@link FlutterEngine} is attached.
* <p>
* This flag is specific to a given {@link FlutterEngine}. The following hypothetical timeline
* demonstrates how this flag changes over time.
* <ol>
* <li>{@code flutterEngineA} is attached to this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineA} renders its first frame to this {@code FlutterView}: returns true</li>
* <li>{@code flutterEngineA} is detached from this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineB} is attached to this {@code FlutterView}: returns false</li>
* <li>{@code flutterEngineB} renders its first frame to this {@code FlutterView}: returns true</li>
* </ol>
*/
public boolean hasRenderedFirstFrame() {
return didRenderFirstFrame;
}
/** /**
* Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's * Adds the given {@code listener} to this {@code FlutterView}, to be notified upon Flutter's
* first rendered frame. * first rendered frame.
*/ */
public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void addOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
renderSurface.addOnFirstFrameRenderedListener(listener); onFirstFrameRenderedListeners.add(listener);
} }
/** /**
...@@ -156,7 +242,7 @@ public class XFlutterView extends FrameLayout { ...@@ -156,7 +242,7 @@ public class XFlutterView extends FrameLayout {
* {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}. * {@link #addOnFirstFrameRenderedListener(OnFirstFrameRenderedListener)}.
*/ */
public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) { public void removeOnFirstFrameRenderedListener(@NonNull OnFirstFrameRenderedListener listener) {
renderSurface.removeOnFirstFrameRenderedListener(listener); onFirstFrameRenderedListeners.remove(listener);
} }
//------- Start: Process View configuration that Flutter cares about. ------ //------- Start: Process View configuration that Flutter cares about. ------
...@@ -168,16 +254,11 @@ public class XFlutterView extends FrameLayout { ...@@ -168,16 +254,11 @@ public class XFlutterView extends FrameLayout {
* change, device language change, device text scale factor change, etc. * change, device language change, device text scale factor change, etc.
*/ */
@Override @Override
protected void onConfigurationChanged(Configuration newConfig) { protected void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
try { Log.v(TAG, "Configuration changed. Sending locales and user settings to Flutter.");
sendLocalesToFlutter(newConfig); sendLocalesToFlutter(newConfig);
sendUserSettingsToFlutter(); sendUserSettingsToFlutter();
}catch (Throwable e){
Log.e(TAG, "onConfigurationChanged error ");
}
} }
/** /**
...@@ -194,6 +275,9 @@ public class XFlutterView extends FrameLayout { ...@@ -194,6 +275,9 @@ public class XFlutterView extends FrameLayout {
@Override @Override
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight); super.onSizeChanged(width, height, oldWidth, oldHeight);
Log.v(TAG, "Size changed. Sending Flutter new viewport metrics. FlutterView was "
+ oldWidth + " x " + oldHeight
+ ", it is now " + width + " x " + height);
viewportMetrics.width = width; viewportMetrics.width = width;
viewportMetrics.height = height; viewportMetrics.height = height;
sendViewportMetricsToFlutter(); sendViewportMetricsToFlutter();
...@@ -212,7 +296,12 @@ public class XFlutterView extends FrameLayout { ...@@ -212,7 +296,12 @@ public class XFlutterView extends FrameLayout {
@Override @Override
@TargetApi(20) @TargetApi(20)
@RequiresApi(20) @RequiresApi(20)
public final WindowInsets onApplyWindowInsets(WindowInsets insets) { // The annotations to suppress "InlinedApi" and "NewApi" lints prevent lint warnings
// caused by usage of Android Q APIs. These calls are safe because they are
// guarded.
@SuppressLint({"InlinedApi", "NewApi"})
@NonNull
public final WindowInsets onApplyWindowInsets(@NonNull WindowInsets insets) {
WindowInsets newInsets = super.onApplyWindowInsets(insets); WindowInsets newInsets = super.onApplyWindowInsets(insets);
// Status bar (top) and left/right system insets should partially obscure the content (padding). // Status bar (top) and left/right system insets should partially obscure the content (padding).
...@@ -226,6 +315,23 @@ public class XFlutterView extends FrameLayout { ...@@ -226,6 +315,23 @@ public class XFlutterView extends FrameLayout {
viewportMetrics.viewInsetRight = 0; viewportMetrics.viewInsetRight = 0;
viewportMetrics.viewInsetBottom = insets.getSystemWindowInsetBottom(); viewportMetrics.viewInsetBottom = insets.getSystemWindowInsetBottom();
viewportMetrics.viewInsetLeft = 0; viewportMetrics.viewInsetLeft = 0;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Insets systemGestureInsets = insets.getSystemGestureInsets();
// viewportMetrics.systemGestureInsetTop = systemGestureInsets.top;
// viewportMetrics.systemGestureInsetRight = systemGestureInsets.right;
// viewportMetrics.systemGestureInsetBottom = systemGestureInsets.bottom;
// viewportMetrics.systemGestureInsetLeft = systemGestureInsets.left;
// }
Log.v(TAG, "Updating window insets (onApplyWindowInsets()):\n"
+ "Status bar insets: Top: " + viewportMetrics.paddingTop
+ ", Left: " + viewportMetrics.paddingLeft + ", Right: " + viewportMetrics.paddingRight + "\n"
+ "Keyboard insets: Bottom: " + viewportMetrics.viewInsetBottom
+ ", Left: " + viewportMetrics.viewInsetLeft + ", Right: " + viewportMetrics.viewInsetRight
+ "System Gesture Insets - Left: " + viewportMetrics.systemGestureInsetLeft + ", Top: " + viewportMetrics.systemGestureInsetTop
+ ", Right: " + viewportMetrics.systemGestureInsetRight + ", Bottom: " + viewportMetrics.viewInsetBottom);
sendViewportMetricsToFlutter(); sendViewportMetricsToFlutter();
return newInsets; return newInsets;
...@@ -240,7 +346,7 @@ public class XFlutterView extends FrameLayout { ...@@ -240,7 +346,7 @@ public class XFlutterView extends FrameLayout {
*/ */
@Override @Override
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
protected boolean fitSystemWindows(Rect insets) { protected boolean fitSystemWindows(@NonNull Rect insets) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
// Status bar, left/right system insets partially obscure content (padding). // Status bar, left/right system insets partially obscure content (padding).
viewportMetrics.paddingTop = insets.top; viewportMetrics.paddingTop = insets.top;
...@@ -253,6 +359,13 @@ public class XFlutterView extends FrameLayout { ...@@ -253,6 +359,13 @@ public class XFlutterView extends FrameLayout {
viewportMetrics.viewInsetRight = 0; viewportMetrics.viewInsetRight = 0;
viewportMetrics.viewInsetBottom = insets.bottom; viewportMetrics.viewInsetBottom = insets.bottom;
viewportMetrics.viewInsetLeft = 0; viewportMetrics.viewInsetLeft = 0;
Log.v(TAG, "Updating window insets (fitSystemWindows()):\n"
+ "Status bar insets: Top: " + viewportMetrics.paddingTop
+ ", Left: " + viewportMetrics.paddingLeft + ", Right: " + viewportMetrics.paddingRight + "\n"
+ "Keyboard insets: Bottom: " + viewportMetrics.viewInsetBottom
+ ", Left: " + viewportMetrics.viewInsetLeft + ", Right: " + viewportMetrics.viewInsetRight);
sendViewportMetricsToFlutter(); sendViewportMetricsToFlutter();
return true; return true;
} else { } else {
...@@ -276,7 +389,8 @@ public class XFlutterView extends FrameLayout { ...@@ -276,7 +389,8 @@ public class XFlutterView extends FrameLayout {
* rather than spread that logic throughout this {@code FlutterView}. * rather than spread that logic throughout this {@code FlutterView}.
*/ */
@Override @Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) { @Nullable
public InputConnection onCreateInputConnection(@NonNull EditorInfo outAttrs) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onCreateInputConnection(outAttrs); return super.onCreateInputConnection(outAttrs);
} }
...@@ -284,6 +398,21 @@ public class XFlutterView extends FrameLayout { ...@@ -284,6 +398,21 @@ public class XFlutterView extends FrameLayout {
return textInputPlugin.createInputConnection(this, outAttrs); return textInputPlugin.createInputConnection(this, outAttrs);
} }
/**
* Allows a {@code View} that is not currently the input connection target to invoke commands on
* the {@link android.view.inputmethod.InputMethodManager}, which is otherwise disallowed.
* <p>
* Returns true to allow non-input-connection-targets to invoke methods on
* {@code InputMethodManager}, or false to exclusively allow the input connection target to invoke
* such methods.
*/
@Override
public boolean checkInputConnectionProxy(View view) {
return flutterEngine != null
? flutterEngine.getPlatformViewsController().checkInputConnectionProxy(view)
: super.checkInputConnectionProxy(view);
}
/** /**
* Invoked when key is released. * Invoked when key is released.
* *
...@@ -292,13 +421,13 @@ public class XFlutterView extends FrameLayout { ...@@ -292,13 +421,13 @@ public class XFlutterView extends FrameLayout {
* software keyboard is used, though a software keyboard may choose to invoke * software keyboard is used, though a software keyboard may choose to invoke
* this method in some situations. * this method in some situations.
* *
* {@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor} * {@link KeyEvent}s are sent from Android to Flutter. {@link }
* may do some additional work with the given {@link KeyEvent}, e.g., combine this * may do some additional work with the given {@link KeyEvent}, e.g., combine this
* {@code keyCode} with the previous {@code keyCode} to generate a unicode combined * {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
* character. * character.
*/ */
@Override @Override
public boolean onKeyUp(int keyCode, KeyEvent event) { public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onKeyUp(keyCode, event); return super.onKeyUp(keyCode, event);
} }
...@@ -315,13 +444,13 @@ public class XFlutterView extends FrameLayout { ...@@ -315,13 +444,13 @@ public class XFlutterView extends FrameLayout {
* software keyboard is used, though a software keyboard may choose to invoke * software keyboard is used, though a software keyboard may choose to invoke
* this method in some situations. * this method in some situations.
* *
* {@link KeyEvent}s are sent from Android to Flutter. {@link AndroidKeyProcessor} * {@link KeyEvent}s are sent from Android to Flutter. {@link }
* may do some additional work with the given {@link KeyEvent}, e.g., combine this * may do some additional work with the given {@link KeyEvent}, e.g., combine this
* {@code keyCode} with the previous {@code keyCode} to generate a unicode combined * {@code keyCode} with the previous {@code keyCode} to generate a unicode combined
* character. * character.
*/ */
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} }
...@@ -337,7 +466,7 @@ public class XFlutterView extends FrameLayout { ...@@ -337,7 +466,7 @@ public class XFlutterView extends FrameLayout {
* method forwards all {@link MotionEvent} data from Android to Flutter. * method forwards all {@link MotionEvent} data from Android to Flutter.
*/ */
@Override @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(@NonNull MotionEvent event) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onTouchEvent(event); return super.onTouchEvent(event);
} }
...@@ -362,7 +491,7 @@ public class XFlutterView extends FrameLayout { ...@@ -362,7 +491,7 @@ public class XFlutterView extends FrameLayout {
* method forwards all {@link MotionEvent} data from Android to Flutter. * method forwards all {@link MotionEvent} data from Android to Flutter.
*/ */
@Override @Override
public boolean onGenericMotionEvent(MotionEvent event) { public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
boolean handled = isAttachedToFlutterEngine() && androidTouchProcessor.onGenericMotionEvent(event); boolean handled = isAttachedToFlutterEngine() && androidTouchProcessor.onGenericMotionEvent(event);
return handled ? true : super.onGenericMotionEvent(event); return handled ? true : super.onGenericMotionEvent(event);
} }
...@@ -379,7 +508,7 @@ public class XFlutterView extends FrameLayout { ...@@ -379,7 +508,7 @@ public class XFlutterView extends FrameLayout {
* processed here for accessibility purposes. * processed here for accessibility purposes.
*/ */
@Override @Override
public boolean onHoverEvent(MotionEvent event) { public boolean onHoverEvent(@NonNull MotionEvent event) {
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
return super.onHoverEvent(event); return super.onHoverEvent(event);
} }
...@@ -395,6 +524,7 @@ public class XFlutterView extends FrameLayout { ...@@ -395,6 +524,7 @@ public class XFlutterView extends FrameLayout {
//-------- Start: Accessibility ------- //-------- Start: Accessibility -------
@Override @Override
@Nullable
public AccessibilityNodeProvider getAccessibilityNodeProvider() { public AccessibilityNodeProvider getAccessibilityNodeProvider() {
if (accessibilityBridge != null && accessibilityBridge.isAccessibilityEnabled()) { if (accessibilityBridge != null && accessibilityBridge.isAccessibilityEnabled()) {
return accessibilityBridge; return accessibilityBridge;
...@@ -406,12 +536,8 @@ public class XFlutterView extends FrameLayout { ...@@ -406,12 +536,8 @@ public class XFlutterView extends FrameLayout {
} }
} }
// TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise add comments. // TODO(mattcarroll): Confer with Ian as to why we need this method. Delete if possible, otherwise add comments.
private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) { private void resetWillNotDraw(boolean isAccessibilityEnabled, boolean isTouchExplorationEnabled) {
if(flutterEngine==null) return;
if(flutterEngine.getRenderer()==null) return;
if (!flutterEngine.getRenderer().isSoftwareRenderingEnabled()) { if (!flutterEngine.getRenderer().isSoftwareRenderingEnabled()) {
setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled)); setWillNotDraw(!(isAccessibilityEnabled || isTouchExplorationEnabled));
} else { } else {
...@@ -431,9 +557,10 @@ public class XFlutterView extends FrameLayout { ...@@ -431,9 +557,10 @@ public class XFlutterView extends FrameLayout {
* See {@link #detachFromFlutterEngine()} for information on how to detach from a * See {@link #detachFromFlutterEngine()} for information on how to detach from a
* {@link FlutterEngine}. * {@link FlutterEngine}.
*/ */
public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) { public void attachToFlutterEngine(
@NonNull FlutterEngine flutterEngine
Log.d(TAG, "attachToFlutterEngine()"); ) {
Log.d(TAG, "Attaching to a FlutterEngine: " + flutterEngine);
if (isAttachedToFlutterEngine()) { if (isAttachedToFlutterEngine()) {
if (flutterEngine == this.flutterEngine) { if (flutterEngine == this.flutterEngine) {
// We are already attached to this FlutterEngine // We are already attached to this FlutterEngine
...@@ -442,76 +569,74 @@ public class XFlutterView extends FrameLayout { ...@@ -442,76 +569,74 @@ public class XFlutterView extends FrameLayout {
} }
// Detach from a previous FlutterEngine so we can attach to this new one. // Detach from a previous FlutterEngine so we can attach to this new one.
Log.d(TAG, "Currently attached to a different engine. Detaching."); Log.d(TAG, "Currently attached to a different engine. Detaching and then attaching"
+ " to new engine.");
detachFromFlutterEngine(); detachFromFlutterEngine();
} }
this.flutterEngine = flutterEngine; this.flutterEngine = flutterEngine;
// initialize PlatformViewsController
// this.flutterEngine.getPluginRegistry().getPlatformViewsController().attach(getContext(),flutterEngine.getRenderer(),flutterEngine.getDartExecutor());
// Instruct our FlutterRenderer that we are now its designated RenderSurface. // Instruct our FlutterRenderer that we are now its designated RenderSurface.
this.flutterEngine.getRenderer().attachToRenderSurface(renderSurface); FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
didRenderFirstFrame = flutterRenderer.hasRenderedFirstFrame();
flutterRenderer.attachToRenderSurface(renderSurface);
flutterRenderer.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
// Initialize various components that know how to process Android View I/O // Initialize various components that know how to process Android View I/O
// in a way that Flutter understands. // in a way that Flutter understands.
if(textInputPlugin==null){ if(this.textInputPlugin!=null){
textInputPlugin = new XTextInputPlugin( this.textInputPlugin.destroy();
this,
flutterEngine.getTextInputChannel()
);
} }
textInputPlugin.setTextInputMethodHandler();
textInputPlugin.getInputMethodManager().restartInput(this);
androidKeyProcessor = new XAndroidKeyProcessor(
this.flutterEngine.getKeyEventChannel(),
textInputPlugin
);
this.textInputPlugin = new TextInputPlugin(this, this.flutterEngine.getDartExecutor(), this.flutterEngine.getPlatformViewsController());
androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer()); this.textInputPlugin.getInputMethodManager().restartInput(this);
if(accessibilityBridge==null){
accessibilityBridge = new AccessibilityBridge(
this,
flutterEngine.getAccessibilityChannel(),
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
getContext().getContentResolver(),
// TODO(mattcaroll): plumb the platform views controller to the accessibility bridge.
// https://github.com/flutter/flutter/issues/29618
null
);
accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
resetWillNotDraw(
accessibilityBridge.isAccessibilityEnabled(),
accessibilityBridge.isTouchExplorationEnabled()
);
} this.androidKeyProcessor = new AndroidKeyProcessor(
this.flutterEngine.getKeyEventChannel(),
textInputPlugin
);
this.androidTouchProcessor = new AndroidTouchProcessor(this.flutterEngine.getRenderer());
this.accessibilityBridge = new AccessibilityBridge(
this,
flutterEngine.getAccessibilityChannel(),
(AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
getContext().getContentResolver(),
this.flutterEngine.getPlatformViewsController()
);
accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
resetWillNotDraw(
accessibilityBridge.isAccessibilityEnabled(),
accessibilityBridge.isTouchExplorationEnabled()
);
// Connect AccessibilityBridge to the PlatformViewsController within the FlutterEngine.
// This allows platform Views to hook into Flutter's overall accessibility system.
this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);
// Inform the Android framework that it should retrieve a new InputConnection // Inform the Android framework that it should retrieve a new InputConnection
// now that an engine is attached. // now that an engine is attached.
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin // TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
// textInputPlugin.getInputMethodManager().restartInput(this);
// Push View and Context related information from Android to Flutter. // Push View and Context related information from Android to Flutter.
sendUserSettingsToFlutter(); sendUserSettingsToFlutter();
sendLocalesToFlutter(getResources().getConfiguration()); sendLocalesToFlutter(getResources().getConfiguration());
sendViewportMetricsToFlutter(); sendViewportMetricsToFlutter();
}
// Notify engine attachment listeners of the attachment.
public void release(){ for (FlutterView.FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
listener.onFlutterEngineAttachedToFlutterView(flutterEngine);
if(accessibilityBridge!=null){
accessibilityBridge.release();
} }
textInputPlugin.release();
// If the first frame has already been rendered, notify all first frame listeners.
// Do this after all other initialization so that listeners don't inadvertently interact
// with a FlutterView that is only partially attached to a FlutterEngine.
if (didRenderFirstFrame) {
onFirstFrameRenderedListener.onFirstFrameRendered();
}
} }
/** /**
...@@ -525,46 +650,79 @@ public class XFlutterView extends FrameLayout { ...@@ -525,46 +650,79 @@ public class XFlutterView extends FrameLayout {
* {@link FlutterEngine}. * {@link FlutterEngine}.
*/ */
public void detachFromFlutterEngine() { public void detachFromFlutterEngine() {
Log.d(TAG, "detachFromFlutterEngine()"); Log.d(TAG, "Detaching from a FlutterEngine: " + flutterEngine);
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
Log.d(TAG, "Not attached to an engine. Doing nothing."); Log.d(TAG, "Not attached to an engine. Doing nothing.");
return; return;
} }
Log.d(TAG, "Detaching from Flutter Engine");
// detach platformviews in page in case memory leak // Notify engine attachment listeners of the detachment.
// flutterEngine.getPluginRegistry().getPlatformViewsController().detach(); for (FlutterView.FlutterEngineAttachmentListener listener : flutterEngineAttachmentListeners) {
// flutterEngine.getPluginRegistry().getPlatformViewsController().onFlutterViewDestroyed(); listener.onFlutterEngineDetachedFromFlutterView();
}
// Disconnect the FlutterEngine's PlatformViewsController from the AccessibilityBridge.
flutterEngine.getPlatformViewsController().detachAccessibiltyBridge();
// Disconnect and clean up the AccessibilityBridge.
accessibilityBridge.release();
accessibilityBridge = null;
// Inform the Android framework that it should retrieve a new InputConnection // Inform the Android framework that it should retrieve a new InputConnection
// now that the engine is detached. The new InputConnection will be null, which // now that the engine is detached. The new InputConnection will be null, which
// signifies that this View does not process input (until a new engine is attached). // signifies that this View does not process input (until a new engine is attached).
// TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin // TODO(mattcarroll): once this is proven to work, move this line ot TextInputPlugin
// textInputPlugin.getInputMethodManager().restartInput(this); textInputPlugin.getInputMethodManager().restartInput(this);
// textInputPlugin.destroy();
// Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface. // Instruct our FlutterRenderer that we are no longer interested in being its RenderSurface.
// this.textInputPlugin.getInputMethodManager().restartInput(this); FlutterRenderer flutterRenderer = flutterEngine.getRenderer();
flutterEngine.getRenderer().detachFromRenderSurface(); didRenderFirstFrame = false;
flutterRenderer.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
flutterRenderer.detachFromRenderSurface();
flutterEngine = null; flutterEngine = null;
// TODO(mattcarroll): clear the surface when JNI doesn't blow up
// if (isSurfaceAvailableForRendering) {
// Canvas canvas = surfaceHolder.lockCanvas();
// canvas.drawColor(Color.RED);
// surfaceHolder.unlockCanvasAndPost(canvas);
// }
} }
private boolean isAttachedToFlutterEngine() { /**
* Returns true if this {@code FlutterView} is currently attached to a {@link FlutterEngine}.
*/
@VisibleForTesting
public boolean isAttachedToFlutterEngine() {
return flutterEngine != null; return flutterEngine != null;
} }
/**
* Returns the {@link FlutterEngine} to which this {@code FlutterView} is currently attached,
* or null if this {@code FlutterView} is not currently attached to a {@link FlutterEngine}.
*/
@VisibleForTesting
@Nullable
public FlutterEngine getAttachedFlutterEngine() {
return flutterEngine;
}
/**
* attached to/detaches from a {@link FlutterEngine}.
*/
@VisibleForTesting
public void addFlutterEngineAttachmentListener(@NonNull FlutterView.FlutterEngineAttachmentListener listener) {
flutterEngineAttachmentListeners.add(listener);
}
/**
*/
@VisibleForTesting
public void removeFlutterEngineAttachmentListener(@NonNull FlutterView.FlutterEngineAttachmentListener listener) {
flutterEngineAttachmentListeners.remove(listener);
}
/** /**
* Send the current {@link Locale} configuration to Flutter. * Send the current {@link Locale} configuration to Flutter.
* *
* FlutterEngine must be non-null when this method is invoked. * FlutterEngine must be non-null when this method is invoked.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
private void sendLocalesToFlutter(Configuration config) { private void sendLocalesToFlutter(@NonNull Configuration config) {
List<Locale> locales = new ArrayList<>(); List<Locale> locales = new ArrayList<>();
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
LocaleList localeList = config.getLocales(); LocaleList localeList = config.getLocales();
...@@ -576,9 +734,7 @@ public class XFlutterView extends FrameLayout { ...@@ -576,9 +734,7 @@ public class XFlutterView extends FrameLayout {
} else { } else {
locales.add(config.locale); locales.add(config.locale);
} }
if(flutterEngine!=null&&flutterEngine.getLocalizationChannel()!=null){ flutterEngine.getLocalizationChannel().sendLocales(locales);
flutterEngine.getLocalizationChannel().sendLocales(locales);
}
} }
/** /**
...@@ -590,20 +746,17 @@ public class XFlutterView extends FrameLayout { ...@@ -590,20 +746,17 @@ public class XFlutterView extends FrameLayout {
* FlutterEngine must be non-null when this method is invoked. * FlutterEngine must be non-null when this method is invoked.
*/ */
private void sendUserSettingsToFlutter() { private void sendUserSettingsToFlutter() {
if(flutterEngine!=null&&flutterEngine.getSettingsChannel()!=null){ flutterEngine.getSettingsChannel().startMessage()
flutterEngine.getSettingsChannel().startMessage() .setTextScaleFactor(getResources().getConfiguration().fontScale)
.setTextScaleFactor(getResources().getConfiguration().fontScale) .setUse24HourFormat(DateFormat.is24HourFormat(getContext()))
.setUse24HourFormat(DateFormat.is24HourFormat(getContext())) .send();
.send();
}
} }
// TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI // TODO(mattcarroll): consider introducing a system channel for this communication instead of JNI
private void sendViewportMetricsToFlutter() { private void sendViewportMetricsToFlutter() {
Log.d(TAG, "sendViewportMetricsToFlutter()");
if (!isAttachedToFlutterEngine()) { if (!isAttachedToFlutterEngine()) {
Log.w(TAG, "Tried to send viewport metrics from Android to Flutter but this FlutterView was not attached to a FlutterEngine."); Log.w(TAG, "Tried to send viewport metrics from Android to Flutter but this "
+ "FlutterView was not attached to a FlutterEngine.");
return; return;
} }
......
package com.idlefish.flutterboost; package com.idlefish.flutterboost;
import android.content.Context; import android.content.Context;
import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.Selection; import android.text.Selection;
...@@ -13,20 +11,23 @@ import android.view.inputmethod.BaseInputConnection; ...@@ -13,20 +11,23 @@ import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.embedding.engine.systemchannels.TextInputChannel; import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.view.FlutterView; import io.flutter.plugin.editing.TextInputPlugin;
import io.flutter.plugin.platform.PlatformViewsController;
/** /**
* Android implementation of the text input plugin. * Android implementation of the text input plugin.
*/ */
public class XTextInputPlugin { public class XTextInputPlugin {
@NonNull @NonNull
private View mView; private View mView;
@NonNull @NonNull
private final InputMethodManager mImm; private final InputMethodManager mImm;
@NonNull @NonNull
private int mClient = 0; private final TextInputChannel textInputChannel;
@NonNull
private InputTarget inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
@Nullable @Nullable
private TextInputChannel.Configuration configuration; private TextInputChannel.Configuration configuration;
@Nullable @Nullable
...@@ -34,17 +35,27 @@ public class XTextInputPlugin { ...@@ -34,17 +35,27 @@ public class XTextInputPlugin {
private boolean mRestartInputPending; private boolean mRestartInputPending;
@Nullable @Nullable
private InputConnection lastInputConnection; private InputConnection lastInputConnection;
private TextInputChannel textInputChannel; @NonNull
public XTextInputPlugin(View view, TextInputChannel mTextInputChannel) { private PlatformViewsController platformViewsController;
// When true following calls to createInputConnection will return the cached lastInputConnection if the input
// target is a platform view. See the comments on lockPlatformViewInputConnection for more details.
private boolean isInputConnectionLocked;
public XTextInputPlugin(View view,TextInputChannel textInputChannel, PlatformViewsController platformViewsController) {
mView = view; mView = view;
mImm = (InputMethodManager) view.getContext().getSystemService( mImm = (InputMethodManager) view.getContext().getSystemService(
Context.INPUT_METHOD_SERVICE); Context.INPUT_METHOD_SERVICE);
textInputChannel=mTextInputChannel; this.textInputChannel=textInputChannel;
this.platformViewsController = platformViewsController;
// this.platformViewsController.attachTextInputPlugin(this);
} }
public void release(){
mView=null;
}
public void setTextInputMethodHandler(){
public void setTextInputMethodHandler( ){
textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() { textInputChannel.setTextInputMethodHandler(new TextInputChannel.TextInputMethodHandler() {
@Override @Override
public void show() { public void show() {
...@@ -62,8 +73,8 @@ public class XTextInputPlugin { ...@@ -62,8 +73,8 @@ public class XTextInputPlugin {
} }
@Override @Override
public void setPlatformViewClient(int i) { public void setPlatformViewClient(int platformViewId) {
setPlatformViewTextInputClient(platformViewId);
} }
@Override @Override
...@@ -76,9 +87,7 @@ public class XTextInputPlugin { ...@@ -76,9 +87,7 @@ public class XTextInputPlugin {
clearTextInputClient(); clearTextInputClient();
} }
}); });
}
public void release(){
mView=null;
} }
@NonNull @NonNull
...@@ -86,6 +95,40 @@ public class XTextInputPlugin { ...@@ -86,6 +95,40 @@ public class XTextInputPlugin {
return mImm; return mImm;
} }
/***
* Use the current platform view input connection until unlockPlatformViewInputConnection is called.
*
* The current input connection instance is cached and any following call to @{link createInputConnection} returns
* the cached connection until unlockPlatformViewInputConnection is called.
*
* This is a no-op if the current input target isn't a platform view.
*
* This is used to preserve an input connection when moving a platform view from one virtual display to another.
*/
public void lockPlatformViewInputConnection() {
if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) {
isInputConnectionLocked = true;
}
}
/**
* Unlocks the input connection.
*
* See also: @{link lockPlatformViewInputConnection}.
*/
public void unlockPlatformViewInputConnection() {
isInputConnectionLocked = false;
}
/**
* Detaches the text input plugin from the platform views controller.
*
* The TextInputPlugin instance should not be used after calling this.
*/
public void destroy() {
platformViewsController.detachTextInputPlugin();
}
private static int inputTypeFromTextInputType( private static int inputTypeFromTextInputType(
TextInputChannel.InputType type, TextInputChannel.InputType type,
boolean obscureText, boolean obscureText,
...@@ -114,6 +157,8 @@ public class XTextInputPlugin { ...@@ -114,6 +157,8 @@ public class XTextInputPlugin {
textType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS; textType |= InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS;
} else if (type.type == TextInputChannel.TextInputType.URL) { } else if (type.type == TextInputChannel.TextInputType.URL) {
textType |= InputType.TYPE_TEXT_VARIATION_URI; textType |= InputType.TYPE_TEXT_VARIATION_URI;
} else if (type.type == TextInputChannel.TextInputType.VISIBLE_PASSWORD) {
textType |= InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
} }
if (obscureText) { if (obscureText) {
...@@ -136,8 +181,16 @@ public class XTextInputPlugin { ...@@ -136,8 +181,16 @@ public class XTextInputPlugin {
} }
public InputConnection createInputConnection(View view, EditorInfo outAttrs) { public InputConnection createInputConnection(View view, EditorInfo outAttrs) {
if (mClient == 0) { if (inputTarget.type == InputTarget.Type.NO_TARGET) {
lastInputConnection = null; lastInputConnection = null;
return null;
}
if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) {
if (isInputConnectionLocked) {
return lastInputConnection;
}
lastInputConnection = platformViewsController.getPlatformViewById(inputTarget.id).onCreateInputConnection(outAttrs);
return lastInputConnection; return lastInputConnection;
} }
...@@ -166,7 +219,7 @@ public class XTextInputPlugin { ...@@ -166,7 +219,7 @@ public class XTextInputPlugin {
XInputConnectionAdaptor connection = new XInputConnectionAdaptor( XInputConnectionAdaptor connection = new XInputConnectionAdaptor(
view, view,
mClient, inputTarget.id,
textInputChannel, textInputChannel,
mEditable mEditable
); );
...@@ -182,24 +235,55 @@ public class XTextInputPlugin { ...@@ -182,24 +235,55 @@ public class XTextInputPlugin {
return lastInputConnection; return lastInputConnection;
} }
/**
* Clears a platform view text input client if it is the current input target.
*
* This is called when a platform view is disposed to make sure we're not hanging to a stale input
* connection.
*/
public void clearPlatformViewClient(int platformViewId) {
if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW && inputTarget.id == platformViewId) {
inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
hideTextInput(mView);
mImm.restartInput(mView);
mRestartInputPending = false;
}
}
private void showTextInput(View view) { private void showTextInput(View view) {
if(view==null) return;
view.requestFocus(); view.requestFocus();
mImm.showSoftInput(view, 0); mImm.showSoftInput(view, 0);
} }
private void hideTextInput(View view) { private void hideTextInput(View view) {
// Note: a race condition may lead to us hiding the keyboard here just after a platform view has shown it.
// This can only potentially happen when switching focus from a Flutter text field to a platform view's text
// field(by text field here I mean anything that keeps the keyboard open).
// See: https://github.com/flutter/flutter/issues/34169
mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0); mImm.hideSoftInputFromWindow(view.getApplicationWindowToken(), 0);
} }
private void setTextInputClient(int client, TextInputChannel.Configuration configuration) { private void setTextInputClient(int client, TextInputChannel.Configuration configuration) {
mClient = client; inputTarget = new InputTarget(InputTarget.Type.FRAMEWORK_CLIENT, client);
this.configuration = configuration; this.configuration = configuration;
mEditable = Editable.Factory.getInstance().newEditable(""); mEditable = Editable.Factory.getInstance().newEditable("");
// setTextInputClient will be followed by a call to setTextInputEditingState. // setTextInputClient will be followed by a call to setTextInputEditingState.
// Do a restartInput at that time. // Do a restartInput at that time.
mRestartInputPending = true; mRestartInputPending = true;
unlockPlatformViewInputConnection();
}
private void setPlatformViewTextInputClient(int platformViewId) {
// We need to make sure that the Flutter view is focused so that no imm operations get short circuited.
// Not asking for focus here specifically manifested in a but on API 28 devices where the platform view's
// request to show a keyboard was ignored.
mView.requestFocus();
inputTarget = new InputTarget(InputTarget.Type.PLATFORM_VIEW, platformViewId);
mImm.restartInput(mView);
mRestartInputPending = false;
} }
private void applyStateToSelection(TextInputChannel.TextEditState state) { private void applyStateToSelection(TextInputChannel.TextEditState state) {
...@@ -213,7 +297,6 @@ public class XTextInputPlugin { ...@@ -213,7 +297,6 @@ public class XTextInputPlugin {
} }
} }
@RequiresApi(api = Build.VERSION_CODES.CUPCAKE)
private void setTextInputEditingState(View view, TextInputChannel.TextEditState state) { private void setTextInputEditingState(View view, TextInputChannel.TextEditState state) {
if (!mRestartInputPending && state.text.equals(mEditable.toString())) { if (!mRestartInputPending && state.text.equals(mEditable.toString())) {
applyStateToSelection(state); applyStateToSelection(state);
...@@ -230,6 +313,45 @@ public class XTextInputPlugin { ...@@ -230,6 +313,45 @@ public class XTextInputPlugin {
} }
private void clearTextInputClient() { private void clearTextInputClient() {
mClient = 0; if (inputTarget.type == InputTarget.Type.PLATFORM_VIEW) {
// Focus changes in the framework tree have no guarantees on the order focus nodes are notified. A node
// that lost focus may be notified before or after a node that gained focus.
// When moving the focus from a Flutter text field to an AndroidView, it is possible that the Flutter text
// field's focus node will be notified that it lost focus after the AndroidView was notified that it gained
// focus. When this happens the text field will send a clearTextInput command which we ignore.
// By doing this we prevent the framework from clearing a platform view input client(the only way to do so
// is to set a new framework text client). I don't see an obvious use case for "clearing" a platform views
// text input client, and it may be error prone as we don't know how the platform view manages the input
// connection and we probably shouldn't interfere.
// If we ever want to allow the framework to clear a platform view text client we should probably consider
// changing the focus manager such that focus nodes that lost focus are notified before focus nodes that
// gained focus as part of the same focus event.
return;
}
inputTarget = new InputTarget(InputTarget.Type.NO_TARGET, 0);
unlockPlatformViewInputConnection();
}
static private class InputTarget {
enum Type {
NO_TARGET,
// InputConnection is managed by the TextInputPlugin, and events are forwarded to the Flutter framework.
FRAMEWORK_CLIENT,
// InputConnection is managed by an embedded platform view.
PLATFORM_VIEW
}
public InputTarget(@NonNull Type type, int id) {
this.type = type;
this.id = id;
}
@NonNull
Type type;
// The ID of the input target.
//
// For framework clients this is the framework input connection client ID.
// For platform views this is the platform view's ID.
int id;
} }
} }
\ No newline at end of file
...@@ -19,6 +19,7 @@ import java.util.Map; ...@@ -19,6 +19,7 @@ import java.util.Map;
import com.idlefish.flutterboost.NewFlutterBoost; import com.idlefish.flutterboost.NewFlutterBoost;
import com.idlefish.flutterboost.XFlutterView;
import com.idlefish.flutterboost.interfaces.IFlutterViewContainer; import com.idlefish.flutterboost.interfaces.IFlutterViewContainer;
import com.idlefish.flutterboost.interfaces.IOperateSyncer; import com.idlefish.flutterboost.interfaces.IOperateSyncer;
import io.flutter.Log; import io.flutter.Log;
...@@ -44,7 +45,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine ...@@ -44,7 +45,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine
@Nullable @Nullable
private FlutterSplashView flutterSplashView; private FlutterSplashView flutterSplashView;
@Nullable @Nullable
private FlutterView flutterView; private XFlutterView flutterView;
@Nullable @Nullable
private PlatformPlugin platformPlugin; private PlatformPlugin platformPlugin;
...@@ -167,7 +168,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine ...@@ -167,7 +168,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine
mSyncer = NewFlutterBoost.instance().containerManager().generateSyncer(this); mSyncer = NewFlutterBoost.instance().containerManager().generateSyncer(this);
ensureAlive(); ensureAlive();
flutterView = new FlutterView(host.getActivity(), NewFlutterBoost.instance().platform().renderMode(), host.getTransparencyMode()); flutterView = new XFlutterView(host.getActivity(), NewFlutterBoost.instance().platform().renderMode(), host.getTransparencyMode());
flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener); flutterView.addOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
...@@ -229,6 +230,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine ...@@ -229,6 +230,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine
Log.v(TAG, "onPause()"); Log.v(TAG, "onPause()");
ensureAlive(); ensureAlive();
mSyncer.onDisappear();
flutterEngine.getLifecycleChannel().appIsInactive(); flutterEngine.getLifecycleChannel().appIsInactive();
} }
...@@ -236,8 +238,6 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine ...@@ -236,8 +238,6 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContaine
void onStop() { void onStop() {
Log.v(TAG, "onStop()"); Log.v(TAG, "onStop()");
ensureAlive(); ensureAlive();
mSyncer.onDisappear();
flutterEngine.getLifecycleChannel().appIsPaused();
// flutterView.detachFromFlutterEngine(); // flutterView.detachFromFlutterEngine();
} }
......
...@@ -28,7 +28,7 @@ public class FlutterSplashView extends FrameLayout { ...@@ -28,7 +28,7 @@ public class FlutterSplashView extends FrameLayout {
@Nullable @Nullable
private SplashScreen splashScreen; private SplashScreen splashScreen;
@Nullable @Nullable
private FlutterView flutterView; private XFlutterView flutterView;
@Nullable @Nullable
private View splashScreenView; private View splashScreenView;
@Nullable @Nullable
...@@ -112,7 +112,7 @@ public class FlutterSplashView extends FrameLayout { ...@@ -112,7 +112,7 @@ public class FlutterSplashView extends FrameLayout {
* If no {@code splashScreen} is provided, this {@code FlutterSplashView} displays the * If no {@code splashScreen} is provided, this {@code FlutterSplashView} displays the
* given {@code flutterView} on its own. * given {@code flutterView} on its own.
*/ */
public void displayFlutterViewWithSplash(@NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) { public void displayFlutterViewWithSplash(@NonNull XFlutterView flutterView, @Nullable SplashScreen splashScreen) {
// If we were displaying a previous FlutterView, remove it. // If we were displaying a previous FlutterView, remove it.
if (this.flutterView != null) { if (this.flutterView != null) {
this.flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener); this.flutterView.removeOnFirstFrameRenderedListener(onFirstFrameRenderedListener);
......
...@@ -35,7 +35,7 @@ class _MyAppState extends State<MyApp> { ...@@ -35,7 +35,7 @@ class _MyAppState extends State<MyApp> {
return MaterialApp( return MaterialApp(
title: 'Flutter Boost example', title: 'Flutter Boost example',
builder: FlutterBoost.init(postPush: _onRoutePushed), builder: FlutterBoost.init(postPush: _onRoutePushed),
home: FlutterRouteWidget()); home: Container());
} }
void _onRoutePushed( void _onRoutePushed(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment