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