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

Commit eec552e9 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Allow IMM to forward API calls to IMS

This is one further step towards deprecating 8 IME APIs that were
accidentally defined InputMethodManager (IMM) instead of
InputMethodService (IMS).

With this CL, API calls to those 8 deprecated ones in IMM will be
forwarded to IMS so that we can completely remove corresponding IPC
methods from IInputMethodManager.aidl.  This guarantees that processes
that have no InputMethodService running there become unable to access
IPC methods behind such IME APIs that are intended to be used only
from IMEs.

One tricky thing is that the following 4 public APIs have been allowed
to processes that have WRITE_SECURE_SETTINGS permission, even if such
a process does not have active InputMethodService.

 * InputMethodManager.setInputMethod
 * InputMethodManager.setInputMethodAndSubtype
 * InputMethodManager.switchToLastInputMethod
 * InputMethodManager.switchToNextInputMethod

In general, user mode apps should not have WRITE_SECURE_SETTINGS
permission.  Thus it might be not that difficult for us to simply
deprecate such a special rule.  Bug 114488811 is tracking that effort.

For now, this CL preserves the existing behavior when a null IME token
is specified to those 4 APIs.

Bug: 114418674
Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases
Change-Id: If762714b2003fa6477e1318110f63e13968c1d7e
parent c7ca3684
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -458,6 +458,7 @@ public class InputMethodService extends AbstractInputMethodService {
        public final void initializeInternal(IBinder token,
                IInputMethodPrivilegedOperations privilegedOperations) {
            mPrivOps.set(privilegedOperations);
            mImm.registerInputMethodPrivOps(token, mPrivOps);
            attachToken(token);
        }

@@ -1000,6 +1001,11 @@ public class InputMethodService extends AbstractInputMethodService {
            mSettingsObserver.unregister();
            mSettingsObserver = null;
        }
        if (mToken != null) {
            // This is completely optional, but allows us to show more explicit error messages
            // when IME developers are doing something unsupported.
            mImm.unregisterInputMethodPrivOps(mToken);
        }
    }

    /**
+82 −41
Original line number Diff line number Diff line
@@ -55,6 +55,8 @@ import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillManager;

import com.android.internal.inputmethod.InputMethodPrivilegedOperations;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInputConnectionWrapper;
import com.android.internal.view.IInputContext;
@@ -405,6 +407,9 @@ public final class InputMethodManager {
    final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
    final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);

    private final InputMethodPrivilegedOperationsRegistry mPrivOpsRegistry =
            new InputMethodPrivilegedOperationsRegistry();

    // -----------------------------------------------------------

    static final int MSG_DUMP = 1;
@@ -772,11 +777,7 @@ public final class InputMethodManager {
     */
    @Deprecated
    public void showStatusIcon(IBinder imeToken, String packageName, int iconId) {
        try {
            mService.updateStatusIcon(imeToken, packageName, iconId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        mPrivOpsRegistry.get(imeToken).updateStatusIcon(packageName, iconId);
    }

    /**
@@ -786,11 +787,7 @@ public final class InputMethodManager {
     */
    @Deprecated
    public void hideStatusIcon(IBinder imeToken) {
        try {
            mService.updateStatusIcon(imeToken, null, 0);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        mPrivOpsRegistry.get(imeToken).updateStatusIcon(null, 0);
    }

    /** @hide */
@@ -1831,11 +1828,18 @@ public final class InputMethodManager {
     */
    @Deprecated
    public void setInputMethod(IBinder token, String id) {
        if (token == null) {
            // Note: null token is allowed for callers that have WRITE_SECURE_SETTINGS permission.
            // Thus we cannot always rely on mPrivOpsRegistry unfortunately.
            // TODO(Bug 114488811): Consider deprecating null token rule.
            try {
                mService.setInputMethod(token, id);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            return;
        }
        mPrivOpsRegistry.get(token).setInputMethod(id);
    }

    /**
@@ -1853,11 +1857,18 @@ public final class InputMethodManager {
     */
    @Deprecated
    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
        if (token == null) {
            // Note: null token is allowed for callers that have WRITE_SECURE_SETTINGS permission.
            // Thus we cannot always rely on mPrivOpsRegistry unfortunately.
            // TODO(Bug 114488811): Consider deprecating null token rule.
            try {
                mService.setInputMethodAndSubtype(token, id, subtype);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
            return;
        }
        mPrivOpsRegistry.get(token).setInputMethodAndSubtype(id, subtype);
    }

    /**
@@ -1877,11 +1888,7 @@ public final class InputMethodManager {
     */
    @Deprecated
    public void hideSoftInputFromInputMethod(IBinder token, int flags) {
        try {
            mService.hideMySoftInput(token, flags);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        mPrivOpsRegistry.get(token).hideMySoftInput(flags);
    }

    /**
@@ -1902,11 +1909,7 @@ public final class InputMethodManager {
     */
    @Deprecated
    public void showSoftInputFromInputMethod(IBinder token, int flags) {
        try {
            mService.showMySoftInput(token, flags);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        mPrivOpsRegistry.get(token).showMySoftInput(flags);
    }

    /**
@@ -2285,12 +2288,18 @@ public final class InputMethodManager {
     */
    @Deprecated
    public boolean switchToLastInputMethod(IBinder imeToken) {
        if (imeToken == null) {
            // Note: null token is allowed for callers that have WRITE_SECURE_SETTINGS permission.
            // Thus we cannot always rely on mPrivOpsRegistry unfortunately.
            // TODO(Bug 114488811): Consider deprecating null token rule.
            try {
                return mService.switchToPreviousInputMethod(imeToken);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return mPrivOpsRegistry.get(imeToken).switchToPreviousInputMethod();
    }

    /**
     * Force switch to the next input method and subtype. If there is no IME enabled except
@@ -2307,12 +2316,18 @@ public final class InputMethodManager {
     */
    @Deprecated
    public boolean switchToNextInputMethod(IBinder imeToken, boolean onlyCurrentIme) {
        if (imeToken == null) {
            // Note: null token is allowed for callers that have WRITE_SECURE_SETTINGS permission.
            // Thus we cannot always rely on mPrivOpsRegistry unfortunately.
            // TODO(Bug 114488811): Consider deprecating null token rule.
            try {
                return mService.switchToNextInputMethod(imeToken, onlyCurrentIme);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return mPrivOpsRegistry.get(imeToken).switchToNextInputMethod(onlyCurrentIme);
    }

    /**
     * Returns true if the current IME needs to offer the users ways to switch to a next input
@@ -2330,11 +2345,7 @@ public final class InputMethodManager {
     */
    @Deprecated
    public boolean shouldOfferSwitchingToNextInputMethod(IBinder imeToken) {
        try {
            return mService.shouldOfferSwitchingToNextInputMethod(imeToken);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return mPrivOpsRegistry.get(imeToken).shouldOfferSwitchingToNextInputMethod();
    }

    /**
@@ -2474,4 +2485,34 @@ public final class InputMethodManager {
        sb.append(",temporaryDetach=" + view.isTemporarilyDetached());
        return sb.toString();
    }

    /**
     * Called by {@link InputMethodService} so that API calls to deprecated ones defined in this
     * class can be forwarded to {@link InputMethodPrivilegedOperations}.
     *
     * <p>Note: this method does not hold strong references to {@code token} and {@code ops}. The
     * registry entry will be automatically cleared after {@code token} is garbage collected.</p>
     *
     * @param token IME token that is associated with {@code ops}
     * @param ops {@link InputMethodPrivilegedOperations} that is associated with {@code token}
     * @hide
     */
    public void registerInputMethodPrivOps(IBinder token, InputMethodPrivilegedOperations ops) {
        mPrivOpsRegistry.put(token, ops);
    }

    /**
     * Called from {@link InputMethodService#onDestroy()} to make sure that deprecated IME APIs
     * defined in this class can no longer access to {@link InputMethodPrivilegedOperations}.
     *
     * <p>Note: Calling this method is optional, but at least gives more explict error message in
     * logcat when IME developers are doing something unsupported (e.g. trying to call IME APIs
     * after {@link InputMethodService#onDestroy()}).</p>
     *
     * @param token IME token to be removed.
     * @hide
     */
    public void unregisterInputMethodPrivOps(IBinder token) {
        mPrivOpsRegistry.remove(token);
    }
}
+116 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.internal.inputmethod;

import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;

import com.android.internal.annotations.GuardedBy;

import java.lang.ref.WeakReference;
import java.util.WeakHashMap;

/**
 * A weak-reference-based mapper from IME token to {@link InputMethodPrivilegedOperations} that is
 * used only to support deprecated IME APIs in {@link android.view.inputmethod.InputMethodManager}.
 */
public final class InputMethodPrivilegedOperationsRegistry {
    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private final WeakHashMap<IBinder, WeakReference<InputMethodPrivilegedOperations>>
            mRegistry = new WeakHashMap<>();

    @Nullable
    private static InputMethodPrivilegedOperations sNop;

    @NonNull
    @AnyThread
    private static InputMethodPrivilegedOperations getNopOps() {
        // Strict thread-safety is not necessary because temporarily creating multiple nop instance
        // is basically harmless
        if (sNop == null) {
            sNop = new InputMethodPrivilegedOperations();
        }
        return sNop;
    }

    /**
     * Put a new entry to the registry.
     *
     * <p>Note: {@link InputMethodPrivilegedOperationsRegistry} does not hold strong reference to
     * {@code token} and {@code ops}.  The caller must be responsible for holding strong references
     * to those objects, that is until {@link android.inputmethodservice.InputMethodService} is
     * destroyed.</p>
     *
     * @param token IME token
     * @param ops {@link InputMethodPrivilegedOperations} to be associated with the given IME token
     */
    @AnyThread
    public void put(IBinder token, InputMethodPrivilegedOperations ops) {
        synchronized (mLock) {
            final WeakReference<InputMethodPrivilegedOperations> previousOps =
                    mRegistry.put(token, new WeakReference<>(ops));
            if (previousOps != null) {
                throw new IllegalStateException(previousOps.get() + " is already registered for "
                        + " this token=" + token + " newOps=" + ops);
            }
        }
    }

    /**
     * Get a {@link InputMethodPrivilegedOperations} from the given IME token.  If it is not
     * available, return a fake instance that does nothing except for showing warning messages.
     *
     * @param token IME token
     * @return real {@link InputMethodPrivilegedOperations} object if {@code token} is still valid.
     *         Otherwise a fake instance of {@link InputMethodPrivilegedOperations} hat does nothing
     *         except for showing warning messages
     */
    @NonNull
    @AnyThread
    public InputMethodPrivilegedOperations get(IBinder token) {
        synchronized (mLock) {
            final WeakReference<InputMethodPrivilegedOperations> wrapperRef = mRegistry.get(token);
            if (wrapperRef == null) {
                return getNopOps();
            }
            final InputMethodPrivilegedOperations wrapper = wrapperRef.get();
            if (wrapper == null) {
                return getNopOps();
            }
            return wrapper;
        }
    }

    /**
     * Explicitly removes the specified entry.
     *
     * <p>Note: Calling this method is optional. In general, {@link WeakHashMap} and
     * {@link WeakReference} guarantee that the entry will be removed after specified binder proxies
     * are garbage collected.</p>
     *
     * @param token IME token to be removed.
     */
    @AnyThread
    public void remove(IBinder token) {
        synchronized (mLock) {
            mRegistry.remove(token);
        }
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -63,18 +63,18 @@ interface IInputMethodManager {
            int auxiliarySubtypeMode);
    void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
    boolean isInputMethodPickerShownForTest();
    // TODO(Bug 114488811): this can be removed once we deprecate special null token rule.
    void setInputMethod(in IBinder token, String id);
    // TODO(Bug 114488811): this can be removed once we deprecate special null token rule.
    void setInputMethodAndSubtype(in IBinder token, String id, in InputMethodSubtype subtype);
    void hideMySoftInput(in IBinder token, int flags);
    void showMySoftInput(in IBinder token, int flags);
    void updateStatusIcon(in IBinder token, String packageName, int iconId);
    void registerSuggestionSpansForNotification(in SuggestionSpan[] spans);
    boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index);
    InputMethodSubtype getCurrentInputMethodSubtype();
    boolean setCurrentInputMethodSubtype(in InputMethodSubtype subtype);
    // TODO(Bug 114488811): this can be removed once we deprecate special null token rule.
    boolean switchToPreviousInputMethod(in IBinder token);
    // TODO(Bug 114488811): this can be removed once we deprecate special null token rule.
    boolean switchToNextInputMethod(in IBinder token, boolean onlyCurrentIme);
    boolean shouldOfferSwitchingToNextInputMethod(in IBinder token);
    void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes);
    // This is kept due to @UnsupportedAppUsage.
    // TODO(Bug 113914148): Consider removing this.
+8 −8
Original line number Diff line number Diff line
@@ -2113,8 +2113,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        }
    }

    @Override
    public void updateStatusIcon(IBinder token, String packageName, int iconId) {
    @BinderThread
    private void updateStatusIcon(@NonNull IBinder token, String packageName, int iconId) {
        synchronized (mMethodMap) {
            if (!calledWithValidToken(token)) {
                return;
@@ -3060,8 +3060,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        }
    }

    @Override
    public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
    @BinderThread
    private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token) {
        if (!calledFromValidUser()) {
            return false;
        }
@@ -3220,8 +3220,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        }
    }

    @Override
    public void hideMySoftInput(IBinder token, int flags) {
    @BinderThread
    private void hideMySoftInput(@NonNull IBinder token, int flags) {
        if (!calledFromValidUser()) {
            return;
        }
@@ -3238,8 +3238,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
        }
    }

    @Override
    public void showMySoftInput(IBinder token, int flags) {
    @BinderThread
    private void showMySoftInput(@NonNull IBinder token, int flags) {
        if (!calledFromValidUser()) {
            return;
        }