Introduce IMM#getCurrentInputMethodInfo(userId)
introduce an API for apps to query currently selected aka default input method for current or a given user. Existing API getEnabledInputMethodList() builds list for allenabled IMEs, which is inefficient and redundant for several cases. Bug: 255799034 Test: atest CtsInputMethodServiceHostTestCases:MultiUserTest Change-Id: I60a0f67bf7d261d3a4a733adcb8a022ceac6e1db
This commit is contained in:
@@ -54250,6 +54250,7 @@ package android.view.inputmethod {
|
||||
public final class InputMethodManager {
|
||||
method public void dispatchKeyEventFromInputMethod(@Nullable android.view.View, @NonNull android.view.KeyEvent);
|
||||
method public void displayCompletions(android.view.View, android.view.inputmethod.CompletionInfo[]);
|
||||
method @Nullable public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfo();
|
||||
method @Nullable public android.view.inputmethod.InputMethodSubtype getCurrentInputMethodSubtype();
|
||||
method @NonNull public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodList();
|
||||
method @NonNull public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable android.view.inputmethod.InputMethodInfo, boolean);
|
||||
|
||||
@@ -16307,6 +16307,14 @@ package android.view.displayhash {
|
||||
|
||||
}
|
||||
|
||||
package android.view.inputmethod {
|
||||
|
||||
public final class InputMethodManager {
|
||||
method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfoAsUser(@NonNull android.os.UserHandle);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.view.translation {
|
||||
|
||||
public final class TranslationCapability implements android.os.Parcelable {
|
||||
|
||||
@@ -210,6 +210,21 @@ final class IInputMethodManagerGlobalInvoker {
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@Nullable
|
||||
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
|
||||
static InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
|
||||
final IInputMethodManager service = getService();
|
||||
if (service == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return service.getCurrentInputMethodInfoAsUser(userId);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
@NonNull
|
||||
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
|
||||
|
||||
@@ -331,6 +331,28 @@ public final class InputMethodInfo implements Parcelable {
|
||||
mIsVrOnly = isVrOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public InputMethodInfo(InputMethodInfo source) {
|
||||
mId = source.mId;
|
||||
mSettingsActivityName = source.mSettingsActivityName;
|
||||
mIsDefaultResId = source.mIsDefaultResId;
|
||||
mIsAuxIme = source.mIsAuxIme;
|
||||
mSupportsSwitchingToNextInputMethod = source.mSupportsSwitchingToNextInputMethod;
|
||||
mInlineSuggestionsEnabled = source.mInlineSuggestionsEnabled;
|
||||
mSupportsInlineSuggestionsWithTouchExploration =
|
||||
source.mSupportsInlineSuggestionsWithTouchExploration;
|
||||
mSuppressesSpellChecker = source.mSuppressesSpellChecker;
|
||||
mShowInInputMethodPicker = source.mShowInInputMethodPicker;
|
||||
mIsVrOnly = source.mIsVrOnly;
|
||||
mService = source.mService;
|
||||
mSubtypes = source.mSubtypes;
|
||||
mHandledConfigChanges = source.mHandledConfigChanges;
|
||||
mSupportsStylusHandwriting = source.mSupportsStylusHandwriting;
|
||||
mForceDefault = source.mForceDefault;
|
||||
}
|
||||
|
||||
InputMethodInfo(Parcel source) {
|
||||
mId = source.readString();
|
||||
mSettingsActivityName = source.readString();
|
||||
|
||||
@@ -42,6 +42,8 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresFeature;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.annotation.TestApi;
|
||||
import android.annotation.UiThread;
|
||||
@@ -1594,6 +1596,37 @@ public final class InputMethodManager {
|
||||
return IInputMethodManagerGlobalInvoker.getInputMethodList(userId, directBootAwareness);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link InputMethodInfo} of the currently selected input method (for the process's
|
||||
* user).
|
||||
*
|
||||
* <p>On multi user environment, this API returns a result for the calling process user.</p>
|
||||
*/
|
||||
@Nullable
|
||||
public InputMethodInfo getCurrentInputMethodInfo() {
|
||||
// We intentionally do not use UserHandle.getCallingUserId() here because for system
|
||||
// services InputMethodManagerInternal.getCurrentInputMethodInfoForUser() should be used
|
||||
// instead.
|
||||
return IInputMethodManagerGlobalInvoker.getCurrentInputMethodInfoAsUser(
|
||||
UserHandle.myUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link InputMethodInfo} for currently selected input method for the given user.
|
||||
*
|
||||
* @param user user to query.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL)
|
||||
@Nullable
|
||||
@SystemApi
|
||||
@SuppressLint("UserHandle")
|
||||
public InputMethodInfo getCurrentInputMethodInfoAsUser(@NonNull UserHandle user) {
|
||||
Objects.requireNonNull(user);
|
||||
return IInputMethodManagerGlobalInvoker.getCurrentInputMethodInfoAsUser(
|
||||
user.getIdentifier());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of enabled input methods.
|
||||
*
|
||||
|
||||
@@ -36,6 +36,11 @@ interface IInputMethodManager {
|
||||
void addClient(in IInputMethodClient client, in IRemoteInputConnection inputmethod,
|
||||
int untrustedDisplayId);
|
||||
|
||||
// TODO: Use ParceledListSlice instead
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
|
||||
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
|
||||
InputMethodInfo getCurrentInputMethodInfoAsUser(int userId);
|
||||
|
||||
// TODO: Use ParceledListSlice instead
|
||||
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
|
||||
+ "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
|
||||
|
||||
@@ -1993,6 +1993,19 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
return true;
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
@Nullable
|
||||
@Override
|
||||
public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) {
|
||||
if (UserHandle.getCallingUserId() != userId) {
|
||||
mContext.enforceCallingPermission(
|
||||
Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
|
||||
}
|
||||
synchronized (ImfLock.class) {
|
||||
return queryDefaultInputMethodForUserIdLocked(userId);
|
||||
}
|
||||
}
|
||||
|
||||
@BinderThread
|
||||
@NonNull
|
||||
@Override
|
||||
@@ -5503,6 +5516,53 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
return mCurrentSubtype;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default {@link InputMethodInfo} for the specific userId.
|
||||
* @param userId user ID to query.
|
||||
*/
|
||||
@GuardedBy("ImfLock.class")
|
||||
private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) {
|
||||
final String imeId = mSettings.getSelectedInputMethodForUser(userId);
|
||||
if (TextUtils.isEmpty(imeId)) {
|
||||
Slog.e(TAG, "No default input method found for userId " + userId);
|
||||
return null;
|
||||
}
|
||||
|
||||
InputMethodInfo curInputMethodInfo;
|
||||
if (userId == mSettings.getCurrentUserId()
|
||||
&& (curInputMethodInfo = mMethodMap.get(imeId)) != null) {
|
||||
// clone the InputMethodInfo before returning.
|
||||
return new InputMethodInfo(curInputMethodInfo);
|
||||
}
|
||||
|
||||
final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = new ArrayMap<>();
|
||||
AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
|
||||
Context userAwareContext =
|
||||
mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
|
||||
|
||||
final int flags = PackageManager.GET_META_DATA
|
||||
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
|
||||
| PackageManager.MATCH_DIRECT_BOOT_AUTO;
|
||||
final List<ResolveInfo> services =
|
||||
userAwareContext.getPackageManager().queryIntentServicesAsUser(
|
||||
new Intent(InputMethod.SERVICE_INTERFACE),
|
||||
PackageManager.ResolveInfoFlags.of(flags),
|
||||
userId);
|
||||
for (ResolveInfo ri : services) {
|
||||
final String imeIdResolved = InputMethodInfo.computeId(ri);
|
||||
if (imeId.equals(imeIdResolved)) {
|
||||
try {
|
||||
return new InputMethodInfo(
|
||||
userAwareContext, ri, additionalSubtypeMap.get(imeId));
|
||||
} catch (Exception e) {
|
||||
Slog.wtf(TAG, "Unable to load input method " + imeId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
// we didn't find the InputMethodInfo for imeId. This shouldn't happen.
|
||||
Slog.e(TAG, "Error while locating input method info for imeId: " + imeId);
|
||||
return null;
|
||||
}
|
||||
private ArrayMap<String, InputMethodInfo> queryMethodMapForUser(@UserIdInt int userId) {
|
||||
final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
|
||||
final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
|
||||
|
||||
@@ -330,11 +330,17 @@ final class InputMethodUtils {
|
||||
|
||||
@Nullable
|
||||
private String getString(@NonNull String key, @Nullable String defaultValue) {
|
||||
return getStringForUser(key, defaultValue, mCurrentUserId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getStringForUser(
|
||||
@NonNull String key, @Nullable String defaultValue, @UserIdInt int userId) {
|
||||
final String result;
|
||||
if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
|
||||
result = mCopyOnWriteDataStore.get(key);
|
||||
} else {
|
||||
result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
|
||||
result = Settings.Secure.getStringForUser(mResolver, key, userId);
|
||||
}
|
||||
return result != null ? result : defaultValue;
|
||||
}
|
||||
@@ -741,6 +747,16 @@ final class InputMethodUtils {
|
||||
return imi;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getSelectedInputMethodForUser(@UserIdInt int userId) {
|
||||
final String imi =
|
||||
getStringForUser(Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "getSelectedInputMethodForUserStr: " + imi);
|
||||
}
|
||||
return imi;
|
||||
}
|
||||
|
||||
void putDefaultVoiceInputMethod(String imeId) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
|
||||
|
||||
Reference in New Issue
Block a user