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.content.PackageMonitor;
     20 import com.android.internal.textservice.ISpellCheckerService;
     21 import com.android.internal.textservice.ISpellCheckerSession;
     22 import com.android.internal.textservice.ISpellCheckerSessionListener;
     23 import com.android.internal.textservice.ITextServicesManager;
     24 import com.android.internal.textservice.ITextServicesSessionListener;
     25 
     26 import org.xmlpull.v1.XmlPullParserException;
     27 
     28 import android.content.ComponentName;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.content.ServiceConnection;
     32 import android.content.pm.PackageManager;
     33 import android.content.pm.ResolveInfo;
     34 import android.content.pm.ServiceInfo;
     35 import android.os.Binder;
     36 import android.os.Bundle;
     37 import android.os.IBinder;
     38 import android.os.RemoteException;
     39 import android.provider.Settings;
     40 import android.service.textservice.SpellCheckerService;
     41 import android.text.TextUtils;
     42 import android.util.Slog;
     43 import android.view.inputmethod.InputMethodManager;
     44 import android.view.inputmethod.InputMethodSubtype;
     45 import android.view.textservice.SpellCheckerInfo;
     46 import android.view.textservice.SpellCheckerSubtype;
     47 
     48 import java.io.FileDescriptor;
     49 import java.io.IOException;
     50 import java.io.PrintWriter;
     51 import java.util.ArrayList;
     52 import java.util.HashMap;
     53 import java.util.List;
     54 import java.util.Map;
     55 
     56 public class TextServicesManagerService extends ITextServicesManager.Stub {
     57     private static final String TAG = TextServicesManagerService.class.getSimpleName();
     58     private static final boolean DBG = false;
     59 
     60     private final Context mContext;
     61     private boolean mSystemReady;
     62     private final TextServicesMonitor mMonitor;
     63     private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap =
     64             new HashMap<String, SpellCheckerInfo>();
     65     private final ArrayList<SpellCheckerInfo> mSpellCheckerList = new ArrayList<SpellCheckerInfo>();
     66     private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups =
     67             new HashMap<String, SpellCheckerBindGroup>();
     68 
     69     public void systemReady() {
     70         if (!mSystemReady) {
     71             mSystemReady = true;
     72         }
     73     }
     74 
     75     public TextServicesManagerService(Context context) {
     76         mSystemReady = false;
     77         mContext = context;
     78         mMonitor = new TextServicesMonitor();
     79         mMonitor.register(context, true);
     80         synchronized (mSpellCheckerMap) {
     81             buildSpellCheckerMapLocked(context, mSpellCheckerList, mSpellCheckerMap);
     82         }
     83         SpellCheckerInfo sci = getCurrentSpellChecker(null);
     84         if (sci == null) {
     85             sci = findAvailSpellCheckerLocked(null, null);
     86             if (sci != null) {
     87                 // Set the current spell checker if there is one or more spell checkers
     88                 // available. In this case, "sci" is the first one in the available spell
     89                 // checkers.
     90                 setCurrentSpellCheckerLocked(sci.getId());
     91             }
     92         }
     93     }
     94 
     95     private class TextServicesMonitor extends PackageMonitor {
     96         @Override
     97         public void onSomePackagesChanged() {
     98             synchronized (mSpellCheckerMap) {
     99                 buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap);
    100                 // TODO: Update for each locale
    101                 SpellCheckerInfo sci = getCurrentSpellChecker(null);
    102                 if (sci == null) return;
    103                 final String packageName = sci.getPackageName();
    104                 final int change = isPackageDisappearing(packageName);
    105                 if (// Package disappearing
    106                         change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE
    107                         // Package modified
    108                         || isPackageModified(packageName)) {
    109                     sci = findAvailSpellCheckerLocked(null, packageName);
    110                     if (sci != null) {
    111                         setCurrentSpellCheckerLocked(sci.getId());
    112                     }
    113                 }
    114             }
    115         }
    116     }
    117 
    118     private static void buildSpellCheckerMapLocked(Context context,
    119             ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map) {
    120         list.clear();
    121         map.clear();
    122         final PackageManager pm = context.getPackageManager();
    123         List<ResolveInfo> services = pm.queryIntentServices(
    124                 new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
    125         final int N = services.size();
    126         for (int i = 0; i < N; ++i) {
    127             final ResolveInfo ri = services.get(i);
    128             final ServiceInfo si = ri.serviceInfo;
    129             final ComponentName compName = new ComponentName(si.packageName, si.name);
    130             if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
    131                 Slog.w(TAG, "Skipping text service " + compName
    132                         + ": it does not require the permission "
    133                         + android.Manifest.permission.BIND_TEXT_SERVICE);
    134                 continue;
    135             }
    136             if (DBG) Slog.d(TAG, "Add: " + compName);
    137             try {
    138                 final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri);
    139                 if (sci.getSubtypeCount() <= 0) {
    140                     Slog.w(TAG, "Skipping text service " + compName
    141                             + ": it does not contain subtypes.");
    142                     continue;
    143                 }
    144                 list.add(sci);
    145                 map.put(sci.getId(), sci);
    146             } catch (XmlPullParserException e) {
    147                 Slog.w(TAG, "Unable to load the spell checker " + compName, e);
    148             } catch (IOException e) {
    149                 Slog.w(TAG, "Unable to load the spell checker " + compName, e);
    150             }
    151         }
    152         if (DBG) {
    153             Slog.d(TAG, "buildSpellCheckerMapLocked: " + list.size() + "," + map.size());
    154         }
    155     }
    156 
    157     // TODO: find an appropriate spell checker for specified locale
    158     private SpellCheckerInfo findAvailSpellCheckerLocked(String locale, String prefPackage) {
    159         final int spellCheckersCount = mSpellCheckerList.size();
    160         if (spellCheckersCount == 0) {
    161             Slog.w(TAG, "no available spell checker services found");
    162             return null;
    163         }
    164         if (prefPackage != null) {
    165             for (int i = 0; i < spellCheckersCount; ++i) {
    166                 final SpellCheckerInfo sci = mSpellCheckerList.get(i);
    167                 if (prefPackage.equals(sci.getPackageName())) {
    168                     if (DBG) {
    169                         Slog.d(TAG, "findAvailSpellCheckerLocked: " + sci.getPackageName());
    170                     }
    171                     return sci;
    172                 }
    173             }
    174         }
    175         if (spellCheckersCount > 1) {
    176             Slog.w(TAG, "more than one spell checker service found, picking first");
    177         }
    178         return mSpellCheckerList.get(0);
    179     }
    180 
    181     // TODO: Save SpellCheckerService by supported languages. Currently only one spell
    182     // checker is saved.
    183     @Override
    184     public SpellCheckerInfo getCurrentSpellChecker(String locale) {
    185         synchronized (mSpellCheckerMap) {
    186             final String curSpellCheckerId =
    187                     Settings.Secure.getString(mContext.getContentResolver(),
    188                             Settings.Secure.SELECTED_SPELL_CHECKER);
    189             if (DBG) {
    190                 Slog.w(TAG, "getCurrentSpellChecker: " + curSpellCheckerId);
    191             }
    192             if (TextUtils.isEmpty(curSpellCheckerId)) {
    193                 return null;
    194             }
    195             return mSpellCheckerMap.get(curSpellCheckerId);
    196         }
    197     }
    198 
    199     // TODO: Respect allowImplicitlySelectedSubtype
    200     // TODO: Save SpellCheckerSubtype by supported languages.
    201     @Override
    202     public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
    203             String locale, boolean allowImplicitlySelectedSubtype) {
    204         synchronized (mSpellCheckerMap) {
    205             final String subtypeHashCodeStr =
    206                     Settings.Secure.getString(mContext.getContentResolver(),
    207                             Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE);
    208             if (DBG) {
    209                 Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCodeStr);
    210             }
    211             final SpellCheckerInfo sci = getCurrentSpellChecker(null);
    212             if (sci == null || sci.getSubtypeCount() == 0) {
    213                 if (DBG) {
    214                     Slog.w(TAG, "Subtype not found.");
    215                 }
    216                 return null;
    217             }
    218             final int hashCode;
    219             if (!TextUtils.isEmpty(subtypeHashCodeStr)) {
    220                 hashCode = Integer.valueOf(subtypeHashCodeStr);
    221             } else {
    222                 hashCode = 0;
    223             }
    224             if (hashCode == 0 && !allowImplicitlySelectedSubtype) {
    225                 return null;
    226             }
    227             String candidateLocale = null;
    228             if (hashCode == 0) {
    229                 // Spell checker language settings == "auto"
    230                 final InputMethodManager imm =
    231                         (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
    232                 if (imm != null) {
    233                     final InputMethodSubtype currentInputMethodSubtype =
    234                             imm.getCurrentInputMethodSubtype();
    235                     if (currentInputMethodSubtype != null) {
    236                         final String localeString = currentInputMethodSubtype.getLocale();
    237                         if (!TextUtils.isEmpty(localeString)) {
    238                             // 1. Use keyboard locale if available in the spell checker
    239                             candidateLocale = localeString;
    240                         }
    241                     }
    242                 }
    243                 if (candidateLocale == null) {
    244                     // 2. Use System locale if available in the spell checker
    245                     candidateLocale = mContext.getResources().getConfiguration().locale.toString();
    246                 }
    247             }
    248             SpellCheckerSubtype candidate = null;
    249             for (int i = 0; i < sci.getSubtypeCount(); ++i) {
    250                 final SpellCheckerSubtype scs = sci.getSubtypeAt(i);
    251                 if (hashCode == 0) {
    252                     if (candidateLocale.equals(locale)) {
    253                         return scs;
    254                     } else if (candidate == null) {
    255                         final String scsLocale = scs.getLocale();
    256                         if (candidateLocale.length() >= 2
    257                                 && scsLocale.length() >= 2
    258                                 && candidateLocale.substring(0, 2).equals(
    259                                         scsLocale.substring(0, 2))) {
    260                             // Fall back to the applicable language
    261                             candidate = scs;
    262                         }
    263                     }
    264                 } else if (scs.hashCode() == hashCode) {
    265                     if (DBG) {
    266                         Slog.w(TAG, "Return subtype " + scs.hashCode() + ", input= " + locale
    267                                 + ", " + scs.getLocale());
    268                     }
    269                     // 3. Use the user specified spell check language
    270                     return scs;
    271                 }
    272             }
    273             // 4. Fall back to the applicable language and return it if not null
    274             // 5. Simply just return it even if it's null which means we could find no suitable
    275             // spell check languages
    276             return candidate;
    277         }
    278     }
    279 
    280     @Override
    281     public void getSpellCheckerService(String sciId, String locale,
    282             ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
    283             Bundle bundle) {
    284         if (!mSystemReady) {
    285             return;
    286         }
    287         if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) {
    288             Slog.e(TAG, "getSpellCheckerService: Invalid input.");
    289             return;
    290         }
    291         synchronized(mSpellCheckerMap) {
    292             if (!mSpellCheckerMap.containsKey(sciId)) {
    293                 return;
    294             }
    295             final SpellCheckerInfo sci = mSpellCheckerMap.get(sciId);
    296             final int uid = Binder.getCallingUid();
    297             if (mSpellCheckerBindGroups.containsKey(sciId)) {
    298                 final SpellCheckerBindGroup bindGroup = mSpellCheckerBindGroups.get(sciId);
    299                 if (bindGroup != null) {
    300                     final InternalDeathRecipient recipient =
    301                             mSpellCheckerBindGroups.get(sciId).addListener(
    302                                     tsListener, locale, scListener, uid, bundle);
    303                     if (recipient == null) {
    304                         if (DBG) {
    305                             Slog.w(TAG, "Didn't create a death recipient.");
    306                         }
    307                         return;
    308                     }
    309                     if (bindGroup.mSpellChecker == null & bindGroup.mConnected) {
    310                         Slog.e(TAG, "The state of the spell checker bind group is illegal.");
    311                         bindGroup.removeAll();
    312                     } else if (bindGroup.mSpellChecker != null) {
    313                         if (DBG) {
    314                             Slog.w(TAG, "Existing bind found. Return a spell checker session now. "
    315                                     + "Listeners count = " + bindGroup.mListeners.size());
    316                         }
    317                         try {
    318                             final ISpellCheckerSession session =
    319                                     bindGroup.mSpellChecker.getISpellCheckerSession(
    320                                             recipient.mScLocale, recipient.mScListener, bundle);
    321                             if (session != null) {
    322                                 tsListener.onServiceConnected(session);
    323                                 return;
    324                             } else {
    325                                 if (DBG) {
    326                                     Slog.w(TAG, "Existing bind already expired. ");
    327                                 }
    328                                 bindGroup.removeAll();
    329                             }
    330                         } catch (RemoteException e) {
    331                             Slog.e(TAG, "Exception in getting spell checker session: " + e);
    332                             bindGroup.removeAll();
    333                         }
    334                     }
    335                 }
    336             }
    337             final long ident = Binder.clearCallingIdentity();
    338             try {
    339                 startSpellCheckerServiceInnerLocked(
    340                         sci, locale, tsListener, scListener, uid, bundle);
    341             } finally {
    342                 Binder.restoreCallingIdentity(ident);
    343             }
    344         }
    345         return;
    346     }
    347 
    348     @Override
    349     public boolean isSpellCheckerEnabled() {
    350         synchronized(mSpellCheckerMap) {
    351             return isSpellCheckerEnabledLocked();
    352         }
    353     }
    354 
    355     private void startSpellCheckerServiceInnerLocked(SpellCheckerInfo info, String locale,
    356             ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
    357             int uid, Bundle bundle) {
    358         if (DBG) {
    359             Slog.w(TAG, "Start spell checker session inner locked.");
    360         }
    361         final String sciId = info.getId();
    362         final InternalServiceConnection connection = new InternalServiceConnection(
    363                 sciId, locale, bundle);
    364         final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
    365         serviceIntent.setComponent(info.getComponent());
    366         if (DBG) {
    367             Slog.w(TAG, "bind service: " + info.getId());
    368         }
    369         if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
    370             Slog.e(TAG, "Failed to get a spell checker service.");
    371             return;
    372         }
    373         final SpellCheckerBindGroup group = new SpellCheckerBindGroup(
    374                 connection, tsListener, locale, scListener, uid, bundle);
    375         mSpellCheckerBindGroups.put(sciId, group);
    376     }
    377 
    378     @Override
    379     public SpellCheckerInfo[] getEnabledSpellCheckers() {
    380         if (DBG) {
    381             Slog.d(TAG, "getEnabledSpellCheckers: " + mSpellCheckerList.size());
    382             for (int i = 0; i < mSpellCheckerList.size(); ++i) {
    383                 Slog.d(TAG, "EnabledSpellCheckers: " + mSpellCheckerList.get(i).getPackageName());
    384             }
    385         }
    386         return mSpellCheckerList.toArray(new SpellCheckerInfo[mSpellCheckerList.size()]);
    387     }
    388 
    389     @Override
    390     public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
    391         if (DBG) {
    392             Slog.d(TAG, "FinishSpellCheckerService");
    393         }
    394         synchronized(mSpellCheckerMap) {
    395             final ArrayList<SpellCheckerBindGroup> removeList =
    396                     new ArrayList<SpellCheckerBindGroup>();
    397             for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) {
    398                 if (group == null) continue;
    399                 // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop.
    400                 removeList.add(group);
    401             }
    402             final int removeSize = removeList.size();
    403             for (int i = 0; i < removeSize; ++i) {
    404                 removeList.get(i).removeListener(listener);
    405             }
    406         }
    407     }
    408 
    409     @Override
    410     public void setCurrentSpellChecker(String locale, String sciId) {
    411         synchronized(mSpellCheckerMap) {
    412             if (mContext.checkCallingOrSelfPermission(
    413                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
    414                     != PackageManager.PERMISSION_GRANTED) {
    415                 throw new SecurityException(
    416                         "Requires permission "
    417                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
    418             }
    419             setCurrentSpellCheckerLocked(sciId);
    420         }
    421     }
    422 
    423     @Override
    424     public void setCurrentSpellCheckerSubtype(String locale, int hashCode) {
    425         synchronized(mSpellCheckerMap) {
    426             if (mContext.checkCallingOrSelfPermission(
    427                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
    428                     != PackageManager.PERMISSION_GRANTED) {
    429                 throw new SecurityException(
    430                         "Requires permission "
    431                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
    432             }
    433             setCurrentSpellCheckerSubtypeLocked(hashCode);
    434         }
    435     }
    436 
    437     @Override
    438     public void setSpellCheckerEnabled(boolean enabled) {
    439         synchronized(mSpellCheckerMap) {
    440             if (mContext.checkCallingOrSelfPermission(
    441                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
    442                     != PackageManager.PERMISSION_GRANTED) {
    443                 throw new SecurityException(
    444                         "Requires permission "
    445                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
    446             }
    447             setSpellCheckerEnabledLocked(enabled);
    448         }
    449     }
    450 
    451     private void setCurrentSpellCheckerLocked(String sciId) {
    452         if (DBG) {
    453             Slog.w(TAG, "setCurrentSpellChecker: " + sciId);
    454         }
    455         if (TextUtils.isEmpty(sciId) || !mSpellCheckerMap.containsKey(sciId)) return;
    456         final SpellCheckerInfo currentSci = getCurrentSpellChecker(null);
    457         if (currentSci != null && currentSci.getId().equals(sciId)) {
    458             // Do nothing if the current spell checker is same as new spell checker.
    459             return;
    460         }
    461         final long ident = Binder.clearCallingIdentity();
    462         try {
    463             Settings.Secure.putString(mContext.getContentResolver(),
    464                     Settings.Secure.SELECTED_SPELL_CHECKER, sciId);
    465             setCurrentSpellCheckerSubtypeLocked(0);
    466         } finally {
    467             Binder.restoreCallingIdentity(ident);
    468         }
    469     }
    470 
    471     private void setCurrentSpellCheckerSubtypeLocked(int hashCode) {
    472         if (DBG) {
    473             Slog.w(TAG, "setCurrentSpellCheckerSubtype: " + hashCode);
    474         }
    475         final SpellCheckerInfo sci = getCurrentSpellChecker(null);
    476         int tempHashCode = 0;
    477         for (int i = 0; sci != null && i < sci.getSubtypeCount(); ++i) {
    478             if(sci.getSubtypeAt(i).hashCode() == hashCode) {
    479                 tempHashCode = hashCode;
    480                 break;
    481             }
    482         }
    483         final long ident = Binder.clearCallingIdentity();
    484         try {
    485             Settings.Secure.putString(mContext.getContentResolver(),
    486                     Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, String.valueOf(tempHashCode));
    487         } finally {
    488             Binder.restoreCallingIdentity(ident);
    489         }
    490     }
    491 
    492     private void setSpellCheckerEnabledLocked(boolean enabled) {
    493         if (DBG) {
    494             Slog.w(TAG, "setSpellCheckerEnabled: " + enabled);
    495         }
    496         final long ident = Binder.clearCallingIdentity();
    497         try {
    498             Settings.Secure.putInt(mContext.getContentResolver(),
    499                     Settings.Secure.SPELL_CHECKER_ENABLED, enabled ? 1 : 0);
    500         } finally {
    501             Binder.restoreCallingIdentity(ident);
    502         }
    503     }
    504 
    505     private boolean isSpellCheckerEnabledLocked() {
    506         final long ident = Binder.clearCallingIdentity();
    507         try {
    508             final boolean retval = Settings.Secure.getInt(mContext.getContentResolver(),
    509                     Settings.Secure.SPELL_CHECKER_ENABLED, 1) == 1;
    510             if (DBG) {
    511                 Slog.w(TAG, "getSpellCheckerEnabled: " + retval);
    512             }
    513             return retval;
    514         } finally {
    515             Binder.restoreCallingIdentity(ident);
    516         }
    517     }
    518 
    519     @Override
    520     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    521         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
    522                 != PackageManager.PERMISSION_GRANTED) {
    523 
    524             pw.println("Permission Denial: can't dump TextServicesManagerService from from pid="
    525                     + Binder.getCallingPid()
    526                     + ", uid=" + Binder.getCallingUid());
    527             return;
    528         }
    529 
    530         synchronized(mSpellCheckerMap) {
    531             pw.println("Current Text Services Manager state:");
    532             pw.println("  Spell Checker Map:");
    533             for (Map.Entry<String, SpellCheckerInfo> ent : mSpellCheckerMap.entrySet()) {
    534                 pw.print("    "); pw.print(ent.getKey()); pw.println(":");
    535                 SpellCheckerInfo info = ent.getValue();
    536                 pw.print("      "); pw.print("id="); pw.println(info.getId());
    537                 pw.print("      "); pw.print("comp=");
    538                         pw.println(info.getComponent().toShortString());
    539                 int NS = info.getSubtypeCount();
    540                 for (int i=0; i<NS; i++) {
    541                     SpellCheckerSubtype st = info.getSubtypeAt(i);
    542                     pw.print("      "); pw.print("Subtype #"); pw.print(i); pw.println(":");
    543                     pw.print("        "); pw.print("locale="); pw.println(st.getLocale());
    544                     pw.print("        "); pw.print("extraValue=");
    545                             pw.println(st.getExtraValue());
    546                 }
    547             }
    548             pw.println("");
    549             pw.println("  Spell Checker Bind Groups:");
    550             for (Map.Entry<String, SpellCheckerBindGroup> ent
    551                     : mSpellCheckerBindGroups.entrySet()) {
    552                 SpellCheckerBindGroup grp = ent.getValue();
    553                 pw.print("    "); pw.print(ent.getKey()); pw.print(" ");
    554                         pw.print(grp); pw.println(":");
    555                 pw.print("      "); pw.print("mInternalConnection=");
    556                         pw.println(grp.mInternalConnection);
    557                 pw.print("      "); pw.print("mSpellChecker=");
    558                         pw.println(grp.mSpellChecker);
    559                 pw.print("      "); pw.print("mBound="); pw.print(grp.mBound);
    560                         pw.print(" mConnected="); pw.println(grp.mConnected);
    561                 int NL = grp.mListeners.size();
    562                 for (int i=0; i<NL; i++) {
    563                     InternalDeathRecipient listener = grp.mListeners.get(i);
    564                     pw.print("      "); pw.print("Listener #"); pw.print(i); pw.println(":");
    565                     pw.print("        "); pw.print("mTsListener=");
    566                             pw.println(listener.mTsListener);
    567                     pw.print("        "); pw.print("mScListener=");
    568                             pw.println(listener.mScListener);
    569                     pw.print("        "); pw.print("mGroup=");
    570                             pw.println(listener.mGroup);
    571                     pw.print("        "); pw.print("mScLocale=");
    572                             pw.print(listener.mScLocale);
    573                             pw.print(" mUid="); pw.println(listener.mUid);
    574                 }
    575             }
    576         }
    577     }
    578 
    579     // SpellCheckerBindGroup contains active text service session listeners.
    580     // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
    581     // mSpellCheckerBindGroups
    582     private class SpellCheckerBindGroup {
    583         private final String TAG = SpellCheckerBindGroup.class.getSimpleName();
    584         private final InternalServiceConnection mInternalConnection;
    585         private final ArrayList<InternalDeathRecipient> mListeners =
    586                 new ArrayList<InternalDeathRecipient>();
    587         public boolean mBound;
    588         public ISpellCheckerService mSpellChecker;
    589         public boolean mConnected;
    590 
    591         public SpellCheckerBindGroup(InternalServiceConnection connection,
    592                 ITextServicesSessionListener listener, String locale,
    593                 ISpellCheckerSessionListener scListener, int uid, Bundle bundle) {
    594             mInternalConnection = connection;
    595             mBound = true;
    596             mConnected = false;
    597             addListener(listener, locale, scListener, uid, bundle);
    598         }
    599 
    600         public void onServiceConnected(ISpellCheckerService spellChecker) {
    601             if (DBG) {
    602                 Slog.d(TAG, "onServiceConnected");
    603             }
    604             synchronized(mSpellCheckerMap) {
    605                 for (InternalDeathRecipient listener : mListeners) {
    606                     try {
    607                         final ISpellCheckerSession session = spellChecker.getISpellCheckerSession(
    608                                 listener.mScLocale, listener.mScListener, listener.mBundle);
    609                         listener.mTsListener.onServiceConnected(session);
    610                     } catch (RemoteException e) {
    611                         Slog.e(TAG, "Exception in getting the spell checker session."
    612                                 + "Reconnect to the spellchecker. ", e);
    613                         removeAll();
    614                         return;
    615                     }
    616                 }
    617                 mSpellChecker = spellChecker;
    618                 mConnected = true;
    619             }
    620         }
    621 
    622         public InternalDeathRecipient addListener(ITextServicesSessionListener tsListener,
    623                 String locale, ISpellCheckerSessionListener scListener, int uid, Bundle bundle) {
    624             if (DBG) {
    625                 Slog.d(TAG, "addListener: " + locale);
    626             }
    627             InternalDeathRecipient recipient = null;
    628             synchronized(mSpellCheckerMap) {
    629                 try {
    630                     final int size = mListeners.size();
    631                     for (int i = 0; i < size; ++i) {
    632                         if (mListeners.get(i).hasSpellCheckerListener(scListener)) {
    633                             // do not add the lister if the group already contains this.
    634                             return null;
    635                         }
    636                     }
    637                     recipient = new InternalDeathRecipient(
    638                             this, tsListener, locale, scListener, uid, bundle);
    639                     scListener.asBinder().linkToDeath(recipient, 0);
    640                     mListeners.add(recipient);
    641                 } catch(RemoteException e) {
    642                     // do nothing
    643                 }
    644                 cleanLocked();
    645             }
    646             return recipient;
    647         }
    648 
    649         public void removeListener(ISpellCheckerSessionListener listener) {
    650             if (DBG) {
    651                 Slog.w(TAG, "remove listener: " + listener.hashCode());
    652             }
    653             synchronized(mSpellCheckerMap) {
    654                 final int size = mListeners.size();
    655                 final ArrayList<InternalDeathRecipient> removeList =
    656                         new ArrayList<InternalDeathRecipient>();
    657                 for (int i = 0; i < size; ++i) {
    658                     final InternalDeathRecipient tempRecipient = mListeners.get(i);
    659                     if(tempRecipient.hasSpellCheckerListener(listener)) {
    660                         if (DBG) {
    661                             Slog.w(TAG, "found existing listener.");
    662                         }
    663                         removeList.add(tempRecipient);
    664                     }
    665                 }
    666                 final int removeSize = removeList.size();
    667                 for (int i = 0; i < removeSize; ++i) {
    668                     if (DBG) {
    669                         Slog.w(TAG, "Remove " + removeList.get(i));
    670                     }
    671                     final InternalDeathRecipient idr = removeList.get(i);
    672                     idr.mScListener.asBinder().unlinkToDeath(idr, 0);
    673                     mListeners.remove(idr);
    674                 }
    675                 cleanLocked();
    676             }
    677         }
    678 
    679         // cleanLocked may remove elements from mSpellCheckerBindGroups
    680         private void cleanLocked() {
    681             if (DBG) {
    682                 Slog.d(TAG, "cleanLocked");
    683             }
    684             // If there are no more active listeners, clean up.  Only do this
    685             // once.
    686             if (mBound && mListeners.isEmpty()) {
    687                 mBound = false;
    688                 final String sciId = mInternalConnection.mSciId;
    689                 SpellCheckerBindGroup cur = mSpellCheckerBindGroups.get(sciId);
    690                 if (cur == this) {
    691                     if (DBG) {
    692                         Slog.d(TAG, "Remove bind group.");
    693                     }
    694                     mSpellCheckerBindGroups.remove(sciId);
    695                 }
    696                 mContext.unbindService(mInternalConnection);
    697             }
    698         }
    699 
    700         public void removeAll() {
    701             Slog.e(TAG, "Remove the spell checker bind unexpectedly.");
    702             synchronized(mSpellCheckerMap) {
    703                 final int size = mListeners.size();
    704                 for (int i = 0; i < size; ++i) {
    705                     final InternalDeathRecipient idr = mListeners.get(i);
    706                     idr.mScListener.asBinder().unlinkToDeath(idr, 0);
    707                 }
    708                 mListeners.clear();
    709                 cleanLocked();
    710             }
    711         }
    712     }
    713 
    714     private class InternalServiceConnection implements ServiceConnection {
    715         private final String mSciId;
    716         private final String mLocale;
    717         private final Bundle mBundle;
    718         public InternalServiceConnection(
    719                 String id, String locale, Bundle bundle) {
    720             mSciId = id;
    721             mLocale = locale;
    722             mBundle = bundle;
    723         }
    724 
    725         @Override
    726         public void onServiceConnected(ComponentName name, IBinder service) {
    727             synchronized(mSpellCheckerMap) {
    728                 if (DBG) {
    729                     Slog.w(TAG, "onServiceConnected: " + name);
    730                 }
    731                 ISpellCheckerService spellChecker = ISpellCheckerService.Stub.asInterface(service);
    732                 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
    733                 if (group != null && this == group.mInternalConnection) {
    734                     group.onServiceConnected(spellChecker);
    735                 }
    736             }
    737         }
    738 
    739         @Override
    740         public void onServiceDisconnected(ComponentName name) {
    741             synchronized(mSpellCheckerMap) {
    742                 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
    743                 if (group != null && this == group.mInternalConnection) {
    744                     mSpellCheckerBindGroups.remove(mSciId);
    745                 }
    746             }
    747         }
    748     }
    749 
    750     private class InternalDeathRecipient implements IBinder.DeathRecipient {
    751         public final ITextServicesSessionListener mTsListener;
    752         public final ISpellCheckerSessionListener mScListener;
    753         public final String mScLocale;
    754         private final SpellCheckerBindGroup mGroup;
    755         public final int mUid;
    756         public final Bundle mBundle;
    757         public InternalDeathRecipient(SpellCheckerBindGroup group,
    758                 ITextServicesSessionListener tsListener, String scLocale,
    759                 ISpellCheckerSessionListener scListener, int uid, Bundle bundle) {
    760             mTsListener = tsListener;
    761             mScListener = scListener;
    762             mScLocale = scLocale;
    763             mGroup = group;
    764             mUid = uid;
    765             mBundle = bundle;
    766         }
    767 
    768         public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) {
    769             return listener.asBinder().equals(mScListener.asBinder());
    770         }
    771 
    772         @Override
    773         public void binderDied() {
    774             mGroup.removeListener(mScListener);
    775         }
    776     }
    777 }
    778