Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 35d12221 authored by Johannes Gallmann's avatar Johannes Gallmann
Browse files

Prevent custom IME back callbacks from "leaking" in app process

This CL ensures that no custom IME back callback can remain registered
in the app process when the default (system) IME callback is not
registered. ImeOnBackInvokedDispatcher does not send any registration
requests to the app process while there is no system back callback
registered by the IME process. Additionally, it unregisters any
remaining non-system IME back callbacks when the system callback is
unregistered.

Furthermore, whenever the target ImeOnBackInvokedDispatcher changes,
any registered non-system back callbacks are transferred to the new
dispatcher.

Bug: 420870283
Test: KeyboardVisibilityControlTest
Flag: com.android.window.flags.ime_back_callback_leak_prevention
Change-Id: I2d935efe13c5e7e033ec87933a7c9da2a63f5bae
parent 9c4fe474
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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.ctrlShiftShortcut;

import static com.android.window.flags.Flags.imeBackCallbackLeakPrevention;

import android.annotation.CallSuper;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
@@ -837,7 +839,12 @@ public class InputMethodService extends AbstractInputMethodService {
            // (if any) can be unregistered using the old dispatcher if {@link #doFinishInput()}
            // is called from {@link #startInput(InputConnection, EditorInfo)} or
            // {@link #restartInput(InputConnection, EditorInfo)}.
            final ImeOnBackInvokedDispatcher oldDispatcher = mImeDispatcher;
            mImeDispatcher = params.imeDispatcher;
            if (imeBackCallbackLeakPrevention() && oldDispatcher != null && mImeDispatcher != null
                    && oldDispatcher != mImeDispatcher) {
                mImeDispatcher.transferNonSystemCallbacksFrom(oldDispatcher);
            }
            if (mWindow != null) {
                mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher(mImeDispatcher);
                if (mDecorViewVisible && mShowInputRequested) {
+65 −2
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.window;

import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.window.flags.Flags.imeBackCallbackLeakPrevention;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -62,6 +63,9 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
    // the ViewRootImpl holding IME's WindowOnBackInvokedDispatcher is created on.
    private Handler mHandler;
    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) {
        mResultReceiver = new ResultReceiver(handler) {
            @Override
@@ -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. */
    public void updateReceivingDispatcher(@NonNull WindowOnBackInvokedDispatcher dispatcher) {
        while (!mQueuedReceive.isEmpty()) {
@@ -106,6 +119,29 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
    public void registerOnBackInvokedCallback(
            @OnBackInvokedDispatcher.Priority int priority,
            @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();
        // Always invoke back for ime without checking the window focus.
        // We use strong reference in the binder wrapper to avoid accidentally GC the callback.
@@ -120,8 +156,26 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
    }

    @Override
    public void unregisterOnBackInvokedCallback(
            @NonNull OnBackInvokedCallback callback) {
    public void unregisterOnBackInvokedCallback(@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.putInt(RESULT_KEY_ID, callback.hashCode());
        mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle);
@@ -271,6 +325,15 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
                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)
+10 −0
Original line number Diff line number Diff line
@@ -510,3 +510,13 @@ flag {
    description: "Use seq-id for all client config changes (wearOS)"
    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
    }
}