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