From 6a770ceb140450d4fd1914934bbc60e6c347c1fa Mon Sep 17 00:00:00 2001 From: Shan Huang Date: Mon, 16 May 2022 22:48:13 +0000 Subject: [PATCH] Transfer IME callbacks when app's focused root view changes. Previously when curRootView changes IMM, the back callbacks are not moved to the new focused ViewRootImpl. As a result if IME is up during the focus change, the back callback would fail to unregister when IME tries to hide itself. Test: atest InputMethodServiceLifecycleTest Test: atest CtsInputMethodTestCases:InputMethodServiceTest Test atest CtsInputMethodTestCases:KeyboardVisibilityControlTest Bug: 232660571 Bug: 232331013 Change-Id: Id30e51c74afbcce1f22d87af77e8404b4f0ae7d2 --- .../view/inputmethod/InputMethodManager.java | 1 + .../window/ImeOnBackInvokedDispatcher.java | 58 ++++++++++++++++--- .../window/WindowOnBackInvokedDispatcher.java | 7 ++- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index e2e9a8557793c..a0a3b4f9c5202 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -782,6 +782,7 @@ public final class InputMethodManager { @Override public void setCurrentRootView(ViewRootImpl rootView) { synchronized (mH) { + mImeDispatcher.switchRootView(mCurRootView, rootView); mCurRootView = rootView; } } diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java index 8bdf233b81c8a..5924844aa3b26 100644 --- a/core/java/android/window/ImeOnBackInvokedDispatcher.java +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java @@ -25,8 +25,9 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.Log; +import android.view.ViewRootImpl; -import java.util.HashMap; +import java.util.ArrayList; /** * A {@link OnBackInvokedDispatcher} for IME that forwards {@link OnBackInvokedCallback} @@ -117,7 +118,7 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc } }; - private final HashMap mImeCallbackMap = new HashMap<>(); + private final ArrayList mImeCallbacks = new ArrayList<>(); private void receive( int resultCode, Bundle resultData, @@ -140,39 +141,55 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc int callbackId, @NonNull OnBackInvokedDispatcher receivingDispatcher) { final ImeOnBackInvokedCallback imeCallback = - new ImeOnBackInvokedCallback(iCallback); - mImeCallbackMap.put(callbackId, imeCallback); + new ImeOnBackInvokedCallback(iCallback, callbackId, priority); + mImeCallbacks.add(imeCallback); receivingDispatcher.registerOnBackInvokedCallback(priority, imeCallback); } private void unregisterReceivedCallback( int callbackId, OnBackInvokedDispatcher receivingDispatcher) { - final OnBackInvokedCallback callback = mImeCallbackMap.get(callbackId); + ImeOnBackInvokedCallback callback = null; + for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) { + if (imeCallback.getId() == callbackId) { + callback = imeCallback; + break; + } + } if (callback == null) { Log.e(TAG, "Ime callback not found. Ignoring unregisterReceivedCallback. " + "callbackId: " + callbackId); return; } receivingDispatcher.unregisterOnBackInvokedCallback(callback); + mImeCallbacks.remove(callback); } /** Clears all registered callbacks on the instance. */ public void clear() { // Unregister previously registered callbacks if there's any. if (getReceivingDispatcher() != null) { - for (OnBackInvokedCallback callback : mImeCallbackMap.values()) { + for (ImeOnBackInvokedCallback callback : mImeCallbacks) { getReceivingDispatcher().unregisterOnBackInvokedCallback(callback); } } - mImeCallbackMap.clear(); + mImeCallbacks.clear(); } static class ImeOnBackInvokedCallback implements OnBackInvokedCallback { @NonNull private final IOnBackInvokedCallback mIOnBackInvokedCallback; + /** + * The hashcode of the callback instance in the IME process, used as a unique id to + * identify the callback when it's passed between processes. + */ + private final int mId; + private final int mPriority; - ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback) { + ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback, int id, + @Priority int priority) { mIOnBackInvokedCallback = iCallback; + mId = id; + mPriority = priority; } @Override @@ -186,8 +203,33 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc } } + private int getId() { + return mId; + } + IOnBackInvokedCallback getIOnBackInvokedCallback() { return mIOnBackInvokedCallback; } } + + /** + * Transfers {@link ImeOnBackInvokedCallback}s registered on one {@link ViewRootImpl} to + * another {@link ViewRootImpl} on focus change. + * + * @param previous the previously focused {@link ViewRootImpl}. + * @param current the currently focused {@link ViewRootImpl}. + */ + // TODO(b/232845902): Add CTS to test IME back behavior when there's root view change while + // IME is up. + public void switchRootView(ViewRootImpl previous, ViewRootImpl current) { + for (ImeOnBackInvokedCallback imeCallback : mImeCallbacks) { + if (previous != null) { + previous.getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(imeCallback); + } + if (current != null) { + current.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + imeCallback.mPriority, imeCallback); + } + } + } } diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index edfdbcc1f4f8c..985e2b9630aca 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -157,15 +157,16 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { /** Clears all registered callbacks on the instance. */ public void clear() { + if (mImeDispatcher != null) { + mImeDispatcher.clear(); + mImeDispatcher = null; + } if (!mAllCallbacks.isEmpty()) { // Clear binder references in WM. setTopOnBackInvokedCallback(null); } mAllCallbacks.clear(); mOnBackInvokedCallbacks.clear(); - if (mImeDispatcher != null) { - mImeDispatcher = null; - } } private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {