Loading core/java/android/inputmethodservice/InputMethodService.java +7 −0 Original line number Original line Diff line number Diff line Loading @@ -58,6 +58,8 @@ import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING; import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT; import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static com.android.window.flags.Flags.imeBackCallbackLeakPrevention; import android.annotation.CallSuper; import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.DrawableRes; import android.annotation.DurationMillisLong; import android.annotation.DurationMillisLong; Loading Loading @@ -837,7 +839,12 @@ public class InputMethodService extends AbstractInputMethodService { // (if any) can be unregistered using the old dispatcher if {@link #doFinishInput()} // (if any) can be unregistered using the old dispatcher if {@link #doFinishInput()} // is called from {@link #startInput(InputConnection, EditorInfo)} or // is called from {@link #startInput(InputConnection, EditorInfo)} or // {@link #restartInput(InputConnection, EditorInfo)}. // {@link #restartInput(InputConnection, EditorInfo)}. final ImeOnBackInvokedDispatcher oldDispatcher = mImeDispatcher; mImeDispatcher = params.imeDispatcher; mImeDispatcher = params.imeDispatcher; if (imeBackCallbackLeakPrevention() && oldDispatcher != null && mImeDispatcher != null && oldDispatcher != mImeDispatcher) { mImeDispatcher.transferNonSystemCallbacksFrom(oldDispatcher); } if (mWindow != null) { if (mWindow != null) { mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(mImeDispatcher); mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(mImeDispatcher); if (mDecorViewVisible && mShowInputRequested) { if (mDecorViewVisible && mShowInputRequested) { Loading core/java/android/window/ImeOnBackInvokedDispatcher.java +65 −2 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.window; package android.window; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.window.flags.Flags.imeBackCallbackLeakPrevention; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; Loading Loading @@ -62,6 +63,9 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc // the ViewRootImpl holding IME's WindowOnBackInvokedDispatcher is created on. // the ViewRootImpl holding IME's WindowOnBackInvokedDispatcher is created on. private Handler mHandler; private Handler mHandler; private final ArrayDeque<Pair<Integer, Bundle>> mQueuedReceive = new ArrayDeque<>(); private final ArrayDeque<Pair<Integer, Bundle>> mQueuedReceive = new ArrayDeque<>(); private final ArrayDeque<Pair<Integer, OnBackInvokedCallback>> mNonSystemCallbacks = new ArrayDeque<>(); private OnBackInvokedCallback mRegisteredSystemCallback = null; public ImeOnBackInvokedDispatcher(Handler handler) { public ImeOnBackInvokedDispatcher(Handler handler) { mResultReceiver = new ResultReceiver(handler) { mResultReceiver = new ResultReceiver(handler) { @Override @Override Loading @@ -76,6 +80,15 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc }; }; } } /** * Transfers the non-system callbacks from another {@link ImeOnBackInvokedDispatcher}. * This is used when the target IME dispatcher changes. */ public void transferNonSystemCallbacksFrom(@NonNull ImeOnBackInvokedDispatcher other) { mNonSystemCallbacks.addAll(other.mNonSystemCallbacks); other.mNonSystemCallbacks.clear(); } /** Set receiving dispatcher to consume queued receiving events. */ /** Set receiving dispatcher to consume queued receiving events. */ public void updateReceivingDispatcher(@NonNull WindowOnBackInvokedDispatcher dispatcher) { public void updateReceivingDispatcher(@NonNull WindowOnBackInvokedDispatcher dispatcher) { while (!mQueuedReceive.isEmpty()) { while (!mQueuedReceive.isEmpty()) { Loading Loading @@ -106,6 +119,29 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc public void registerOnBackInvokedCallback( public void registerOnBackInvokedCallback( @OnBackInvokedDispatcher.Priority int priority, @OnBackInvokedDispatcher.Priority int priority, @NonNull OnBackInvokedCallback callback) { @NonNull OnBackInvokedCallback callback) { if (!imeBackCallbackLeakPrevention()) { registerOnBackInvokedCallbackAtTarget(priority, callback); return; } if (priority == PRIORITY_SYSTEM || callback instanceof CompatOnBackInvokedCallback) { registerOnBackInvokedCallbackAtTarget(priority, callback); mRegisteredSystemCallback = callback; // Register all pending non-system callbacks. for (Pair<Integer, OnBackInvokedCallback> pair : mNonSystemCallbacks) { registerOnBackInvokedCallbackAtTarget(pair.first, pair.second); } } else { mNonSystemCallbacks.removeIf(pair -> pair.second.equals(callback)); mNonSystemCallbacks.add(new Pair<>(priority, callback)); if (mRegisteredSystemCallback != null) { registerOnBackInvokedCallbackAtTarget(priority, callback); } } } private void registerOnBackInvokedCallbackAtTarget( @OnBackInvokedDispatcher.Priority int priority, @NonNull OnBackInvokedCallback callback) { final Bundle bundle = new Bundle(); final Bundle bundle = new Bundle(); // Always invoke back for ime without checking the window focus. // Always invoke back for ime without checking the window focus. // We use strong reference in the binder wrapper to avoid accidentally GC the callback. // We use strong reference in the binder wrapper to avoid accidentally GC the callback. Loading @@ -120,8 +156,26 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc } } @Override @Override public void unregisterOnBackInvokedCallback( public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { @NonNull OnBackInvokedCallback callback) { if (!imeBackCallbackLeakPrevention()) { unregisterOnBackInvokedCallbackAtTarget(callback); return; } if (callback == mRegisteredSystemCallback) { // Unregister all non-system callbacks first. for (Pair<Integer, OnBackInvokedCallback> nonSystemCallback : mNonSystemCallbacks) { unregisterOnBackInvokedCallbackAtTarget(nonSystemCallback.second); } // Unregister the system callback. unregisterOnBackInvokedCallbackAtTarget(callback); } else { if (mNonSystemCallbacks.removeIf(pair -> pair.second.equals(callback))) { unregisterOnBackInvokedCallbackAtTarget(callback); } } } private void unregisterOnBackInvokedCallbackAtTarget(@NonNull OnBackInvokedCallback callback) { Bundle bundle = new Bundle(); Bundle bundle = new Bundle(); bundle.putInt(RESULT_KEY_ID, callback.hashCode()); bundle.putInt(RESULT_KEY_ID, callback.hashCode()); mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle); mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle); Loading Loading @@ -271,6 +325,15 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc p.println(prefix + " " + callback); p.println(prefix + " " + callback); } } } } if (mNonSystemCallbacks.isEmpty()) { p.println(prefix + "mNonSystemCallbacks: []"); } else { p.println(prefix + "mNonSystemCallbacks:"); for (Pair<Integer, OnBackInvokedCallback> pair : mNonSystemCallbacks) { p.println(prefix + " " + pair.second + " (priority=" + pair.first + ")"); } } p.println(prefix + "mRegisteredSystemCallback: " + mRegisteredSystemCallback); } } @VisibleForTesting(visibility = PACKAGE) @VisibleForTesting(visibility = PACKAGE) Loading core/java/android/window/flags/windowing_frontend.aconfig +10 −0 Original line number Original line Diff line number Diff line Loading @@ -483,3 +483,13 @@ flag { description: "Use seq-id for all client config changes (wearOS)" description: "Use seq-id for all client config changes (wearOS)" bug: "385976595" bug: "385976595" } } flag { name: "ime_back_callback_leak_prevention" namespace: "input_method" description: "Prevents custom ime back callbacks from remaining registered in app process after IME hide" bug: "420870283" metadata { purpose: PURPOSE_BUGFIX } } Loading
core/java/android/inputmethodservice/InputMethodService.java +7 −0 Original line number Original line Diff line number Diff line Loading @@ -58,6 +58,8 @@ import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING; import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT; import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static com.android.window.flags.Flags.imeBackCallbackLeakPrevention; import android.annotation.CallSuper; import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.DrawableRes; import android.annotation.DurationMillisLong; import android.annotation.DurationMillisLong; Loading Loading @@ -837,7 +839,12 @@ public class InputMethodService extends AbstractInputMethodService { // (if any) can be unregistered using the old dispatcher if {@link #doFinishInput()} // (if any) can be unregistered using the old dispatcher if {@link #doFinishInput()} // is called from {@link #startInput(InputConnection, EditorInfo)} or // is called from {@link #startInput(InputConnection, EditorInfo)} or // {@link #restartInput(InputConnection, EditorInfo)}. // {@link #restartInput(InputConnection, EditorInfo)}. final ImeOnBackInvokedDispatcher oldDispatcher = mImeDispatcher; mImeDispatcher = params.imeDispatcher; mImeDispatcher = params.imeDispatcher; if (imeBackCallbackLeakPrevention() && oldDispatcher != null && mImeDispatcher != null && oldDispatcher != mImeDispatcher) { mImeDispatcher.transferNonSystemCallbacksFrom(oldDispatcher); } if (mWindow != null) { if (mWindow != null) { mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(mImeDispatcher); mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(mImeDispatcher); if (mDecorViewVisible && mShowInputRequested) { if (mDecorViewVisible && mShowInputRequested) { Loading
core/java/android/window/ImeOnBackInvokedDispatcher.java +65 −2 Original line number Original line Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.window; package android.window; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.window.flags.Flags.imeBackCallbackLeakPrevention; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; Loading Loading @@ -62,6 +63,9 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc // the ViewRootImpl holding IME's WindowOnBackInvokedDispatcher is created on. // the ViewRootImpl holding IME's WindowOnBackInvokedDispatcher is created on. private Handler mHandler; private Handler mHandler; private final ArrayDeque<Pair<Integer, Bundle>> mQueuedReceive = new ArrayDeque<>(); private final ArrayDeque<Pair<Integer, Bundle>> mQueuedReceive = new ArrayDeque<>(); private final ArrayDeque<Pair<Integer, OnBackInvokedCallback>> mNonSystemCallbacks = new ArrayDeque<>(); private OnBackInvokedCallback mRegisteredSystemCallback = null; public ImeOnBackInvokedDispatcher(Handler handler) { public ImeOnBackInvokedDispatcher(Handler handler) { mResultReceiver = new ResultReceiver(handler) { mResultReceiver = new ResultReceiver(handler) { @Override @Override Loading @@ -76,6 +80,15 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc }; }; } } /** * Transfers the non-system callbacks from another {@link ImeOnBackInvokedDispatcher}. * This is used when the target IME dispatcher changes. */ public void transferNonSystemCallbacksFrom(@NonNull ImeOnBackInvokedDispatcher other) { mNonSystemCallbacks.addAll(other.mNonSystemCallbacks); other.mNonSystemCallbacks.clear(); } /** Set receiving dispatcher to consume queued receiving events. */ /** Set receiving dispatcher to consume queued receiving events. */ public void updateReceivingDispatcher(@NonNull WindowOnBackInvokedDispatcher dispatcher) { public void updateReceivingDispatcher(@NonNull WindowOnBackInvokedDispatcher dispatcher) { while (!mQueuedReceive.isEmpty()) { while (!mQueuedReceive.isEmpty()) { Loading Loading @@ -106,6 +119,29 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc public void registerOnBackInvokedCallback( public void registerOnBackInvokedCallback( @OnBackInvokedDispatcher.Priority int priority, @OnBackInvokedDispatcher.Priority int priority, @NonNull OnBackInvokedCallback callback) { @NonNull OnBackInvokedCallback callback) { if (!imeBackCallbackLeakPrevention()) { registerOnBackInvokedCallbackAtTarget(priority, callback); return; } if (priority == PRIORITY_SYSTEM || callback instanceof CompatOnBackInvokedCallback) { registerOnBackInvokedCallbackAtTarget(priority, callback); mRegisteredSystemCallback = callback; // Register all pending non-system callbacks. for (Pair<Integer, OnBackInvokedCallback> pair : mNonSystemCallbacks) { registerOnBackInvokedCallbackAtTarget(pair.first, pair.second); } } else { mNonSystemCallbacks.removeIf(pair -> pair.second.equals(callback)); mNonSystemCallbacks.add(new Pair<>(priority, callback)); if (mRegisteredSystemCallback != null) { registerOnBackInvokedCallbackAtTarget(priority, callback); } } } private void registerOnBackInvokedCallbackAtTarget( @OnBackInvokedDispatcher.Priority int priority, @NonNull OnBackInvokedCallback callback) { final Bundle bundle = new Bundle(); final Bundle bundle = new Bundle(); // Always invoke back for ime without checking the window focus. // Always invoke back for ime without checking the window focus. // We use strong reference in the binder wrapper to avoid accidentally GC the callback. // We use strong reference in the binder wrapper to avoid accidentally GC the callback. Loading @@ -120,8 +156,26 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc } } @Override @Override public void unregisterOnBackInvokedCallback( public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { @NonNull OnBackInvokedCallback callback) { if (!imeBackCallbackLeakPrevention()) { unregisterOnBackInvokedCallbackAtTarget(callback); return; } if (callback == mRegisteredSystemCallback) { // Unregister all non-system callbacks first. for (Pair<Integer, OnBackInvokedCallback> nonSystemCallback : mNonSystemCallbacks) { unregisterOnBackInvokedCallbackAtTarget(nonSystemCallback.second); } // Unregister the system callback. unregisterOnBackInvokedCallbackAtTarget(callback); } else { if (mNonSystemCallbacks.removeIf(pair -> pair.second.equals(callback))) { unregisterOnBackInvokedCallbackAtTarget(callback); } } } private void unregisterOnBackInvokedCallbackAtTarget(@NonNull OnBackInvokedCallback callback) { Bundle bundle = new Bundle(); Bundle bundle = new Bundle(); bundle.putInt(RESULT_KEY_ID, callback.hashCode()); bundle.putInt(RESULT_KEY_ID, callback.hashCode()); mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle); mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle); Loading Loading @@ -271,6 +325,15 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc p.println(prefix + " " + callback); p.println(prefix + " " + callback); } } } } if (mNonSystemCallbacks.isEmpty()) { p.println(prefix + "mNonSystemCallbacks: []"); } else { p.println(prefix + "mNonSystemCallbacks:"); for (Pair<Integer, OnBackInvokedCallback> pair : mNonSystemCallbacks) { p.println(prefix + " " + pair.second + " (priority=" + pair.first + ")"); } } p.println(prefix + "mRegisteredSystemCallback: " + mRegisteredSystemCallback); } } @VisibleForTesting(visibility = PACKAGE) @VisibleForTesting(visibility = PACKAGE) Loading
core/java/android/window/flags/windowing_frontend.aconfig +10 −0 Original line number Original line Diff line number Diff line Loading @@ -483,3 +483,13 @@ flag { description: "Use seq-id for all client config changes (wearOS)" description: "Use seq-id for all client config changes (wearOS)" bug: "385976595" bug: "385976595" } } flag { name: "ime_back_callback_leak_prevention" namespace: "input_method" description: "Prevents custom ime back callbacks from remaining registered in app process after IME hide" bug: "420870283" metadata { purpose: PURPOSE_BUGFIX } }