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

Commit 095fa371 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Conditionally allow non-primay profiles to use TextServices

With this CL, non-primary profiles become able to use
TextServices (spelling correction services) if and only if
the active spell checker service is the system's one.

Basically this CL just copies the same logic from
InputMethodManagerService that were implemented as
I3bd87b32aec69c3f8d and Id5d4f29017b7ca6844632 with adding
a condition to see if the spell checker is system's one
or not.

Note that this is a tentative solution and can be removed
once TSMS fully supports multiuser.

Also note that this CL heavily relies on the fact that
setting apps isonly running in the current user. This is
because that some risky operations that are exposed from
TSMS are non-public APIs an accessible only from @hide
methods in TextServicesManager, and those @hide methods
are actually used only by the settings app so far.

BUG: 16285536
Change-Id: Iae9045ba5baccd04ed68906e7afb9160677ec4a5
parent 33d9dc46
Loading
Loading
Loading
Loading
+94 −4
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.textservice.ISpellCheckerService;
import com.android.internal.textservice.ISpellCheckerSession;
@@ -28,14 +29,18 @@ import org.xmlpull.v1.XmlPullParserException;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.IUserSwitchObserver;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
@@ -43,6 +48,7 @@ import android.os.IRemoteCallback;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.service.textservice.SpellCheckerService;
import android.text.TextUtils;
@@ -84,6 +90,12 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
    public TextServicesManagerService(Context context) {
        mSystemReady = false;
        mContext = context;

        final IntentFilter broadcastFilter = new IntentFilter();
        broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
        broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
        mContext.registerReceiver(new TextServicesBroadcastReceiver(), broadcastFilter);

        int userId = UserHandle.USER_OWNER;
        try {
            ActivityManagerNative.getDefault().registerUserSwitchObserver(
@@ -119,6 +131,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {

    private void switchUserLocked(int userId) {
        mSettings.setCurrentUserId(userId);
        updateCurrentProfileIds();
        unbindServiceLocked();
        buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap, mSettings);
        SpellCheckerInfo sci = getCurrentSpellChecker(null);
@@ -133,6 +146,16 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
        }
    }

    void updateCurrentProfileIds() {
        List<UserInfo> profiles =
                UserManager.get(mContext).getProfiles(mSettings.getCurrentUserId());
        int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
        for (int i = 0; i < currentProfileIds.length; i++) {
            currentProfileIds[i] = profiles.get(i).id;
        }
        mSettings.setCurrentProfileIds(currentProfileIds);
    }

    private class TextServicesMonitor extends PackageMonitor {
        private boolean isChangingPackagesOfCurrentUser() {
            final int userId = getChangingUserId();
@@ -171,6 +194,19 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
        }
    }

    class TextServicesBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (Intent.ACTION_USER_ADDED.equals(action)
                    || Intent.ACTION_USER_REMOVED.equals(action)) {
                updateCurrentProfileIds();
                return;
            }
            Slog.w(TAG, "Unexpected intent " + intent);
        }
    }

    private static void buildSpellCheckerMapLocked(Context context,
            ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map,
            TextServicesSettings settings) {
@@ -223,7 +259,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
            Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
                    + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
                    + " calling userId = " + userId + ", foreground user id = "
                    + mSettings.getCurrentUserId());
                    + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid());
            try {
                final String[] packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
                for (int i = 0; i < packageNames.length; ++i) {
@@ -237,12 +273,42 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {

        if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
            return true;
        } else {
            Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace());
            return false;
        }

        // Permits current profile to use TSFM as long as the current text service is the system's
        // one. This is a tentative solution and should be replaced with fully functional multiuser
        // support.
        // TODO: Implement multiuser support in TSMS.
        final boolean isCurrentProfile = mSettings.isCurrentProfile(userId);
        if (DBG) {
            Slog.d(TAG, "--- userId = "+ userId + " isCurrentProfile = " + isCurrentProfile);
        }
        if (mSettings.isCurrentProfile(userId)) {
            final SpellCheckerInfo spellCheckerInfo = getCurrentSpellCheckerWithoutVerification();
            if (spellCheckerInfo != null) {
                final ServiceInfo serviceInfo = spellCheckerInfo.getServiceInfo();
                final boolean isSystemSpellChecker =
                        (serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
                if (DBG) {
                    Slog.d(TAG, "--- current spell checker = "+ spellCheckerInfo.getPackageName()
                            + " isSystem = " + isSystemSpellChecker);
                }
                if (isSystemSpellChecker) {
                    return true;
                }
            }
        }

        // Unlike InputMethodManagerService#calledFromValidUser, INTERACT_ACROSS_USERS_FULL isn't
        // taken into account here.  Anyway this method is supposed to be removed once multiuser
        // support is implemented.
        if (DBG) {
            Slog.d(TAG, "--- IPC from userId:" + userId + " is being ignored. \n"
                    + getStackTrace());
        }
        return false;
    }

    private boolean bindCurrentSpellCheckerService(
            Intent service, ServiceConnection conn, int flags) {
        if (service == null || conn == null) {
@@ -292,6 +358,10 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
        if (!calledFromValidUser()) {
            return null;
        }
        return getCurrentSpellCheckerWithoutVerification();
    }

    private SpellCheckerInfo getCurrentSpellCheckerWithoutVerification() {
        synchronized (mSpellCheckerMap) {
            final String curSpellCheckerId = mSettings.getSelectedSpellChecker();
            if (DBG) {
@@ -914,6 +984,10 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
    private static class TextServicesSettings {
        private final ContentResolver mResolver;
        private int mCurrentUserId;
        @GuardedBy("mLock")
        private int[] mCurrentProfileIds = new int[0];
        private Object mLock = new Object();

        public TextServicesSettings(ContentResolver resolver, int userId) {
            mResolver = resolver;
            mCurrentUserId = userId;
@@ -928,6 +1002,22 @@ public class TextServicesManagerService extends ITextServicesManager.Stub {
            mCurrentUserId = userId;
        }

        public void setCurrentProfileIds(int[] currentProfileIds) {
            synchronized (mLock) {
                mCurrentProfileIds = currentProfileIds;
            }
        }

        public boolean isCurrentProfile(int userId) {
            synchronized (mLock) {
                if (userId == mCurrentUserId) return true;
                for (int i = 0; i < mCurrentProfileIds.length; i++) {
                    if (userId == mCurrentProfileIds[i]) return true;
                }
                return false;
            }
        }

        public int getCurrentUserId() {
            return mCurrentUserId;
        }