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