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

Commit 0f5eade4 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Introduce @hide EditorInfo#targetInputMethodUser

This is a preparation to propagate the expected IME user ID from
direct-reply notification to InputMethodManagerService (IMMS).

When per-profile IME mode [1] is enabled, IMMS basically assumes that
the IME user ID should be determined by calling process's user ID.
This works for most of apps, but does not work for direct-reply hosted
in the System UI process, which always runs as user 0.

With this CL, client apps can explicitly specify the target IME user
ID by using @hide field in EditorInfo.  For instance, to tell IMMS to
connect to user 10's IME, do this:

 @Override
 public InputConnection onCreateInputConnection(EditorInfo info) {
    InputConnection ic = super.onCreateInputConnection(info);
    info.targetInputMethodUser = UserHandle.of(10);  // user 10
    return ic;
 }

The calling process will receive SecurityException if it does not
belong to user 10 and does not have INTERACT_ACROSS_USERS_FULL.

This CL is just a preparation.  There should be no user-visible
behavior change yet.

 [1]: Ied99664d3dc61b97c919b220c601f90b29761b96
      a878b950

Bug: 120744418
Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases
Change-Id: Ia7ea944438d69669ccdf9111b34ba400e786a602
parent d70e1ad7
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -16,11 +16,15 @@

package android.view.inputmethod;

import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;

import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.text.InputType;
import android.text.TextUtils;
import android.util.Printer;
@@ -471,6 +475,26 @@ public class EditorInfo implements InputType, Parcelable {
    @Nullable
    public String[] contentMimeTypes = null;

    /**
     * If not {@code null}, this editor needs to talk to IMEs that run for the specified user, no
     * matter what user ID the calling process has.
     *
     * <p>Note: This field is silently ignored when:</p>
     * <ul>
     *     <li>{@link android.view.inputmethod.InputMethodSystemProperty#PER_PROFILE_IME_ENABLED} is
     *     {@code false}.</li>
     *     <li>{@link android.view.inputmethod.InputMethodSystemProperty#MULTI_CLIENT_IME_ENABLED}
     *     is {@code true}.</li>
     * </ul>
     *
     * <p>Note also that pseudo handles such as {@link UserHandle#ALL} are not supported.</p>
     *
     * @hide
     */
    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
    @Nullable
    public UserHandle targetInputMethodUser = null;

    /**
     * Ensure that the data in this EditorInfo is compatible with an application
     * that was developed against the given target API version.  This can
@@ -527,6 +551,9 @@ public class EditorInfo implements InputType, Parcelable {
        pw.println(prefix + "extras=" + extras);
        pw.println(prefix + "hintLocales=" + hintLocales);
        pw.println(prefix + "contentMimeTypes=" + Arrays.toString(contentMimeTypes));
        if (targetInputMethodUser != null) {
            pw.println(prefix + "targetInputMethodUserId=" + targetInputMethodUser.getIdentifier());
        }
    }

    /**
@@ -556,6 +583,7 @@ public class EditorInfo implements InputType, Parcelable {
            LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
        }
        dest.writeStringArray(contentMimeTypes);
        UserHandle.writeToParcel(targetInputMethodUser, dest);
    }

    /**
@@ -582,6 +610,7 @@ public class EditorInfo implements InputType, Parcelable {
                    LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
                    res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
                    res.contentMimeTypes = source.readStringArray();
                    res.targetInputMethodUser = UserHandle.readFromParcel(source);
                    return res;
                }

+4 −2
Original line number Diff line number Diff line
@@ -127,8 +127,10 @@ public final class InputBindResult implements Parcelable {
         */
        int ERROR_IME_NOT_CONNECTED = 8;
        /**
         * Indicates that the caller is not the foreground user (or does not have
         * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission).
         * Indicates that the caller is not the foreground user, does not have
         * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission, or the user
         * specified in {@link android.view.inputmethod.EditorInfo#targetInputMethodUser} is not
         * running.
         */
        int ERROR_INVALID_USER = 9;
        /**
+17 −1
Original line number Diff line number Diff line
@@ -2903,7 +2903,23 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
            @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
            IInputContext inputContext, @MissingMethodFlags int missingMethods,
            int unverifiedTargetSdkVersion) {
        final int userId = UserHandle.getUserId(Binder.getCallingUid());
        final int callingUserId = UserHandle.getCallingUserId();
        final int userId;
        if (PER_PROFILE_IME_ENABLED && attribute != null && attribute.targetInputMethodUser != null
                && attribute.targetInputMethodUser.getIdentifier() != callingUserId) {
            mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                    "Using EditorInfo.user requires INTERACT_ACROSS_USERS_FULL.");
            userId = attribute.targetInputMethodUser.getIdentifier();
            if (!mUserManagerInternal.isUserRunning(userId)) {
                // There is a chance that we hit here because of race condition.  Let's just return
                // an error code instead of crashing the caller process, which at least has
                // INTERACT_ACROSS_USERS_FULL permission thus is likely to be an important process.
                Slog.e(TAG, "User #" + userId + " is not running.");
                return InputBindResult.INVALID_USER;
            }
        } else {
            userId = callingUserId;
        }
        InputBindResult res = null;
        synchronized (mMethodMap) {
            // Needs to check the validity before clearing calling identity