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

Commit cad6c745 authored by Johannes Gallmann's avatar Johannes Gallmann Committed by Android (Google) Code Review
Browse files

Merge "Prevent custom IME back callbacks from "leaking" in app process" into main

parents 478e8f32 35d12221
Loading
Loading
Loading
Loading
+7 −0
Original line number Original line 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.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;
@@ -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) {
+65 −2
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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
@@ -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()) {
@@ -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.
@@ -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);
@@ -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)
+10 −0
Original line number Original line Diff line number Diff line
@@ -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
    }
}