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

Commit 9f141ee8 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Plumb Context#mUser to TextServicesManager to TextServicesManagerService

This is a follow up CL to our previous CL [1], which enabled spell
checker for background users.  In that CL, we assumed that spell
checker user ID can and should always be determined by the calling
user ID.  This assumption is not valid at least for direct-reply
notifications on System UI, because System UI always runs as user 0 no
matter who is the current active user.

In order to allow TextServicesManagerService (TSMS) connect to the
right user for such a special use case, this CL introduces a hidden
parameter "userId" to each IPC so that clients that have
INTERACT_ACROSS_USERS_FULL can override the target user ID when
necessary.

For instance, to interact with user 10's spell checker services, you
can obrain a special instance of TextServicesManager as follows.

  TextServicesManager tsmForUser10 = context
          .createPackageContextAsUser("android", 0, 10 /* userId */)
          .getSystemService(TextServicesManager.class)

If the calling process does not belong to user 10, any operations on
that TextServicesManager will result in SecurityException unless the
calling package needs to have INTERACT_ACROSS_USERS_FULL.

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

 [1]: I06c27ef834203a21cc445dc126602c799384527b
      06a26240

Bug: 123043618
Test: spell checker still works
Change-Id: I31dda3ae8795190d44b0622b8335c34ddbc5dd48
parent 5daa3568
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -439,10 +439,11 @@ final class SystemServiceRegistry {
            }});

        registerService(Context.TEXT_SERVICES_MANAGER_SERVICE, TextServicesManager.class,
                new StaticServiceFetcher<TextServicesManager>() {
                new CachedServiceFetcher<TextServicesManager>() {
            @Override
            public TextServicesManager createService() {
                return TextServicesManager.getInstance();
            public TextServicesManager createService(ContextImpl ctx)
                    throws ServiceNotFoundException {
                return TextServicesManager.createInstance(ctx);
            }});

        registerService(Context.KEYGUARD_SERVICE, KeyguardManager.class,
+4 −9
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.util.Log;

import com.android.internal.textservice.ISpellCheckerSession;
import com.android.internal.textservice.ISpellCheckerSessionListener;
import com.android.internal.textservice.ITextServicesManager;
import com.android.internal.textservice.ITextServicesSessionListener;

import dalvik.system.CloseGuard;
@@ -96,7 +95,7 @@ public class SpellCheckerSession {
    private static final int MSG_ON_GET_SUGGESTION_MULTIPLE_FOR_SENTENCE = 2;

    private final InternalListener mInternalListener;
    private final ITextServicesManager mTextServicesManager;
    private final TextServicesManager mTextServicesManager;
    private final SpellCheckerInfo mSpellCheckerInfo;
    @UnsupportedAppUsage
    private final SpellCheckerSessionListener mSpellCheckerSessionListener;
@@ -124,7 +123,7 @@ public class SpellCheckerSession {
     * @hide
     */
    public SpellCheckerSession(
            SpellCheckerInfo info, ITextServicesManager tsm, SpellCheckerSessionListener listener) {
            SpellCheckerInfo info, TextServicesManager tsm, SpellCheckerSessionListener listener) {
        if (info == null || listener == null || tsm == null) {
            throw new NullPointerException();
        }
@@ -166,12 +165,8 @@ public class SpellCheckerSession {
     */
    public void close() {
        mGuard.close();
        try {
        mSpellCheckerSessionListenerImpl.close();
        mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
        } catch (RemoteException e) {
            // do nothing
        }
    }

    /**
+46 −10
Original line number Diff line number Diff line
@@ -16,16 +16,20 @@

package android.view.textservice;

import android.annotation.NonNull;
import android.annotation.SystemService;
import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.content.Context;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
import android.util.Log;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;

import com.android.internal.textservice.ISpellCheckerSessionListener;
import com.android.internal.textservice.ITextServicesManager;

import java.util.Locale;
@@ -67,17 +71,41 @@ public final class TextServicesManager {
    private static final String TAG = TextServicesManager.class.getSimpleName();
    private static final boolean DBG = false;

    /**
     * @deprecated Do not use. Just kept because of {@link UnsupportedAppUsage} in
     * {@link #getInstance()}.
     */
    @Deprecated
    private static TextServicesManager sInstance;

    private final ITextServicesManager mService;

    private TextServicesManager() throws ServiceNotFoundException {
    @UserIdInt
    private final int mUserId;

    private TextServicesManager(@UserIdInt int userId) throws ServiceNotFoundException {
        mService = ITextServicesManager.Stub.asInterface(
                ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
        mUserId = userId;
    }

    /**
     * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
     * The factory method of {@link TextServicesManager}.
     *
     * @param context {@link Context} from which {@link TextServicesManager} should be instantiated.
     * @return {@link TextServicesManager} that is associated with {@link Context#getUserId()}.
     * @throws ServiceNotFoundException When {@link TextServicesManager} is not available.
     * @hide
     */
    @NonNull
    public static TextServicesManager createInstance(@NonNull Context context)
            throws ServiceNotFoundException {
        return new TextServicesManager(context.getUserId());
    }

    /**
     * @deprecated Do not use. Just kept because of {@link UnsupportedAppUsage} in
     * {@link #getInstance()}.
     * @hide
     */
    @UnsupportedAppUsage
@@ -85,7 +113,7 @@ public final class TextServicesManager {
        synchronized (TextServicesManager.class) {
            if (sInstance == null) {
                try {
                    sInstance = new TextServicesManager();
                    sInstance = new TextServicesManager(UserHandle.myUserId());
                } catch (ServiceNotFoundException e) {
                    throw new IllegalStateException(e);
                }
@@ -136,7 +164,7 @@ public final class TextServicesManager {

        final SpellCheckerInfo sci;
        try {
            sci = mService.getCurrentSpellChecker(null);
            sci = mService.getCurrentSpellChecker(mUserId, null);
        } catch (RemoteException e) {
            return null;
        }
@@ -174,9 +202,9 @@ public final class TextServicesManager {
        if (subtypeInUse == null) {
            return null;
        }
        final SpellCheckerSession session = new SpellCheckerSession(sci, mService, listener);
        final SpellCheckerSession session = new SpellCheckerSession(sci, this, listener);
        try {
            mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
            mService.getSpellCheckerService(mUserId, sci.getId(), subtypeInUse.getLocale(),
                    session.getTextServicesSessionListener(),
                    session.getSpellCheckerSessionListener(), bundle);
        } catch (RemoteException e) {
@@ -191,7 +219,7 @@ public final class TextServicesManager {
    @UnsupportedAppUsage
    public SpellCheckerInfo[] getEnabledSpellCheckers() {
        try {
            final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
            final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers(mUserId);
            if (DBG) {
                Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
            }
@@ -208,7 +236,7 @@ public final class TextServicesManager {
    public SpellCheckerInfo getCurrentSpellChecker() {
        try {
            // Passing null as a locale for ICS
            return mService.getCurrentSpellChecker(null);
            return mService.getCurrentSpellChecker(mUserId, null);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
@@ -221,7 +249,7 @@ public final class TextServicesManager {
    public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
            boolean allowImplicitlySelectedSubtype) {
        try {
            return mService.getCurrentSpellCheckerSubtype(allowImplicitlySelectedSubtype);
            return mService.getCurrentSpellCheckerSubtype(mUserId, allowImplicitlySelectedSubtype);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
@@ -233,7 +261,15 @@ public final class TextServicesManager {
    @UnsupportedAppUsage
    public boolean isSpellCheckerEnabled() {
        try {
            return mService.isSpellCheckerEnabled();
            return mService.isSpellCheckerEnabled(mUserId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
        try {
            mService.finishSpellCheckerService(mUserId, listener);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
+7 −6
Original line number Diff line number Diff line
@@ -29,12 +29,13 @@ import android.view.textservice.SpellCheckerSubtype;
 * @hide
 */
interface ITextServicesManager {
    SpellCheckerInfo getCurrentSpellChecker(String locale);
    SpellCheckerSubtype getCurrentSpellCheckerSubtype(boolean allowImplicitlySelectedSubtype);
    oneway void getSpellCheckerService(String sciId, in String locale,
    SpellCheckerInfo getCurrentSpellChecker(int userId, String locale);
    SpellCheckerSubtype getCurrentSpellCheckerSubtype(int userId,
            boolean allowImplicitlySelectedSubtype);
    oneway void getSpellCheckerService(int userId, String sciId, in String locale,
            in ITextServicesSessionListener tsListener,
            in ISpellCheckerSessionListener scListener, in Bundle bundle);
    oneway void finishSpellCheckerService(in ISpellCheckerSessionListener listener);
    boolean isSpellCheckerEnabled();
    SpellCheckerInfo[] getEnabledSpellCheckers();
    oneway void finishSpellCheckerService(int userId, in ISpellCheckerSessionListener listener);
    boolean isSpellCheckerEnabled(int userId);
    SpellCheckerInfo[] getEnabledSpellCheckers(int userId);
}
+27 −14
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.textservices;

import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -513,8 +515,8 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
    // TODO: Save SpellCheckerService by supported languages. Currently only one spell
    // checker is saved.
    @Override
    public SpellCheckerInfo getCurrentSpellChecker(String locale) {
        int userId = UserHandle.getCallingUserId();
    public SpellCheckerInfo getCurrentSpellChecker(@UserIdInt int userId, String locale) {
        verifyUser(userId);
        synchronized (mLock) {
            final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
            if (tsd == null) return null;
@@ -527,11 +529,12 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
    // TODO: Save SpellCheckerSubtype by supported languages by looking at "locale".
    @Override
    public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
            boolean allowImplicitlySelectedSubtype) {
            @UserIdInt int userId, boolean allowImplicitlySelectedSubtype) {
        verifyUser(userId);

        final int subtypeHashCode;
        final SpellCheckerInfo sci;
        final Locale systemLocale;
        final int userId = UserHandle.getCallingUserId();

        synchronized (mLock) {
            final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
@@ -591,17 +594,17 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
    }

    @Override
    public void getSpellCheckerService(String sciId, String locale,
    public void getSpellCheckerService(@UserIdInt int userId, String sciId, String locale,
            ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
            Bundle bundle) {
        verifyUser(userId);
        if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) {
            Slog.e(TAG, "getSpellCheckerService: Invalid input.");
            return;
        }
        int callingUserId = UserHandle.getCallingUserId();

        synchronized (mLock) {
            final TextServicesData tsd = getDataFromCallingUserIdLocked(callingUserId);
            final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
            if (tsd == null) return;

            HashMap<String, SpellCheckerInfo> spellCheckerMap = tsd.mSpellCheckerMap;
@@ -634,8 +637,8 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
    }

    @Override
    public boolean isSpellCheckerEnabled() {
        int userId = UserHandle.getCallingUserId();
    public boolean isSpellCheckerEnabled(@UserIdInt int userId) {
        verifyUser(userId);

        synchronized (mLock) {
            final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
@@ -671,11 +674,11 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
    }

    @Override
    public SpellCheckerInfo[] getEnabledSpellCheckers() {
        int callingUserId = UserHandle.getCallingUserId();
    public SpellCheckerInfo[] getEnabledSpellCheckers(@UserIdInt int userId) {
        verifyUser(userId);

        synchronized (mLock) {
            final TextServicesData tsd = getDataFromCallingUserIdLocked(callingUserId);
            final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
            if (tsd == null) return null;

            ArrayList<SpellCheckerInfo> spellCheckerList = tsd.mSpellCheckerList;
@@ -691,11 +694,12 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
    }

    @Override
    public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
    public void finishSpellCheckerService(@UserIdInt int userId,
            ISpellCheckerSessionListener listener) {
        if (DBG) {
            Slog.d(TAG, "FinishSpellCheckerService");
        }
        int userId = UserHandle.getCallingUserId();
        verifyUser(userId);

        synchronized (mLock) {
            final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
@@ -716,6 +720,15 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
        }
    }

    private void verifyUser(@UserIdInt int userId) {
        final int callingUserId = UserHandle.getCallingUserId();
        if (userId != callingUserId) {
            mContext.enforceCallingPermission(INTERACT_ACROSS_USERS_FULL,
                    "Cross-user interaction requires INTERACT_ACROSS_USERS_FULL. userId=" + userId
                            + " callingUserId=" + callingUserId);
        }
    }

    private void setCurrentSpellCheckerLocked(@Nullable SpellCheckerInfo sci, TextServicesData tsd) {
        final String sciId = (sci != null) ? sci.getId() : "";
        if (DBG) {