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

Commit 82c4baba authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Introduce IMM#setExplicitlyEnabledInputMethodSubtypes()

While it still makes sense to require a user consent when enabling
non-system IMEs, programmatically modifying explicitly enabled
subtypes can be allowed for each IME as long as affected subtypes are
their own.  This is why

  IMM#setExplicitlyEnabledInputMethodSubtypes(String, int[])

is introduced in this CL.  Here are use cases for IME developers.

  * If they like, IME developers can take full control of what
    subtypes are explicitly enabled for their IME, with their own
    settings UI instead of relying on
      InputMethodManager#showInputMethodAndSubtypeEnabler()
    and user interactions.  This also enables IME developers to
    backup and restore enabled subtypes in some cloud services.

  * If IME developers find that
      Settings.Secure.ENABLED_INPUT_METHODS
    contains any invalid subtype hashcode, or they plan to migrate
    some legacy subtype hashcode to a new subtype hashcode, they can
    programmatically fix/migrate it without bothering users.

See also the corresponding end-to-end test in CTS [1].

 [1]: I0d46efb1ec98aaf529e2d531205ccd2cc0261492

Fix: 249110888
Test: atest CtsInputMethodTestCases:InputMethodSubtypeTest
Test: atest FrameworksServicesTests:InputMethodUtilsTest#updateEnabledImeStringTest
Change-Id: I836903dbdb6bc1ff3dc4b99710e4e08a65f59417
parent 16d89714
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -53462,6 +53462,7 @@ package android.view.inputmethod {
    method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
    method public void sendAppPrivateCommand(android.view.View, String, android.os.Bundle);
    method @Deprecated public void setAdditionalInputMethodSubtypes(@NonNull String, @NonNull android.view.inputmethod.InputMethodSubtype[]);
    method @Deprecated public void setAdditionalInputMethodSubtypes(@NonNull String, @NonNull android.view.inputmethod.InputMethodSubtype[]);
    method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
    method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype);
    method public void setExplicitlyEnabledInputMethodSubtypes(@NonNull String, @NonNull int[]);
    method @Deprecated public void setInputMethod(android.os.IBinder, String);
    method @Deprecated public void setInputMethod(android.os.IBinder, String);
    method @Deprecated public void setInputMethodAndSubtype(@NonNull android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype);
    method @Deprecated public void setInputMethodAndSubtype(@NonNull android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype);
    method @Deprecated public boolean shouldOfferSwitchingToNextInputMethod(android.os.IBinder);
    method @Deprecated public boolean shouldOfferSwitchingToNextInputMethod(android.os.IBinder);
+10 −0
Original line number Original line Diff line number Diff line
@@ -201,6 +201,16 @@ final class IInputMethodManagerInvoker {
        }
        }
    }
    }


    @AnyThread
    void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imeId,
            @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
        try {
            mTarget.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @AnyThread
    @AnyThread
    int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) {
    int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) {
        try {
        try {
+50 −0
Original line number Original line Diff line number Diff line
@@ -3632,6 +3632,56 @@ public final class InputMethodManager {
        mServiceInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes, UserHandle.myUserId());
        mServiceInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes, UserHandle.myUserId());
    }
    }


    /**
     * Updates the list of explicitly enabled {@link InputMethodSubtype} for a given IME owned by
     * the calling process.
     *
     * <p>By default each IME has no explicitly enabled {@link InputMethodSubtype}.  In this state
     * the system will decide what {@link InputMethodSubtype} should be enabled by using information
     * available at runtime as per-user language settings.  Users can, however, manually pick up one
     * or more {@link InputMethodSubtype} to be enabled on an Activity shown by
     * {@link #showInputMethodAndSubtypeEnabler(String)}. Such a manual change is stored in
     * {@link Settings.Secure#ENABLED_INPUT_METHODS} so that the change can persist across reboots.
     * {@link Settings.Secure#ENABLED_INPUT_METHODS} stores {@link InputMethodSubtype#hashCode()} as
     * the identifier of {@link InputMethodSubtype} for historical reasons.</p>
     *
     * <p>This API provides a safe and managed way for IME developers to modify what
     * {@link InputMethodSubtype} are referenced in {@link Settings.Secure#ENABLED_INPUT_METHODS}
     * for their own IME.  One use case is when IME developers want to use their own Activity for
     * users to pick up {@link InputMethodSubtype}. Another use case is for IME developers to fix up
     * any stale and/or invalid value stored in {@link Settings.Secure#ENABLED_INPUT_METHODS}
     * without bothering users. Passing an empty {@code subtypeHashCodes} is guaranteed to reset
     * the state to default.</p>
     *
     * <h3>To control the return value of {@link InputMethodSubtype#hashCode()}</h3>
     * <p>{@link android.R.attr#subtypeId} and {@link
     * android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder#setSubtypeId(int)} are
     * available for IME developers to control the return value of
     * {@link InputMethodSubtype#hashCode()}. Beware that {@code -1} is not a valid value of
     * {@link InputMethodSubtype#hashCode()} for historical reasons.</p>
     *
     * <h3>Note for Direct Boot support</h3>
     * <p>While IME developers can call this API even before
     * {@link android.os.UserManager#isUserUnlocked()} becomes {@code true}, such a change is
     * volatile thus remains effective only until {@link android.os.UserManager#isUserUnlocked()}
     * becomes {@code true} or the device is rebooted. To make the change persistent IME developers
     * need to call this API again after receiving {@link Intent#ACTION_USER_UNLOCKED}.</p>
     *
     * @param imiId IME ID. The specified IME and the calling process need to belong to the same
     *              package.  Otherwise {@link SecurityException} will be thrown.
     * @param subtypeHashCodes An arrays of {@link InputMethodSubtype#hashCode()} to be explicitly
     *                         enabled. Entries that are found in the specified IME will be silently
     *                         ignored. Pass an empty array to reset the state to default.
     * @throws NullPointerException if {@code subtypeHashCodes} is {@code null}.
     * @throws SecurityException if the specified IME and the calling process do not belong to the
     *                           same package.
     */
    public void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imiId,
            @NonNull int[] subtypeHashCodes) {
        mServiceInvoker.setExplicitlyEnabledInputMethodSubtypes(imiId, subtypeHashCodes,
                UserHandle.myUserId());
    }

    /**
    /**
     * Returns the last used {@link InputMethodSubtype} in system history.
     * Returns the last used {@link InputMethodSubtype} in system history.
     *
     *
+5 −0
Original line number Original line Diff line number Diff line
@@ -97,6 +97,11 @@ interface IInputMethodManager {
    void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes,
    void setAdditionalInputMethodSubtypes(String id, in InputMethodSubtype[] subtypes,
            int userId);
            int userId);


    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
            + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)")
    void setExplicitlyEnabledInputMethodSubtypes(String imeId, in int[] subtypeHashCodes,
            int userId);

    // This is kept due to @UnsupportedAppUsage.
    // This is kept due to @UnsupportedAppUsage.
    // TODO(Bug 113914148): Consider removing this.
    // TODO(Bug 113914148): Consider removing this.
    int getInputMethodWindowVisibleHeight(in IInputMethodClient client);
    int getInputMethodWindowVisibleHeight(in IInputMethodClient client);
+41 −0
Original line number Original line Diff line number Diff line
@@ -75,6 +75,7 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Context;
@@ -4214,6 +4215,46 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        }
        }
    }
    }


    @Override
    public void setExplicitlyEnabledInputMethodSubtypes(String imeId,
            @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
        if (UserHandle.getCallingUserId() != userId) {
            mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
        }
        final int callingUid = Binder.getCallingUid();
        final ComponentName imeComponentName =
                imeId != null ? ComponentName.unflattenFromString(imeId) : null;
        if (imeComponentName == null || !InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager,
                callingUid, imeComponentName.getPackageName())) {
            throw new SecurityException("Calling UID=" + callingUid + " does not belong to imeId="
                    + imeId);
        }
        Objects.requireNonNull(subtypeHashCodes, "subtypeHashCodes must not be null");

        final long ident = Binder.clearCallingIdentity();
        try {
            synchronized (ImfLock.class) {
                final boolean currentUser = (mSettings.getCurrentUserId() == userId);
                final InputMethodSettings settings = currentUser
                        ? mSettings
                        : new InputMethodSettings(mContext, queryMethodMapForUser(userId), userId,
                                !mUserManagerInternal.isUserUnlocked(userId));
                if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) {
                    return;
                }
                if (currentUser) {
                    // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening.
                    if (mSettingsObserver != null) {
                        mSettingsObserver.mLastEnabled = settings.getEnabledInputMethodsStr();
                    }
                    updateInputMethodsFromSettingsLocked(false /* enabledChanged */);
                }
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    }

    /**
    /**
     * This is kept due to {@code @UnsupportedAppUsage} in
     * This is kept due to {@code @UnsupportedAppUsage} in
     * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in
     * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in
Loading