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
      a878b9500e6b89dce9738179edc27bcd0d736b7e

Bug: 120744418
Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases
Change-Id: Ia7ea944438d69669ccdf9111b34ba400e786a602
diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java
index 28d9fcf..f99afe6 100644
--- a/core/java/android/view/inputmethod/EditorInfo.java
+++ b/core/java/android/view/inputmethod/EditorInfo.java
@@ -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;
@@ -472,6 +476,26 @@
     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
      * impact the following input types:
@@ -527,6 +551,9 @@
         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 @@
             LocaleList.getEmptyLocaleList().writeToParcel(dest, flags);
         }
         dest.writeStringArray(contentMimeTypes);
+        UserHandle.writeToParcel(targetInputMethodUser, dest);
     }
 
     /**
@@ -582,6 +610,7 @@
                     LocaleList hintLocales = LocaleList.CREATOR.createFromParcel(source);
                     res.hintLocales = hintLocales.isEmpty() ? null : hintLocales;
                     res.contentMimeTypes = source.readStringArray();
+                    res.targetInputMethodUser = UserHandle.readFromParcel(source);
                     return res;
                 }
 
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 901cfe3..9fe49b4 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -127,8 +127,10 @@
          */
         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;
         /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 7ff6a2f..7788d26 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2903,7 +2903,23 @@
             @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