Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server;
     18 
     19 import static android.view.textservice.TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER;
     20 
     21 import com.android.internal.annotations.GuardedBy;
     22 import com.android.internal.content.PackageMonitor;
     23 import com.android.internal.inputmethod.InputMethodUtils;
     24 import com.android.internal.textservice.ISpellCheckerService;
     25 import com.android.internal.textservice.ISpellCheckerServiceCallback;
     26 import com.android.internal.textservice.ISpellCheckerSession;
     27 import com.android.internal.textservice.ISpellCheckerSessionListener;
     28 import com.android.internal.textservice.ITextServicesManager;
     29 import com.android.internal.textservice.ITextServicesSessionListener;
     30 import com.android.internal.textservice.LazyIntToIntMap;
     31 import com.android.internal.util.DumpUtils;
     32 
     33 import org.xmlpull.v1.XmlPullParserException;
     34 
     35 import android.annotation.NonNull;
     36 import android.annotation.Nullable;
     37 import android.annotation.UserIdInt;
     38 import android.content.ComponentName;
     39 import android.content.ContentResolver;
     40 import android.content.Context;
     41 import android.content.Intent;
     42 import android.content.ServiceConnection;
     43 import android.content.pm.ApplicationInfo;
     44 import android.content.pm.PackageManager;
     45 import android.content.pm.ResolveInfo;
     46 import android.content.pm.ServiceInfo;
     47 import android.content.pm.UserInfo;
     48 import android.os.Binder;
     49 import android.os.Bundle;
     50 import android.os.IBinder;
     51 import android.os.RemoteCallbackList;
     52 import android.os.RemoteException;
     53 import android.os.UserHandle;
     54 import android.os.UserManager;
     55 import android.provider.Settings;
     56 import android.service.textservice.SpellCheckerService;
     57 import android.text.TextUtils;
     58 import android.util.Slog;
     59 import android.util.SparseArray;
     60 import android.view.inputmethod.InputMethodManager;
     61 import android.view.inputmethod.InputMethodSubtype;
     62 import android.view.textservice.SpellCheckerInfo;
     63 import android.view.textservice.SpellCheckerSubtype;
     64 
     65 import java.io.FileDescriptor;
     66 import java.io.IOException;
     67 import java.io.PrintWriter;
     68 import java.util.Arrays;
     69 import java.util.ArrayList;
     70 import java.util.HashMap;
     71 import java.util.List;
     72 import java.util.Locale;
     73 import java.util.Map;
     74 import java.util.function.Predicate;
     75 
     76 public class TextServicesManagerService extends ITextServicesManager.Stub {
     77     private static final String TAG = TextServicesManagerService.class.getSimpleName();
     78     private static final boolean DBG = false;
     79 
     80     private final Context mContext;
     81     private final TextServicesMonitor mMonitor;
     82     private final SparseArray<TextServicesData> mUserData = new SparseArray<>();
     83     @NonNull
     84     private final UserManager mUserManager;
     85     private final Object mLock = new Object();
     86 
     87     @NonNull
     88     @GuardedBy("mLock")
     89     private final LazyIntToIntMap mSpellCheckerOwnerUserIdMap;
     90 
     91     private static class TextServicesData {
     92         @UserIdInt
     93         private final int mUserId;
     94         private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap;
     95         private final ArrayList<SpellCheckerInfo> mSpellCheckerList;
     96         private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups;
     97         private final Context mContext;
     98         private final ContentResolver mResolver;
     99         public int mUpdateCount = 0;
    100 
    101         public TextServicesData(@UserIdInt int userId, @NonNull Context context) {
    102             mUserId = userId;
    103             mSpellCheckerMap = new HashMap<>();
    104             mSpellCheckerList = new ArrayList<>();
    105             mSpellCheckerBindGroups = new HashMap<>();
    106             mContext = context;
    107             mResolver = context.getContentResolver();
    108         }
    109 
    110         private void putString(final String key, final String str) {
    111             Settings.Secure.putStringForUser(mResolver, key, str, mUserId);
    112         }
    113 
    114         @Nullable
    115         private String getString(@NonNull final String key, @Nullable final String defaultValue) {
    116             final String result;
    117             result = Settings.Secure.getStringForUser(mResolver, key, mUserId);
    118             return result != null ? result : defaultValue;
    119         }
    120 
    121         private void putInt(final String key, final int value) {
    122             Settings.Secure.putIntForUser(mResolver, key, value, mUserId);
    123         }
    124 
    125         private int getInt(final String key, final int defaultValue) {
    126             return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mUserId);
    127         }
    128 
    129         private boolean getBoolean(final String key, final boolean defaultValue) {
    130             return getInt(key, defaultValue ? 1 : 0) == 1;
    131         }
    132 
    133         private void putSelectedSpellChecker(@Nullable String sciId) {
    134             putString(Settings.Secure.SELECTED_SPELL_CHECKER, sciId);
    135         }
    136 
    137         private void putSelectedSpellCheckerSubtype(int hashCode) {
    138             putInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, hashCode);
    139         }
    140 
    141         @NonNull
    142         private String getSelectedSpellChecker() {
    143             return getString(Settings.Secure.SELECTED_SPELL_CHECKER, "");
    144         }
    145 
    146         public int getSelectedSpellCheckerSubtype(final int defaultValue) {
    147             return getInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, defaultValue);
    148         }
    149 
    150         public boolean isSpellCheckerEnabled() {
    151             return getBoolean(Settings.Secure.SPELL_CHECKER_ENABLED, true);
    152         }
    153 
    154         @Nullable
    155         public SpellCheckerInfo getCurrentSpellChecker() {
    156             final String curSpellCheckerId = getSelectedSpellChecker();
    157             if (TextUtils.isEmpty(curSpellCheckerId)) {
    158                 return null;
    159             }
    160             return mSpellCheckerMap.get(curSpellCheckerId);
    161         }
    162 
    163         public void setCurrentSpellChecker(@Nullable SpellCheckerInfo sci) {
    164             if (sci != null) {
    165                 putSelectedSpellChecker(sci.getId());
    166             } else {
    167                 putSelectedSpellChecker("");
    168             }
    169             putSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE);
    170         }
    171 
    172         private void initializeTextServicesData() {
    173             if (DBG) {
    174                 Slog.d(TAG, "initializeTextServicesData for user: " + mUserId);
    175             }
    176             mSpellCheckerList.clear();
    177             mSpellCheckerMap.clear();
    178             mUpdateCount++;
    179             final PackageManager pm = mContext.getPackageManager();
    180             // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the
    181             // default behavior of PackageManager is exactly what we want.  It by default picks up
    182             // appropriate services depending on the unlock state for the specified user.
    183             final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
    184                     new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA,
    185                     mUserId);
    186             final int N = services.size();
    187             for (int i = 0; i < N; ++i) {
    188                 final ResolveInfo ri = services.get(i);
    189                 final ServiceInfo si = ri.serviceInfo;
    190                 final ComponentName compName = new ComponentName(si.packageName, si.name);
    191                 if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
    192                     Slog.w(TAG, "Skipping text service " + compName
    193                             + ": it does not require the permission "
    194                             + android.Manifest.permission.BIND_TEXT_SERVICE);
    195                     continue;
    196                 }
    197                 if (DBG) Slog.d(TAG, "Add: " + compName + " for user: " + mUserId);
    198                 try {
    199                     final SpellCheckerInfo sci = new SpellCheckerInfo(mContext, ri);
    200                     if (sci.getSubtypeCount() <= 0) {
    201                         Slog.w(TAG, "Skipping text service " + compName
    202                                 + ": it does not contain subtypes.");
    203                         continue;
    204                     }
    205                     mSpellCheckerList.add(sci);
    206                     mSpellCheckerMap.put(sci.getId(), sci);
    207                 } catch (XmlPullParserException e) {
    208                     Slog.w(TAG, "Unable to load the spell checker " + compName, e);
    209                 } catch (IOException e) {
    210                     Slog.w(TAG, "Unable to load the spell checker " + compName, e);
    211                 }
    212             }
    213             if (DBG) {
    214                 Slog.d(TAG, "initializeSpellCheckerMap: " + mSpellCheckerList.size() + ","
    215                         + mSpellCheckerMap.size());
    216             }
    217         }
    218 
    219         private void dump(PrintWriter pw) {
    220             int spellCheckerIndex = 0;
    221             pw.println("  User #" + mUserId);
    222             pw.println("  Spell Checkers:");
    223             pw.println("  Spell Checkers: " +  "mUpdateCount=" + mUpdateCount);
    224             for (final SpellCheckerInfo info : mSpellCheckerMap.values()) {
    225                 pw.println("  Spell Checker #" + spellCheckerIndex);
    226                 info.dump(pw, "    ");
    227                 ++spellCheckerIndex;
    228             }
    229 
    230             pw.println("");
    231             pw.println("  Spell Checker Bind Groups:");
    232             HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = mSpellCheckerBindGroups;
    233             for (final Map.Entry<String, SpellCheckerBindGroup> ent
    234                     : spellCheckerBindGroups.entrySet()) {
    235                 final SpellCheckerBindGroup grp = ent.getValue();
    236                 pw.println("    " + ent.getKey() + " " + grp + ":");
    237                 pw.println("      " + "mInternalConnection=" + grp.mInternalConnection);
    238                 pw.println("      " + "mSpellChecker=" + grp.mSpellChecker);
    239                 pw.println("      " + "mUnbindCalled=" + grp.mUnbindCalled);
    240                 pw.println("      " + "mConnected=" + grp.mConnected);
    241                 final int numPendingSessionRequests = grp.mPendingSessionRequests.size();
    242                 for (int j = 0; j < numPendingSessionRequests; j++) {
    243                     final SessionRequest req = grp.mPendingSessionRequests.get(j);
    244                     pw.println("      " + "Pending Request #" + j + ":");
    245                     pw.println("        " + "mTsListener=" + req.mTsListener);
    246                     pw.println("        " + "mScListener=" + req.mScListener);
    247                     pw.println(
    248                             "        " + "mScLocale=" + req.mLocale + " mUid=" + req.mUid);
    249                 }
    250                 final int numOnGoingSessionRequests = grp.mOnGoingSessionRequests.size();
    251                 for (int j = 0; j < numOnGoingSessionRequests; j++) {
    252                     final SessionRequest req = grp.mOnGoingSessionRequests.get(j);
    253                     pw.println("      " + "On going Request #" + j + ":");
    254                     ++j;
    255                     pw.println("        " + "mTsListener=" + req.mTsListener);
    256                     pw.println("        " + "mScListener=" + req.mScListener);
    257                     pw.println(
    258                             "        " + "mScLocale=" + req.mLocale + " mUid=" + req.mUid);
    259                 }
    260                 final int N = grp.mListeners.getRegisteredCallbackCount();
    261                 for (int j = 0; j < N; j++) {
    262                     final ISpellCheckerSessionListener mScListener =
    263                             grp.mListeners.getRegisteredCallbackItem(j);
    264                     pw.println("      " + "Listener #" + j + ":");
    265                     pw.println("        " + "mScListener=" + mScListener);
    266                     pw.println("        " + "mGroup=" + grp);
    267                 }
    268             }
    269         }
    270     }
    271 
    272     public static final class Lifecycle extends SystemService {
    273         private TextServicesManagerService mService;
    274 
    275         public Lifecycle(Context context) {
    276             super(context);
    277             mService = new TextServicesManagerService(context);
    278         }
    279 
    280         @Override
    281         public void onStart() {
    282             publishBinderService(Context.TEXT_SERVICES_MANAGER_SERVICE, mService);
    283         }
    284 
    285         @Override
    286         public void onStopUser(@UserIdInt int userHandle) {
    287             if (DBG) {
    288                 Slog.d(TAG, "onStopUser userId: " + userHandle);
    289             }
    290             mService.onStopUser(userHandle);
    291         }
    292 
    293         @Override
    294         public void onUnlockUser(@UserIdInt int userHandle) {
    295             if(DBG) {
    296                 Slog.d(TAG, "onUnlockUser userId: " + userHandle);
    297             }
    298             // Called on the system server's main looper thread.
    299             // TODO: Dispatch this to a worker thread as needed.
    300             mService.onUnlockUser(userHandle);
    301         }
    302     }
    303 
    304     void onStopUser(@UserIdInt int userId) {
    305         synchronized (mLock) {
    306             // Clear user ID mapping table.
    307             mSpellCheckerOwnerUserIdMap.delete(userId);
    308 
    309             // Clean per-user data
    310             TextServicesData tsd = mUserData.get(userId);
    311             if (tsd == null) return;
    312 
    313             unbindServiceLocked(tsd);  // Remove bind groups first
    314             mUserData.remove(userId);  // This needs to be done after bind groups are all removed
    315         }
    316     }
    317 
    318     void onUnlockUser(@UserIdInt int userId) {
    319         synchronized (mLock) {
    320             // Initialize internal state for the given user
    321             initializeInternalStateLocked(userId);
    322         }
    323     }
    324 
    325     public TextServicesManagerService(Context context) {
    326         mContext = context;
    327         mUserManager = mContext.getSystemService(UserManager.class);
    328         mSpellCheckerOwnerUserIdMap = new LazyIntToIntMap(callingUserId -> {
    329             if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
    330                 final long token = Binder.clearCallingIdentity();
    331                 try {
    332                     final UserInfo parent = mUserManager.getProfileParent(callingUserId);
    333                     return (parent != null) ? parent.id : callingUserId;
    334                 } finally {
    335                     Binder.restoreCallingIdentity(token);
    336                 }
    337             } else {
    338                 return callingUserId;
    339             }
    340         });
    341 
    342         mMonitor = new TextServicesMonitor();
    343         mMonitor.register(context, null, UserHandle.ALL, true);
    344     }
    345 
    346     private void initializeInternalStateLocked(@UserIdInt int userId) {
    347         // When DISABLE_PER_PROFILE_SPELL_CHECKER is true, we make sure here that work profile users
    348         // will never have non-null TextServicesData for their user ID.
    349         if (DISABLE_PER_PROFILE_SPELL_CHECKER
    350                 && userId != mSpellCheckerOwnerUserIdMap.get(userId)) {
    351             return;
    352         }
    353 
    354         TextServicesData tsd = mUserData.get(userId);
    355         if (tsd == null) {
    356             tsd = new TextServicesData(userId, mContext);
    357             mUserData.put(userId, tsd);
    358         }
    359 
    360         tsd.initializeTextServicesData();
    361         SpellCheckerInfo sci = tsd.getCurrentSpellChecker();
    362         if (sci == null) {
    363             sci = findAvailSystemSpellCheckerLocked(null, tsd);
    364             // Set the current spell checker if there is one or more system spell checkers
    365             // available. In this case, "sci" is the first one in the available spell
    366             // checkers.
    367             setCurrentSpellCheckerLocked(sci, tsd);
    368         }
    369     }
    370 
    371     private final class TextServicesMonitor extends PackageMonitor {
    372         @Override
    373         public void onSomePackagesChanged() {
    374             int userId = getChangingUserId();
    375             if(DBG) {
    376                 Slog.d(TAG, "onSomePackagesChanged: " + userId);
    377             }
    378 
    379             synchronized (mLock) {
    380                 TextServicesData tsd = mUserData.get(userId);
    381                 if (tsd == null) return;
    382 
    383                 // TODO: Update for each locale
    384                 SpellCheckerInfo sci = tsd.getCurrentSpellChecker();
    385                 tsd.initializeTextServicesData();
    386                 // If spell checker is disabled, just return. The user should explicitly
    387                 // enable the spell checker.
    388                 if (!tsd.isSpellCheckerEnabled()) return;
    389 
    390                 if (sci == null) {
    391                     sci = findAvailSystemSpellCheckerLocked(null, tsd);
    392                     // Set the current spell checker if there is one or more system spell checkers
    393                     // available. In this case, "sci" is the first one in the available spell
    394                     // checkers.
    395                     setCurrentSpellCheckerLocked(sci, tsd);
    396                 } else {
    397                     final String packageName = sci.getPackageName();
    398                     final int change = isPackageDisappearing(packageName);
    399                     if (DBG) Slog.d(TAG, "Changing package name: " + packageName);
    400                     if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) {
    401                         SpellCheckerInfo availSci =
    402                                 findAvailSystemSpellCheckerLocked(packageName, tsd);
    403                         // Set the spell checker settings if different than before
    404                         if (availSci == null
    405                                 || (availSci != null && !availSci.getId().equals(sci.getId()))) {
    406                             setCurrentSpellCheckerLocked(availSci, tsd);
    407                         }
    408                     }
    409                 }
    410             }
    411         }
    412     }
    413 
    414     private boolean bindCurrentSpellCheckerService(
    415             Intent service, ServiceConnection conn, int flags, @UserIdInt int userId) {
    416         if (service == null || conn == null) {
    417             Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn +
    418                     ", userId =" + userId);
    419             return false;
    420         }
    421         return mContext.bindServiceAsUser(service, conn, flags, UserHandle.of(userId));
    422     }
    423 
    424     private void unbindServiceLocked(TextServicesData tsd) {
    425         HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = tsd.mSpellCheckerBindGroups;
    426         for (SpellCheckerBindGroup scbg : spellCheckerBindGroups.values()) {
    427             scbg.removeAllLocked();
    428         }
    429         spellCheckerBindGroups.clear();
    430     }
    431 
    432     private SpellCheckerInfo findAvailSystemSpellCheckerLocked(String prefPackage,
    433             TextServicesData tsd) {
    434         // Filter the spell checker list to remove spell checker services that are not pre-installed
    435         ArrayList<SpellCheckerInfo> spellCheckerList = new ArrayList<>();
    436         for (SpellCheckerInfo sci : tsd.mSpellCheckerList) {
    437             if ((sci.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
    438                 spellCheckerList.add(sci);
    439             }
    440         }
    441 
    442         final int spellCheckersCount = spellCheckerList.size();
    443         if (spellCheckersCount == 0) {
    444             Slog.w(TAG, "no available spell checker services found");
    445             return null;
    446         }
    447         if (prefPackage != null) {
    448             for (int i = 0; i < spellCheckersCount; ++i) {
    449                 final SpellCheckerInfo sci = spellCheckerList.get(i);
    450                 if (prefPackage.equals(sci.getPackageName())) {
    451                     if (DBG) {
    452                         Slog.d(TAG, "findAvailSystemSpellCheckerLocked: " + sci.getPackageName());
    453                     }
    454                     return sci;
    455                 }
    456             }
    457         }
    458 
    459         // Look up a spell checker based on the system locale.
    460         // TODO: Still there is a room to improve in the following logic: e.g., check if the package
    461         // is pre-installed or not.
    462         final Locale systemLocal = mContext.getResources().getConfiguration().locale;
    463         final ArrayList<Locale> suitableLocales =
    464                 InputMethodUtils.getSuitableLocalesForSpellChecker(systemLocal);
    465         if (DBG) {
    466             Slog.w(TAG, "findAvailSystemSpellCheckerLocked suitableLocales="
    467                     + Arrays.toString(suitableLocales.toArray(new Locale[suitableLocales.size()])));
    468         }
    469         final int localeCount = suitableLocales.size();
    470         for (int localeIndex = 0; localeIndex < localeCount; ++localeIndex) {
    471             final Locale locale = suitableLocales.get(localeIndex);
    472             for (int spellCheckersIndex = 0; spellCheckersIndex < spellCheckersCount;
    473                     ++spellCheckersIndex) {
    474                 final SpellCheckerInfo info = spellCheckerList.get(spellCheckersIndex);
    475                 final int subtypeCount = info.getSubtypeCount();
    476                 for (int subtypeIndex = 0; subtypeIndex < subtypeCount; ++subtypeIndex) {
    477                     final SpellCheckerSubtype subtype = info.getSubtypeAt(subtypeIndex);
    478                     final Locale subtypeLocale = InputMethodUtils.constructLocaleFromString(
    479                             subtype.getLocale());
    480                     if (locale.equals(subtypeLocale)) {
    481                         // TODO: We may have more spell checkers that fall into this category.
    482                         // Ideally we should pick up the most suitable one instead of simply
    483                         // returning the first found one.
    484                         return info;
    485                     }
    486                 }
    487             }
    488         }
    489 
    490         if (spellCheckersCount > 1) {
    491             Slog.w(TAG, "more than one spell checker service found, picking first");
    492         }
    493         return spellCheckerList.get(0);
    494     }
    495 
    496     // TODO: Save SpellCheckerService by supported languages. Currently only one spell
    497     // checker is saved.
    498     @Override
    499     public SpellCheckerInfo getCurrentSpellChecker(String locale) {
    500         int userId = UserHandle.getCallingUserId();
    501         synchronized (mLock) {
    502             final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
    503             if (tsd == null) return null;
    504 
    505             return tsd.getCurrentSpellChecker();
    506         }
    507     }
    508 
    509     // TODO: Respect allowImplicitlySelectedSubtype
    510     // TODO: Save SpellCheckerSubtype by supported languages by looking at "locale".
    511     @Override
    512     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
    513             String locale, boolean allowImplicitlySelectedSubtype) {
    514         final int subtypeHashCode;
    515         final SpellCheckerInfo sci;
    516         final Locale systemLocale;
    517         final int userId = UserHandle.getCallingUserId();
    518 
    519         synchronized (mLock) {
    520             final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
    521             if (tsd == null) return null;
    522 
    523             subtypeHashCode =
    524                     tsd.getSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE);
    525             if (DBG) {
    526                 Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCode);
    527             }
    528             sci = tsd.getCurrentSpellChecker();
    529             systemLocale = mContext.getResources().getConfiguration().locale;
    530         }
    531         if (sci == null || sci.getSubtypeCount() == 0) {
    532             if (DBG) {
    533                 Slog.w(TAG, "Subtype not found.");
    534             }
    535             return null;
    536         }
    537         if (subtypeHashCode == SpellCheckerSubtype.SUBTYPE_ID_NONE
    538                 && !allowImplicitlySelectedSubtype) {
    539             return null;
    540         }
    541         String candidateLocale = null;
    542         if (subtypeHashCode == 0) {
    543             // Spell checker language settings == "auto"
    544             final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
    545             if (imm != null) {
    546                 final InputMethodSubtype currentInputMethodSubtype =
    547                         imm.getCurrentInputMethodSubtype();
    548                 if (currentInputMethodSubtype != null) {
    549                     final String localeString = currentInputMethodSubtype.getLocale();
    550                     if (!TextUtils.isEmpty(localeString)) {
    551                         // 1. Use keyboard locale if available in the spell checker
    552                         candidateLocale = localeString;
    553                     }
    554                 }
    555             }
    556             if (candidateLocale == null) {
    557                 // 2. Use System locale if available in the spell checker
    558                 candidateLocale = systemLocale.toString();
    559             }
    560         }
    561         SpellCheckerSubtype candidate = null;
    562         for (int i = 0; i < sci.getSubtypeCount(); ++i) {
    563             final SpellCheckerSubtype scs = sci.getSubtypeAt(i);
    564             if (subtypeHashCode == 0) {
    565                 final String scsLocale = scs.getLocale();
    566                 if (candidateLocale.equals(scsLocale)) {
    567                     return scs;
    568                 } else if (candidate == null) {
    569                     if (candidateLocale.length() >= 2 && scsLocale.length() >= 2
    570                             && candidateLocale.startsWith(scsLocale)) {
    571                         // Fall back to the applicable language
    572                         candidate = scs;
    573                     }
    574                 }
    575             } else if (scs.hashCode() == subtypeHashCode) {
    576                 if (DBG) {
    577                     Slog.w(TAG, "Return subtype " + scs.hashCode() + ", input= " + locale
    578                             + ", " + scs.getLocale());
    579                 }
    580                 // 3. Use the user specified spell check language
    581                 return scs;
    582             }
    583         }
    584         // 4. Fall back to the applicable language and return it if not null
    585         // 5. Simply just return it even if it's null which means we could find no suitable
    586         // spell check languages
    587         return candidate;
    588     }
    589 
    590     @Override
    591     public void getSpellCheckerService(String sciId, String locale,
    592             ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
    593             Bundle bundle) {
    594         if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) {
    595             Slog.e(TAG, "getSpellCheckerService: Invalid input.");
    596             return;
    597         }
    598         int callingUserId = UserHandle.getCallingUserId();
    599 
    600         synchronized (mLock) {
    601             final TextServicesData tsd = getDataFromCallingUserIdLocked(callingUserId);
    602             if (tsd == null) return;
    603 
    604             HashMap<String, SpellCheckerInfo> spellCheckerMap = tsd.mSpellCheckerMap;
    605             if (!spellCheckerMap.containsKey(sciId)) {
    606                 return;
    607             }
    608             final SpellCheckerInfo sci = spellCheckerMap.get(sciId);
    609             HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups =
    610                     tsd.mSpellCheckerBindGroups;
    611             SpellCheckerBindGroup bindGroup = spellCheckerBindGroups.get(sciId);
    612             final int uid = Binder.getCallingUid();
    613             if (bindGroup == null) {
    614                 final long ident = Binder.clearCallingIdentity();
    615                 try {
    616                     bindGroup = startSpellCheckerServiceInnerLocked(sci, tsd);
    617                 } finally {
    618                     Binder.restoreCallingIdentity(ident);
    619                 }
    620                 if (bindGroup == null) {
    621                     // startSpellCheckerServiceInnerLocked failed.
    622                     return;
    623                 }
    624             }
    625 
    626             // Start getISpellCheckerSession async IPC, or just queue the request until the spell
    627             // checker service is bound.
    628             bindGroup.getISpellCheckerSessionOrQueueLocked(
    629                     new SessionRequest(uid, locale, tsListener, scListener, bundle));
    630         }
    631     }
    632 
    633     @Override
    634     public boolean isSpellCheckerEnabled() {
    635         int userId = UserHandle.getCallingUserId();
    636 
    637         synchronized (mLock) {
    638             final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
    639             if (tsd == null) return false;
    640 
    641             return tsd.isSpellCheckerEnabled();
    642         }
    643     }
    644 
    645     @Nullable
    646     private SpellCheckerBindGroup startSpellCheckerServiceInnerLocked(SpellCheckerInfo info,
    647             TextServicesData tsd) {
    648         if (DBG) {
    649             Slog.w(TAG, "Start spell checker session inner locked.");
    650         }
    651         final String sciId = info.getId();
    652         final InternalServiceConnection connection = new InternalServiceConnection(sciId,
    653                 tsd.mSpellCheckerBindGroups);
    654         final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
    655         serviceIntent.setComponent(info.getComponent());
    656         if (DBG) {
    657             Slog.w(TAG, "bind service: " + info.getId());
    658         }
    659         if (!bindCurrentSpellCheckerService(serviceIntent, connection,
    660                 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT_BACKGROUND, tsd.mUserId)) {
    661             Slog.e(TAG, "Failed to get a spell checker service.");
    662             return null;
    663         }
    664         final SpellCheckerBindGroup group = new SpellCheckerBindGroup(connection);
    665 
    666         tsd.mSpellCheckerBindGroups.put(sciId, group);
    667         return group;
    668     }
    669 
    670     @Override
    671     public SpellCheckerInfo[] getEnabledSpellCheckers() {
    672         int callingUserId = UserHandle.getCallingUserId();
    673 
    674         synchronized (mLock) {
    675             final TextServicesData tsd = getDataFromCallingUserIdLocked(callingUserId);
    676             if (tsd == null) return null;
    677 
    678             ArrayList<SpellCheckerInfo> spellCheckerList = tsd.mSpellCheckerList;
    679             if (DBG) {
    680                 Slog.d(TAG, "getEnabledSpellCheckers: " + spellCheckerList.size());
    681                 for (int i = 0; i < spellCheckerList.size(); ++i) {
    682                     Slog.d(TAG,
    683                             "EnabledSpellCheckers: " + spellCheckerList.get(i).getPackageName());
    684                 }
    685             }
    686             return spellCheckerList.toArray(new SpellCheckerInfo[spellCheckerList.size()]);
    687         }
    688     }
    689 
    690     @Override
    691     public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
    692         if (DBG) {
    693             Slog.d(TAG, "FinishSpellCheckerService");
    694         }
    695         int userId = UserHandle.getCallingUserId();
    696 
    697         synchronized (mLock) {
    698             final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
    699             if (tsd == null) return;
    700 
    701             final ArrayList<SpellCheckerBindGroup> removeList = new ArrayList<>();
    702             HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups =
    703                     tsd.mSpellCheckerBindGroups;
    704             for (SpellCheckerBindGroup group : spellCheckerBindGroups.values()) {
    705                 if (group == null) continue;
    706                 // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop.
    707                 removeList.add(group);
    708             }
    709             final int removeSize = removeList.size();
    710             for (int i = 0; i < removeSize; ++i) {
    711                 removeList.get(i).removeListener(listener);
    712             }
    713         }
    714     }
    715 
    716     private void setCurrentSpellCheckerLocked(@Nullable SpellCheckerInfo sci, TextServicesData tsd) {
    717         final String sciId = (sci != null) ? sci.getId() : "";
    718         if (DBG) {
    719             Slog.w(TAG, "setCurrentSpellChecker: " + sciId);
    720         }
    721         final long ident = Binder.clearCallingIdentity();
    722         try {
    723             tsd.setCurrentSpellChecker(sci);
    724         } finally {
    725             Binder.restoreCallingIdentity(ident);
    726         }
    727     }
    728 
    729     @Override
    730     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    731         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
    732 
    733         if (args.length == 0 || (args.length == 1 && args[0].equals("-a"))) {
    734             // Dump all users' data
    735             synchronized (mLock) {
    736                 pw.println("Current Text Services Manager state:");
    737                 pw.println("  Users:");
    738                 final int numOfUsers = mUserData.size();
    739                 for (int i = 0; i < numOfUsers; i++) {
    740                     TextServicesData tsd = mUserData.valueAt(i);
    741                     tsd.dump(pw);
    742                 }
    743             }
    744         } else {  // Dump a given user's data
    745             if (args.length != 2 || !args[0].equals("--user")) {
    746                 pw.println("Invalid arguments to text services." );
    747                 return;
    748             } else {
    749                 int userId = Integer.parseInt(args[1]);
    750                 UserInfo userInfo = mUserManager.getUserInfo(userId);
    751                 if (userInfo == null) {
    752                     pw.println("Non-existent user.");
    753                     return;
    754                 }
    755                 TextServicesData tsd = mUserData.get(userId);
    756                 if (tsd == null) {
    757                     pw.println("User needs to unlock first." );
    758                     return;
    759                 }
    760                 synchronized (mLock) {
    761                     pw.println("Current Text Services Manager state:");
    762                     pw.println("  User " + userId + ":");
    763                     tsd.dump(pw);
    764                 }
    765             }
    766         }
    767     }
    768 
    769     /**
    770      * @param callingUserId user ID of the calling process
    771      * @return {@link TextServicesData} for the given user.  {@code null} if spell checker is not
    772      *         temporarily / permanently available for the specified user
    773      */
    774     @Nullable
    775     private TextServicesData getDataFromCallingUserIdLocked(@UserIdInt int callingUserId) {
    776         final int spellCheckerOwnerUserId = mSpellCheckerOwnerUserIdMap.get(callingUserId);
    777         final TextServicesData data = mUserData.get(spellCheckerOwnerUserId);
    778         if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
    779             if (spellCheckerOwnerUserId != callingUserId) {
    780                 // Calling process is running under child profile.
    781                 if (data == null) {
    782                     return null;
    783                 }
    784                 final SpellCheckerInfo info = data.getCurrentSpellChecker();
    785                 if (info == null) {
    786                     return null;
    787                 }
    788                 final ServiceInfo serviceInfo = info.getServiceInfo();
    789                 if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
    790                     // To be conservative, non pre-installed spell checker services are not allowed
    791                     // to be used for child profiles.
    792                     return null;
    793                 }
    794             }
    795         }
    796         return data;
    797     }
    798 
    799     private static final class SessionRequest {
    800         public final int mUid;
    801         @Nullable
    802         public final String mLocale;
    803         @NonNull
    804         public final ITextServicesSessionListener mTsListener;
    805         @NonNull
    806         public final ISpellCheckerSessionListener mScListener;
    807         @Nullable
    808         public final Bundle mBundle;
    809 
    810         SessionRequest(int uid, @Nullable String locale,
    811                 @NonNull ITextServicesSessionListener tsListener,
    812                 @NonNull ISpellCheckerSessionListener scListener, @Nullable Bundle bundle) {
    813             mUid = uid;
    814             mLocale = locale;
    815             mTsListener = tsListener;
    816             mScListener = scListener;
    817             mBundle = bundle;
    818         }
    819     }
    820 
    821     // SpellCheckerBindGroup contains active text service session listeners.
    822     // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
    823     // mSpellCheckerBindGroups
    824     private final class SpellCheckerBindGroup {
    825         private final String TAG = SpellCheckerBindGroup.class.getSimpleName();
    826         private final InternalServiceConnection mInternalConnection;
    827         private final InternalDeathRecipients mListeners;
    828         private boolean mUnbindCalled;
    829         private ISpellCheckerService mSpellChecker;
    830         private boolean mConnected;
    831         private final ArrayList<SessionRequest> mPendingSessionRequests = new ArrayList<>();
    832         private final ArrayList<SessionRequest> mOnGoingSessionRequests = new ArrayList<>();
    833         @NonNull
    834         HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups;
    835 
    836 
    837         public SpellCheckerBindGroup(InternalServiceConnection connection) {
    838             mInternalConnection = connection;
    839             mListeners = new InternalDeathRecipients(this);
    840             mSpellCheckerBindGroups = connection.mSpellCheckerBindGroups;
    841         }
    842 
    843         public void onServiceConnectedLocked(ISpellCheckerService spellChecker) {
    844             if (DBG) {
    845                 Slog.d(TAG, "onServiceConnectedLocked");
    846             }
    847 
    848             if (mUnbindCalled) {
    849                 return;
    850             }
    851             mSpellChecker = spellChecker;
    852             mConnected = true;
    853             // Dispatch pending getISpellCheckerSession requests.
    854             try {
    855                 final int size = mPendingSessionRequests.size();
    856                 for (int i = 0; i < size; ++i) {
    857                     final SessionRequest request = mPendingSessionRequests.get(i);
    858                     mSpellChecker.getISpellCheckerSession(
    859                             request.mLocale, request.mScListener, request.mBundle,
    860                             new ISpellCheckerServiceCallbackBinder(this, request));
    861                     mOnGoingSessionRequests.add(request);
    862                 }
    863                 mPendingSessionRequests.clear();
    864             } catch(RemoteException e) {
    865                 // The target spell checker service is not available.  Better to reset the state.
    866                 removeAllLocked();
    867             }
    868             cleanLocked();
    869         }
    870 
    871         public void onServiceDisconnectedLocked() {
    872             if (DBG) {
    873                 Slog.d(TAG, "onServiceDisconnectedLocked");
    874             }
    875 
    876             mSpellChecker = null;
    877             mConnected = false;
    878         }
    879 
    880         public void removeListener(ISpellCheckerSessionListener listener) {
    881             if (DBG) {
    882                 Slog.w(TAG, "remove listener: " + listener.hashCode());
    883             }
    884             synchronized (mLock) {
    885                 mListeners.unregister(listener);
    886                 final IBinder scListenerBinder = listener.asBinder();
    887                 final Predicate<SessionRequest> removeCondition =
    888                         request -> request.mScListener.asBinder() == scListenerBinder;
    889                 mPendingSessionRequests.removeIf(removeCondition);
    890                 mOnGoingSessionRequests.removeIf(removeCondition);
    891                 cleanLocked();
    892             }
    893         }
    894 
    895         // cleanLocked may remove elements from mSpellCheckerBindGroups
    896         private void cleanLocked() {
    897             if (DBG) {
    898                 Slog.d(TAG, "cleanLocked");
    899             }
    900             if (mUnbindCalled) {
    901                 return;
    902             }
    903             // If there are no more active listeners, clean up.  Only do this once.
    904             if (mListeners.getRegisteredCallbackCount() > 0) {
    905                 return;
    906             }
    907             if (!mPendingSessionRequests.isEmpty()) {
    908                 return;
    909             }
    910             if (!mOnGoingSessionRequests.isEmpty()) {
    911                 return;
    912             }
    913             final String sciId = mInternalConnection.mSciId;
    914             final SpellCheckerBindGroup cur = mSpellCheckerBindGroups.get(sciId);
    915             if (cur == this) {
    916                 if (DBG) {
    917                     Slog.d(TAG, "Remove bind group.");
    918                 }
    919                 mSpellCheckerBindGroups.remove(sciId);
    920             }
    921             mContext.unbindService(mInternalConnection);
    922             mUnbindCalled = true;
    923         }
    924 
    925         public void removeAllLocked() {
    926             Slog.e(TAG, "Remove the spell checker bind unexpectedly.");
    927             final int size = mListeners.getRegisteredCallbackCount();
    928             for (int i = size - 1; i >= 0; --i) {
    929                 mListeners.unregister(mListeners.getRegisteredCallbackItem(i));
    930             }
    931             mPendingSessionRequests.clear();
    932             mOnGoingSessionRequests.clear();
    933             cleanLocked();
    934         }
    935 
    936         public void getISpellCheckerSessionOrQueueLocked(@NonNull SessionRequest request) {
    937             if (mUnbindCalled) {
    938                 return;
    939             }
    940             mListeners.register(request.mScListener);
    941             if (!mConnected) {
    942                 mPendingSessionRequests.add(request);
    943                 return;
    944             }
    945             try {
    946                 mSpellChecker.getISpellCheckerSession(
    947                         request.mLocale, request.mScListener, request.mBundle,
    948                         new ISpellCheckerServiceCallbackBinder(this, request));
    949                 mOnGoingSessionRequests.add(request);
    950             } catch(RemoteException e) {
    951                 // The target spell checker service is not available.  Better to reset the state.
    952                 removeAllLocked();
    953             }
    954             cleanLocked();
    955         }
    956 
    957         void onSessionCreated(@Nullable final ISpellCheckerSession newSession,
    958                 @NonNull final SessionRequest request) {
    959             synchronized (mLock) {
    960                 if (mUnbindCalled) {
    961                     return;
    962                 }
    963                 if (mOnGoingSessionRequests.remove(request)) {
    964                     try {
    965                         request.mTsListener.onServiceConnected(newSession);
    966                     } catch (RemoteException e) {
    967                         // Technically this can happen if the spell checker client app is already
    968                         // dead.  We can just forget about this request; the request is already
    969                         // removed from mOnGoingSessionRequests and the death recipient listener is
    970                         // not yet added to mListeners. There is nothing to release further.
    971                     }
    972                 }
    973                 cleanLocked();
    974             }
    975         }
    976     }
    977 
    978     private final class InternalServiceConnection implements ServiceConnection {
    979         private final String mSciId;
    980         @NonNull
    981         private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups;
    982         public InternalServiceConnection(String id,
    983                 @NonNull HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups) {
    984             mSciId = id;
    985             mSpellCheckerBindGroups = spellCheckerBindGroups;
    986         }
    987 
    988         @Override
    989         public void onServiceConnected(ComponentName name, IBinder service) {
    990             synchronized (mLock) {
    991                 onServiceConnectedInnerLocked(name, service);
    992             }
    993         }
    994 
    995         private void onServiceConnectedInnerLocked(ComponentName name, IBinder service) {
    996             if (DBG) {
    997                 Slog.w(TAG, "onServiceConnectedInnerLocked: " + name);
    998             }
    999             final ISpellCheckerService spellChecker =
   1000                     ISpellCheckerService.Stub.asInterface(service);
   1001 
   1002             final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
   1003             if (group != null && this == group.mInternalConnection) {
   1004                 group.onServiceConnectedLocked(spellChecker);
   1005             }
   1006         }
   1007 
   1008         @Override
   1009         public void onServiceDisconnected(ComponentName name) {
   1010             synchronized (mLock) {
   1011                 onServiceDisconnectedInnerLocked(name);
   1012             }
   1013         }
   1014 
   1015         private void onServiceDisconnectedInnerLocked(ComponentName name) {
   1016             if (DBG) {
   1017                 Slog.w(TAG, "onServiceDisconnectedInnerLocked: " + name);
   1018             }
   1019             final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
   1020             if (group != null && this == group.mInternalConnection) {
   1021                 group.onServiceDisconnectedLocked();
   1022             }
   1023         }
   1024     }
   1025 
   1026     private static final class InternalDeathRecipients extends
   1027             RemoteCallbackList<ISpellCheckerSessionListener> {
   1028         private final SpellCheckerBindGroup mGroup;
   1029 
   1030         public InternalDeathRecipients(SpellCheckerBindGroup group) {
   1031             mGroup = group;
   1032         }
   1033 
   1034         @Override
   1035         public void onCallbackDied(ISpellCheckerSessionListener listener) {
   1036             mGroup.removeListener(listener);
   1037         }
   1038     }
   1039 
   1040     private static final class ISpellCheckerServiceCallbackBinder
   1041             extends ISpellCheckerServiceCallback.Stub {
   1042         @NonNull
   1043         private final SpellCheckerBindGroup mBindGroup;
   1044         @NonNull
   1045         private final SessionRequest mRequest;
   1046 
   1047         ISpellCheckerServiceCallbackBinder(@NonNull final SpellCheckerBindGroup bindGroup,
   1048                 @NonNull final SessionRequest request) {
   1049             mBindGroup = bindGroup;
   1050             mRequest = request;
   1051         }
   1052 
   1053         @Override
   1054         public void onSessionCreated(@Nullable ISpellCheckerSession newSession) {
   1055             mBindGroup.onSessionCreated(newSession, mRequest);
   1056         }
   1057     }
   1058 }
   1059