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