Skip to content

Commit

Permalink
Close connection on keyboard close (#41500)
Browse files Browse the repository at this point in the history
  • Loading branch information
ksballetba authored May 5, 2023
1 parent 269ce2d commit c97a0de
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,14 @@ public void performPrivateCommand(
"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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
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
Expand Down Expand Up @@ -41,7 +43,9 @@
// 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"})
Expand All @@ -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.
//
Expand Down Expand Up @@ -88,6 +93,11 @@ void remove() {
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;
Expand All @@ -98,6 +108,11 @@ WindowInsetsAnimation.Callback getAnimationCallback() {
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.
Expand All @@ -115,6 +130,20 @@ public void onPrepare(WindowInsetsAnimation animation) {
}
}

@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) {
Expand Down Expand Up @@ -199,4 +228,9 @@ public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
return view.onApplyWindowInsets(windowInsets);
}
}

// Listener for IME visibility changes.
public interface ImeVisibleListener {
void onImeVisibleChanged(boolean visible);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ public TextInputPlugin(
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;
Expand Down Expand Up @@ -838,4 +849,8 @@ public void autofill(@NonNull SparseArray<AutofillValue> values) {
textInputChannel.updateEditingStateWithTag(inputTarget.id, editingValues);
}
// -------- End: Autofill -------

public void onConnectionClosed() {
textInputChannel.onConnectionClosed(inputTarget.id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2118,6 +2118,19 @@ public void ime_windowInsetsSync() {
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);
}
Expand Down

0 comments on commit c97a0de

Please sign in to comment.