陈昱 2023-05-05 10:55:05 +08:00 committed by GitHub
parent 24a866fe8e
commit aa95b83a0d
4 changed files with 71 additions and 1 deletions

View File

@ -367,6 +367,14 @@ public class TextInputChannel {
"TextInputClient.performPrivateCommand", Arrays.asList(inputClientId, json));
}
/** Instructs Flutter to execute a "onConnectionClosed" action. */
public void onConnectionClosed(int inputClientId) {
Log.v(TAG, "Sending 'onConnectionClosed' message.");
channel.invokeMethod(
"TextInputClient.onConnectionClosed",
Arrays.asList(inputClientId, "TextInputClient.onConnectionClosed"));
}
/**
* Sets the {@link TextInputMethodHandler} which receives all events and requests that are parsed
* from the underlying platform channel.

View File

@ -14,6 +14,8 @@ import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;
// Loosely based off of
@ -41,7 +43,9 @@ import java.util.List;
// a no-op. When onEnd indicates the end of the animation, the deferred call is
// dispatched again, this time avoiding any flicker since the animation is now
// complete.
@VisibleForTesting
// This class should have "package private" visibility cause it's called from TextInputPlugin.
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
@TargetApi(30)
@RequiresApi(30)
@SuppressLint({"NewApi", "Override"})
@ -54,6 +58,7 @@ class ImeSyncDeferringInsetsCallback {
private WindowInsets lastWindowInsets;
private AnimationCallback animationCallback;
private InsetsListener insetsListener;
private ImeVisibleListener imeVisibleListener;
// True when an animation that matches deferredInsetTypes is active.
//
@ -88,6 +93,11 @@ class ImeSyncDeferringInsetsCallback {
view.setOnApplyWindowInsetsListener(null);
}
// Set a listener to be notified when the IME visibility changes.
void setImeVisibleListener(ImeVisibleListener imeVisibleListener) {
this.imeVisibleListener = imeVisibleListener;
}
@VisibleForTesting
View.OnApplyWindowInsetsListener getInsetsListener() {
return insetsListener;
@ -98,6 +108,11 @@ class ImeSyncDeferringInsetsCallback {
return animationCallback;
}
@VisibleForTesting
ImeVisibleListener getImeVisibleListener() {
return imeVisibleListener;
}
// WindowInsetsAnimation.Callback was introduced in API level 30. The callback
// subclass is separated into an inner class in order to avoid warnings from
// the Android class loader on older platforms.
@ -115,6 +130,20 @@ class ImeSyncDeferringInsetsCallback {
}
}
@NonNull
@Override
public WindowInsetsAnimation.Bounds onStart(
@NonNull WindowInsetsAnimation animation, @NonNull WindowInsetsAnimation.Bounds bounds) {
// Observe changes to software keyboard visibility and notify listener when animation start.
// See https://developer.android.com/develop/ui/views/layout/sw-keyboard.
WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(view);
if (insets != null && imeVisibleListener != null) {
boolean imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime());
imeVisibleListener.onImeVisibleChanged(imeVisible);
}
return super.onStart(animation, bounds);
}
@Override
public WindowInsets onProgress(
WindowInsets insets, List<WindowInsetsAnimation> runningAnimations) {
@ -199,4 +228,9 @@ class ImeSyncDeferringInsetsCallback {
return view.onApplyWindowInsets(windowInsets);
}
}
// Listener for IME visibility changes.
public interface ImeVisibleListener {
void onImeVisibleChanged(boolean visible);
}
}

View File

@ -94,6 +94,17 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
WindowInsets.Type.ime() // Deferred, insets that will animate
);
imeSyncCallback.install();
// When the IME is hidden, we need to notify the framework that close connection.
imeSyncCallback.setImeVisibleListener(
new ImeSyncDeferringInsetsCallback.ImeVisibleListener() {
@Override
public void onImeVisibleChanged(boolean visible) {
if (!visible) {
onConnectionClosed();
}
}
});
}
this.textInputChannel = textInputChannel;
@ -838,4 +849,8 @@ public class TextInputPlugin implements ListenableEditingState.EditingStateWatch
textInputChannel.updateEditingStateWithTag(inputTarget.id, editingValues);
}
// -------- End: Autofill -------
public void onConnectionClosed() {
textInputChannel.onConnectionClosed(inputTarget.id);
}
}

View File

@ -2118,6 +2118,19 @@ public class TextInputPluginTest {
assertEquals(0, viewportMetricsCaptor.getValue().viewInsetTop);
}
@Test
@TargetApi(30)
@Config(sdk = 30)
public void onConnectionClosed_imeInvisible() {
View testView = new View(ctx);
TextInputChannel textInputChannel = spy(new TextInputChannel(mock(DartExecutor.class)));
TextInputPlugin textInputPlugin =
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
ImeSyncDeferringInsetsCallback imeSyncCallback = textInputPlugin.getImeSyncCallback();
imeSyncCallback.getImeVisibleListener().onImeVisibleChanged(false);
verify(textInputChannel, times(1)).onConnectionClosed(anyInt());
}
interface EventHandler {
void sendAppPrivateCommand(View view, String action, Bundle data);
}