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

Commit 7e678779 authored by Yohei Yukawa's avatar Yohei Yukawa Committed by Android (Google) Code Review
Browse files

Merge "Introduce IInputMethodInvoker"

parents da0a2c7c 67b9a09c
Loading
Loading
Loading
Loading
+224 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.inputmethod;

import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Slog;
import android.view.InputChannel;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;

import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.IInputSessionCallback;
import com.android.internal.view.InlineSuggestionsRequestInfo;

import java.util.List;

/**
 * A wrapper class to invoke IPCs defined in {@link IInputMethod}.
 */
final class IInputMethodInvoker {
    private static final String TAG = InputMethodManagerService.TAG;
    private static final boolean DEBUG = InputMethodManagerService.DEBUG;

    @AnyThread
    @Nullable
    static IInputMethodInvoker create(@Nullable IInputMethod inputMethod) {
        if (inputMethod == null) {
            return null;
        }
        if (!Binder.isProxy(inputMethod)) {
            // IInputMethodInvoker must be used only within the system_server and InputMethodService
            // must not be running in the system_server.  Therefore, "inputMethod" must be a Proxy.
            throw new UnsupportedOperationException(inputMethod + " must have been a BinderProxy.");
        }
        return new IInputMethodInvoker(inputMethod);
    }

    /**
     * A simplified version of {@link android.os.Debug#getCaller()}.
     *
     * @return method name of the caller.
     */
    @AnyThread
    private static String getCallerMethodName() {
        final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
        if (callStack.length <= 4) {
            return "<bottom of call stack>";
        }
        return callStack[4].getMethodName();
    }

    @AnyThread
    private static void logRemoteException(@NonNull RemoteException e) {
        if (DEBUG || !(e instanceof DeadObjectException)) {
            Slog.w(TAG, "IPC failed at IInputMethodInvoker#" + getCallerMethodName(), e);
        }
    }

    @AnyThread
    static int getBinderIdentityHashCode(@Nullable IInputMethodInvoker obj) {
        if (obj == null) {
            return 0;
        }

        return System.identityHashCode(obj.mTarget);
    }

    @NonNull
    private final IInputMethod mTarget;

    private IInputMethodInvoker(@NonNull IInputMethod target) {
        mTarget = target;
    }

    @AnyThread
    @NonNull
    IBinder asBinder() {
        return mTarget.asBinder();
    }

    @AnyThread
    void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
            int configChanges, boolean stylusHwSupported) {
        try {
            mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported);
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }

    @AnyThread
    void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
            IInlineSuggestionsRequestCallback cb) {
        try {
            mTarget.onCreateInlineSuggestionsRequest(requestInfo, cb);
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }

    @AnyThread
    void bindInput(InputBinding binding) {
        try {
            mTarget.bindInput(binding);
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }

    @AnyThread
    void unbindInput() {
        try {
            mTarget.unbindInput();
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }

    @AnyThread
    void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
            boolean restarting) {
        try {
            mTarget.startInput(startInputToken, inputContext, attribute, restarting);
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }

    @AnyThread
    void createSession(InputChannel channel, IInputSessionCallback callback) {
        try {
            mTarget.createSession(channel, callback);
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }

    @AnyThread
    void setSessionEnabled(IInputMethodSession session, boolean enabled) {
        try {
            mTarget.setSessionEnabled(session, enabled);
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }

    // TODO(b/192412909): Convert this back to void method
    @AnyThread
    boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
        try {
            mTarget.showSoftInput(showInputToken, flags, resultReceiver);
        } catch (RemoteException e) {
            logRemoteException(e);
            return false;
        }
        return true;
    }

    // TODO(b/192412909): Convert this back to void method
    @AnyThread
    boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
        try {
            mTarget.hideSoftInput(hideInputToken, flags, resultReceiver);
        } catch (RemoteException e) {
            logRemoteException(e);
            return false;
        }
        return true;
    }

    @AnyThread
    void changeInputMethodSubtype(InputMethodSubtype subtype) {
        try {
            mTarget.changeInputMethodSubtype(subtype);
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }

    @AnyThread
    void canStartStylusHandwriting(int requestId) {
        try {
            mTarget.canStartStylusHandwriting(requestId);
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }

    @AnyThread
    void startStylusHandwriting(InputChannel channel, List<MotionEvent> events) {
        try {
            mTarget.startStylusHandwriting(channel, events);
        } catch (RemoteException e) {
            logRemoteException(e);
        }
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -75,7 +75,7 @@ final class InputMethodBindingController {
    @GuardedBy("ImfLock.class") @Nullable private String mCurId;
    @GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId;
    @GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent;
    @GuardedBy("ImfLock.class") @Nullable private IInputMethod mCurMethod;
    @GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod;
    @GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID;
    @GuardedBy("ImfLock.class") private IBinder mCurToken;
    @GuardedBy("ImfLock.class") private int mCurSeq;
@@ -241,7 +241,7 @@ final class InputMethodBindingController {
     */
    @GuardedBy("ImfLock.class")
    @Nullable
    IInputMethod getCurMethod() {
    IInputMethodInvoker getCurMethod() {
        return mCurMethod;
    }

@@ -298,7 +298,7 @@ final class InputMethodBindingController {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected");
            synchronized (ImfLock.class) {
                if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
                    mCurMethod = IInputMethod.Stub.asInterface(service);
                    mCurMethod = IInputMethodInvoker.create(IInputMethod.Stub.asInterface(service));
                    updateCurrentMethodUid();
                    if (mCurToken == null) {
                        Slog.w(TAG, "Service connected without a token!");
+46 −90
Original line number Diff line number Diff line
@@ -167,7 +167,6 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
@@ -329,7 +328,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub

    static class SessionState {
        final ClientState client;
        final IInputMethod method;
        final IInputMethodInvoker method;

        IInputMethodSession session;
        InputChannel channel;
@@ -338,14 +337,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        public String toString() {
            return "SessionState{uid " + client.uid + " pid " + client.pid
                    + " method " + Integer.toHexString(
                            System.identityHashCode(method))
                            IInputMethodInvoker.getBinderIdentityHashCode(method))
                    + " session " + Integer.toHexString(
                            System.identityHashCode(session))
                    + " channel " + channel
                    + "}";
        }

        SessionState(ClientState _client, IInputMethod _method,
        SessionState(ClientState _client, IInputMethodInvoker _method,
                IInputMethodSession _session, InputChannel _channel) {
            client = _client;
            method = _method;
@@ -613,7 +612,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
     */
    @GuardedBy("ImfLock.class")
    @Nullable
    private IInputMethod getCurMethodLocked() {
    private IInputMethodInvoker getCurMethodLocked() {
        return mBindingController.getCurMethod();
    }

@@ -692,8 +691,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub

    /**
     * Internal state snapshot when
     * {@link IInputMethod#startInput(IBinder, IInputContext, EditorInfo, boolean)} is about to be
     * called.
     * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IInputContext, EditorInfo,
     * boolean)} is about to be called.
     *
     * <p>Calling that IPC endpoint basically means that
     * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
@@ -1951,18 +1950,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
            InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) {
        final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
        try {
            IInputMethod curMethod = getCurMethodLocked();
            IInputMethodInvoker curMethod = getCurMethodLocked();
            if (userId == mSettings.getCurrentUserId() && imi != null
                    && imi.isInlineSuggestionsEnabled() && curMethod != null) {
                final IInlineSuggestionsRequestCallback callbackImpl =
                        new InlineSuggestionsRequestCallbackDecorator(callback,
                                imi.getPackageName(), mCurTokenDisplayId, getCurTokenLocked(),
                                this);
                try {
                curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl);
                } catch (RemoteException e) {
                    Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest()", e);
                }
            } else {
                callback.onInlineSuggestionsUnsupported();
            }
@@ -2190,13 +2185,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                            mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
                    if (mBoundToMethod) {
                        mBoundToMethod = false;
                        IInputMethod curMethod = getCurMethodLocked();
                        IInputMethodInvoker curMethod = getCurMethodLocked();
                        if (curMethod != null) {
                            try {
                            curMethod.unbindInput();
                            } catch (RemoteException e) {
                                // There is nothing interesting about the method dying.
                            }
                        }
                    }
                    mCurClient = null;
@@ -2230,13 +2221,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                    + mCurClient.client.asBinder());
            if (mBoundToMethod) {
                mBoundToMethod = false;
                IInputMethod curMethod = getCurMethodLocked();
                IInputMethodInvoker curMethod = getCurMethodLocked();
                if (curMethod != null) {
                    try {
                    curMethod.unbindInput();
                    } catch (RemoteException e) {
                        // There is nothing interesting about the method dying.
                    }
                }
            }

@@ -2285,11 +2272,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
    @NonNull
    InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
        if (!mBoundToMethod) {
            IInputMethod curMethod = getCurMethodLocked();
            try {
                curMethod.bindInput(mCurClient.binding);
            } catch (RemoteException e) {
            }
            getCurMethodLocked().bindInput(mCurClient.binding);
            mBoundToMethod = true;
        }

@@ -2316,11 +2299,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        }

        final SessionState session = mCurClient.curSession;
        try {
        setEnabledSessionLocked(session);
        session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
        } catch (RemoteException e) {
        }

        if (mShowRequested) {
            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
@@ -2514,18 +2494,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
    }

    @GuardedBy("ImfLock.class")
    void initializeImeLocked(@NonNull IInputMethod inputMethod, @NonNull IBinder token,
    void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
            @android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) {
        if (DEBUG) {
            Slog.v(TAG, "Sending attach of token: " + token + " for display: "
                    + mCurTokenDisplayId);
        }
        try {
            inputMethod.initializeInternal(token,
                    new InputMethodPrivilegedOperationsImpl(this, token), configChanges,
                    supportStylusHw);
        } catch (RemoteException e) {
        }
        inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
                configChanges, supportStylusHw);
    }

    @AnyThread
@@ -2535,7 +2511,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
    }

    @BinderThread
    void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) {
    void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session,
            InputChannel channel) {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated");
        try {
            synchronized (ImfLock.class) {
@@ -2545,7 +2522,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                    channel.dispose();
                    return;
                }
                IInputMethod curMethod = getCurMethodLocked();
                IInputMethodInvoker curMethod = getCurMethodLocked();
                if (curMethod != null && method != null
                        && curMethod.asBinder() == method.asBinder()) {
                    if (mCurClient != null) {
@@ -2609,7 +2586,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub

            cs.sessionRequested = true;

            final IInputMethod curMethod = getCurMethodLocked();
            final IInputMethodInvoker curMethod = getCurMethodLocked();
            final IInputSessionCallback.Stub callback = new IInputSessionCallback.Stub() {
                @Override
                public void sessionCreated(IInputMethodSession session) {
@@ -2624,7 +2601,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub

            try {
                curMethod.createSession(clientChannel, callback);
            } catch (RemoteException e) {
            } finally {
                // Dispose the channel because the remote proxy will get its own copy when
                // unparceled.
@@ -3012,14 +2988,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
            }
            if (newSubtype != oldSubtype) {
                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
                IInputMethod curMethod = getCurMethodLocked();
                IInputMethodInvoker curMethod = getCurMethodLocked();
                if (curMethod != null) {
                    try {
                    updateSystemUiLocked(mImeWindowVis, mBackDisposition);
                    curMethod.changeInputMethodSubtype(newSubtype);
                    } catch (RemoteException e) {
                        Slog.w(TAG, "Failed to call changeInputMethodSubtype");
                    }
                }
            }
            return;
@@ -3096,13 +3068,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                    return;
                }
                if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
                final IInputMethod curMethod = getCurMethodLocked();
                final IInputMethodInvoker curMethod = getCurMethodLocked();
                if (curMethod != null) {
                    try {
                    curMethod.canStartStylusHandwriting(++mHwRequestId);
                    } catch (RemoteException e) {
                        Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e);
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(ident);
@@ -3153,21 +3121,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        }

        mBindingController.setCurrentMethodVisible();
        final IInputMethod curMethod = getCurMethodLocked();
        final IInputMethodInvoker curMethod = getCurMethodLocked();
        if (curMethod != null) {
            // create a placeholder token for IMS so that IMS cannot inject windows into client app.
            Binder showInputToken = new Binder();
            mShowRequestWindowMap.put(showInputToken, windowToken);
            final int showFlags = getImeShowFlagsLocked();
            try {
            if (DEBUG) {
                Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
                        + ", " + showFlags + ", " + resultReceiver + ") for reason: "
                        + InputMethodDebug.softInputDisplayReasonToString(reason));
            }
                curMethod.showSoftInput(showInputToken, showFlags, resultReceiver);
            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
            if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
                onShowHideSoftInputRequested(true /* show */, windowToken, reason);
            } catch (RemoteException e) {
            }
            mInputShown = true;
            return true;
@@ -3236,7 +3203,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
        // IMMS#InputShown indicates that the software keyboard is shown.
        // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
        IInputMethod curMethod = getCurMethodLocked();
        IInputMethodInvoker curMethod = getCurMethodLocked();
        final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
                || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
        boolean res;
@@ -3252,10 +3219,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                        + ", " + resultReceiver + ") for reason: "
                        + InputMethodDebug.softInputDisplayReasonToString(reason));
            }
            try {
                curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver);
            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
            if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) {
                onShowHideSoftInputRequested(false /* show */, windowToken, reason);
            } catch (RemoteException e) {
            }
            res = true;
        } else {
@@ -4160,7 +4126,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        }
    }

    /** Called right after {@link IInputMethod#showSoftInput}. */
    /** Called right after {@link com.android.internal.view.IInputMethod#showSoftInput}. */
    @GuardedBy("ImfLock.class")
    private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
            @SoftInputShowHideReason int reason) {
@@ -4215,19 +4181,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
    void setEnabledSessionLocked(SessionState session) {
        if (mEnabledSession != session) {
            if (mEnabledSession != null && mEnabledSession.session != null) {
                try {
                if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
                mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
                } catch (RemoteException e) {
                }
            }
            mEnabledSession = session;
            if (mEnabledSession != null && mEnabledSession.session != null) {
                try {
                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
                mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
                } catch (RemoteException e) {
                }
            }
        }
    }
@@ -4394,12 +4354,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
                return;
            }

            try {
            // TODO: replace null  with actual Channel, MotionEvents
            getCurMethodLocked().startStylusHandwriting(null, null);
            } catch (RemoteException e) {
                Slog.w(TAG, "RemoteException calling startStylusHandwriting(): ", e);
            }
        }
    }

@@ -5132,7 +5088,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
    @BinderThread
    private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
            boolean isCritical) {
        IInputMethod method;
        IInputMethodInvoker method;
        ClientState client;
        ClientState focusedWindowClient;