TextServicesManagerService.java revision 00d2d4125e4944ebcabdecd423573ee00f716293
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 17package com.android.server; 18 19import com.android.internal.content.PackageMonitor; 20import com.android.internal.textservice.ISpellCheckerService; 21import com.android.internal.textservice.ISpellCheckerSession; 22import com.android.internal.textservice.ISpellCheckerSessionListener; 23import com.android.internal.textservice.ITextServicesManager; 24import com.android.internal.textservice.ITextServicesSessionListener; 25 26import org.xmlpull.v1.XmlPullParserException; 27 28import android.app.ActivityManagerNative; 29import android.app.AppGlobals; 30import android.app.IUserSwitchObserver; 31import android.content.ComponentName; 32import android.content.ContentResolver; 33import android.content.Context; 34import android.content.Intent; 35import android.content.ServiceConnection; 36import android.content.pm.IPackageManager; 37import android.content.pm.PackageManager; 38import android.content.pm.ResolveInfo; 39import android.content.pm.ServiceInfo; 40import android.os.Binder; 41import android.os.Bundle; 42import android.os.IBinder; 43import android.os.IRemoteCallback; 44import android.os.Process; 45import android.os.RemoteException; 46import android.os.UserHandle; 47import android.provider.Settings; 48import android.service.textservice.SpellCheckerService; 49import android.text.TextUtils; 50import android.util.Slog; 51import android.view.inputmethod.InputMethodManager; 52import android.view.inputmethod.InputMethodSubtype; 53import android.view.textservice.SpellCheckerInfo; 54import android.view.textservice.SpellCheckerSubtype; 55 56import java.io.FileDescriptor; 57import java.io.IOException; 58import java.io.PrintWriter; 59import java.util.ArrayList; 60import java.util.HashMap; 61import java.util.List; 62import java.util.Map; 63import java.util.concurrent.CopyOnWriteArrayList; 64 65public class TextServicesManagerService extends ITextServicesManager.Stub { 66 private static final String TAG = TextServicesManagerService.class.getSimpleName(); 67 private static final boolean DBG = false; 68 69 private final Context mContext; 70 private boolean mSystemReady; 71 private final TextServicesMonitor mMonitor; 72 private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap = 73 new HashMap<String, SpellCheckerInfo>(); 74 private final ArrayList<SpellCheckerInfo> mSpellCheckerList = new ArrayList<SpellCheckerInfo>(); 75 private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups = 76 new HashMap<String, SpellCheckerBindGroup>(); 77 private final TextServicesSettings mSettings; 78 79 public void systemReady() { 80 if (!mSystemReady) { 81 mSystemReady = true; 82 } 83 } 84 85 public TextServicesManagerService(Context context) { 86 mSystemReady = false; 87 mContext = context; 88 int userId = UserHandle.USER_OWNER; 89 try { 90 ActivityManagerNative.getDefault().registerUserSwitchObserver( 91 new IUserSwitchObserver.Stub() { 92 @Override 93 public void onUserSwitching(int newUserId, IRemoteCallback reply) { 94 synchronized(mSpellCheckerMap) { 95 switchUserLocked(newUserId); 96 } 97 if (reply != null) { 98 try { 99 reply.sendResult(null); 100 } catch (RemoteException e) { 101 } 102 } 103 } 104 105 @Override 106 public void onUserSwitchComplete(int newUserId) throws RemoteException { 107 } 108 }); 109 userId = ActivityManagerNative.getDefault().getCurrentUser().id; 110 } catch (RemoteException e) { 111 Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e); 112 } 113 mMonitor = new TextServicesMonitor(); 114 mMonitor.register(context, null, true); 115 mSettings = new TextServicesSettings(context.getContentResolver(), userId); 116 117 // "switchUserLocked" initializes the states for the foreground user 118 switchUserLocked(userId); 119 } 120 121 private void switchUserLocked(int userId) { 122 mSettings.setCurrentUserId(userId); 123 unbindServiceLocked(); 124 buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap, mSettings); 125 SpellCheckerInfo sci = getCurrentSpellChecker(null); 126 if (sci == null) { 127 sci = findAvailSpellCheckerLocked(null, null); 128 if (sci != null) { 129 // Set the current spell checker if there is one or more spell checkers 130 // available. In this case, "sci" is the first one in the available spell 131 // checkers. 132 setCurrentSpellCheckerLocked(sci.getId()); 133 } 134 } 135 } 136 137 private class TextServicesMonitor extends PackageMonitor { 138 private boolean isChangingPackagesOfCurrentUser() { 139 final int userId = getChangingUserId(); 140 final boolean retval = userId == mSettings.getCurrentUserId(); 141 if (DBG) { 142 Slog.d(TAG, "--- ignore this call back from a background user: " + userId); 143 } 144 return retval; 145 } 146 147 @Override 148 public void onSomePackagesChanged() { 149 if (!isChangingPackagesOfCurrentUser()) { 150 return; 151 } 152 synchronized (mSpellCheckerMap) { 153 buildSpellCheckerMapLocked( 154 mContext, mSpellCheckerList, mSpellCheckerMap, mSettings); 155 // TODO: Update for each locale 156 SpellCheckerInfo sci = getCurrentSpellChecker(null); 157 if (sci == null) return; 158 final String packageName = sci.getPackageName(); 159 final int change = isPackageDisappearing(packageName); 160 if (// Package disappearing 161 change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE 162 // Package modified 163 || isPackageModified(packageName)) { 164 sci = findAvailSpellCheckerLocked(null, packageName); 165 if (sci != null) { 166 setCurrentSpellCheckerLocked(sci.getId()); 167 } 168 } 169 } 170 } 171 } 172 173 private static void buildSpellCheckerMapLocked(Context context, 174 ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map, 175 TextServicesSettings settings) { 176 list.clear(); 177 map.clear(); 178 final PackageManager pm = context.getPackageManager(); 179 final List<ResolveInfo> services = pm.queryIntentServicesAsUser( 180 new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA, 181 settings.getCurrentUserId()); 182 final int N = services.size(); 183 for (int i = 0; i < N; ++i) { 184 final ResolveInfo ri = services.get(i); 185 final ServiceInfo si = ri.serviceInfo; 186 final ComponentName compName = new ComponentName(si.packageName, si.name); 187 if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) { 188 Slog.w(TAG, "Skipping text service " + compName 189 + ": it does not require the permission " 190 + android.Manifest.permission.BIND_TEXT_SERVICE); 191 continue; 192 } 193 if (DBG) Slog.d(TAG, "Add: " + compName); 194 try { 195 final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri); 196 if (sci.getSubtypeCount() <= 0) { 197 Slog.w(TAG, "Skipping text service " + compName 198 + ": it does not contain subtypes."); 199 continue; 200 } 201 list.add(sci); 202 map.put(sci.getId(), sci); 203 } catch (XmlPullParserException e) { 204 Slog.w(TAG, "Unable to load the spell checker " + compName, e); 205 } catch (IOException e) { 206 Slog.w(TAG, "Unable to load the spell checker " + compName, e); 207 } 208 } 209 if (DBG) { 210 Slog.d(TAG, "buildSpellCheckerMapLocked: " + list.size() + "," + map.size()); 211 } 212 } 213 214 // --------------------------------------------------------------------------------------- 215 // Check whether or not this is a valid IPC. Assumes an IPC is valid when either 216 // 1) it comes from the system process 217 // 2) the calling process' user id is identical to the current user id TSMS thinks. 218 private boolean calledFromValidUser() { 219 final int uid = Binder.getCallingUid(); 220 final int userId = UserHandle.getUserId(uid); 221 if (DBG) { 222 Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? " 223 + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID 224 + " calling userId = " + userId + ", foreground user id = " 225 + mSettings.getCurrentUserId()); 226 try { 227 final String[] packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid); 228 for (int i = 0; i < packageNames.length; ++i) { 229 if (DBG) { 230 Slog.d(TAG, "--- process name for "+ uid + " = " + packageNames[i]); 231 } 232 } 233 } catch (RemoteException e) { 234 } 235 } 236 237 if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) { 238 return true; 239 } else { 240 Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace()); 241 return false; 242 } 243 } 244 245 private boolean bindCurrentSpellCheckerService( 246 Intent service, ServiceConnection conn, int flags) { 247 if (service == null || conn == null) { 248 Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn); 249 return false; 250 } 251 return mContext.bindService(service, conn, flags, mSettings.getCurrentUserId()); 252 } 253 254 private void unbindServiceLocked() { 255 for (SpellCheckerBindGroup scbg : mSpellCheckerBindGroups.values()) { 256 scbg.removeAll(); 257 } 258 mSpellCheckerBindGroups.clear(); 259 } 260 261 // TODO: find an appropriate spell checker for specified locale 262 private SpellCheckerInfo findAvailSpellCheckerLocked(String locale, String prefPackage) { 263 final int spellCheckersCount = mSpellCheckerList.size(); 264 if (spellCheckersCount == 0) { 265 Slog.w(TAG, "no available spell checker services found"); 266 return null; 267 } 268 if (prefPackage != null) { 269 for (int i = 0; i < spellCheckersCount; ++i) { 270 final SpellCheckerInfo sci = mSpellCheckerList.get(i); 271 if (prefPackage.equals(sci.getPackageName())) { 272 if (DBG) { 273 Slog.d(TAG, "findAvailSpellCheckerLocked: " + sci.getPackageName()); 274 } 275 return sci; 276 } 277 } 278 } 279 if (spellCheckersCount > 1) { 280 Slog.w(TAG, "more than one spell checker service found, picking first"); 281 } 282 return mSpellCheckerList.get(0); 283 } 284 285 // TODO: Save SpellCheckerService by supported languages. Currently only one spell 286 // checker is saved. 287 @Override 288 public SpellCheckerInfo getCurrentSpellChecker(String locale) { 289 // TODO: Make this work even for non-current users? 290 if (!calledFromValidUser()) { 291 return null; 292 } 293 synchronized (mSpellCheckerMap) { 294 final String curSpellCheckerId = mSettings.getSelectedSpellChecker(); 295 if (DBG) { 296 Slog.w(TAG, "getCurrentSpellChecker: " + curSpellCheckerId); 297 } 298 if (TextUtils.isEmpty(curSpellCheckerId)) { 299 return null; 300 } 301 return mSpellCheckerMap.get(curSpellCheckerId); 302 } 303 } 304 305 // TODO: Respect allowImplicitlySelectedSubtype 306 // TODO: Save SpellCheckerSubtype by supported languages by looking at "locale". 307 @Override 308 public SpellCheckerSubtype getCurrentSpellCheckerSubtype( 309 String locale, boolean allowImplicitlySelectedSubtype) { 310 // TODO: Make this work even for non-current users? 311 if (!calledFromValidUser()) { 312 return null; 313 } 314 synchronized (mSpellCheckerMap) { 315 final String subtypeHashCodeStr = mSettings.getSelectedSpellCheckerSubtype(); 316 if (DBG) { 317 Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCodeStr); 318 } 319 final SpellCheckerInfo sci = getCurrentSpellChecker(null); 320 if (sci == null || sci.getSubtypeCount() == 0) { 321 if (DBG) { 322 Slog.w(TAG, "Subtype not found."); 323 } 324 return null; 325 } 326 final int hashCode; 327 if (!TextUtils.isEmpty(subtypeHashCodeStr)) { 328 hashCode = Integer.valueOf(subtypeHashCodeStr); 329 } else { 330 hashCode = 0; 331 } 332 if (hashCode == 0 && !allowImplicitlySelectedSubtype) { 333 return null; 334 } 335 String candidateLocale = null; 336 if (hashCode == 0) { 337 // Spell checker language settings == "auto" 338 final InputMethodManager imm = 339 (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE); 340 if (imm != null) { 341 final InputMethodSubtype currentInputMethodSubtype = 342 imm.getCurrentInputMethodSubtype(); 343 if (currentInputMethodSubtype != null) { 344 final String localeString = currentInputMethodSubtype.getLocale(); 345 if (!TextUtils.isEmpty(localeString)) { 346 // 1. Use keyboard locale if available in the spell checker 347 candidateLocale = localeString; 348 } 349 } 350 } 351 if (candidateLocale == null) { 352 // 2. Use System locale if available in the spell checker 353 candidateLocale = mContext.getResources().getConfiguration().locale.toString(); 354 } 355 } 356 SpellCheckerSubtype candidate = null; 357 for (int i = 0; i < sci.getSubtypeCount(); ++i) { 358 final SpellCheckerSubtype scs = sci.getSubtypeAt(i); 359 if (hashCode == 0) { 360 final String scsLocale = scs.getLocale(); 361 if (candidateLocale.equals(scsLocale)) { 362 return scs; 363 } else if (candidate == null) { 364 if (candidateLocale.length() >= 2 && scsLocale.length() >= 2 365 && candidateLocale.startsWith(scsLocale)) { 366 // Fall back to the applicable language 367 candidate = scs; 368 } 369 } 370 } else if (scs.hashCode() == hashCode) { 371 if (DBG) { 372 Slog.w(TAG, "Return subtype " + scs.hashCode() + ", input= " + locale 373 + ", " + scs.getLocale()); 374 } 375 // 3. Use the user specified spell check language 376 return scs; 377 } 378 } 379 // 4. Fall back to the applicable language and return it if not null 380 // 5. Simply just return it even if it's null which means we could find no suitable 381 // spell check languages 382 return candidate; 383 } 384 } 385 386 @Override 387 public void getSpellCheckerService(String sciId, String locale, 388 ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener, 389 Bundle bundle) { 390 if (!calledFromValidUser()) { 391 return; 392 } 393 if (!mSystemReady) { 394 return; 395 } 396 if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) { 397 Slog.e(TAG, "getSpellCheckerService: Invalid input."); 398 return; 399 } 400 synchronized(mSpellCheckerMap) { 401 if (!mSpellCheckerMap.containsKey(sciId)) { 402 return; 403 } 404 final SpellCheckerInfo sci = mSpellCheckerMap.get(sciId); 405 final int uid = Binder.getCallingUid(); 406 if (mSpellCheckerBindGroups.containsKey(sciId)) { 407 final SpellCheckerBindGroup bindGroup = mSpellCheckerBindGroups.get(sciId); 408 if (bindGroup != null) { 409 final InternalDeathRecipient recipient = 410 mSpellCheckerBindGroups.get(sciId).addListener( 411 tsListener, locale, scListener, uid, bundle); 412 if (recipient == null) { 413 if (DBG) { 414 Slog.w(TAG, "Didn't create a death recipient."); 415 } 416 return; 417 } 418 if (bindGroup.mSpellChecker == null & bindGroup.mConnected) { 419 Slog.e(TAG, "The state of the spell checker bind group is illegal."); 420 bindGroup.removeAll(); 421 } else if (bindGroup.mSpellChecker != null) { 422 if (DBG) { 423 Slog.w(TAG, "Existing bind found. Return a spell checker session now. " 424 + "Listeners count = " + bindGroup.mListeners.size()); 425 } 426 try { 427 final ISpellCheckerSession session = 428 bindGroup.mSpellChecker.getISpellCheckerSession( 429 recipient.mScLocale, recipient.mScListener, bundle); 430 if (session != null) { 431 tsListener.onServiceConnected(session); 432 return; 433 } else { 434 if (DBG) { 435 Slog.w(TAG, "Existing bind already expired. "); 436 } 437 bindGroup.removeAll(); 438 } 439 } catch (RemoteException e) { 440 Slog.e(TAG, "Exception in getting spell checker session: " + e); 441 bindGroup.removeAll(); 442 } 443 } 444 } 445 } 446 final long ident = Binder.clearCallingIdentity(); 447 try { 448 startSpellCheckerServiceInnerLocked( 449 sci, locale, tsListener, scListener, uid, bundle); 450 } finally { 451 Binder.restoreCallingIdentity(ident); 452 } 453 } 454 return; 455 } 456 457 @Override 458 public boolean isSpellCheckerEnabled() { 459 if (!calledFromValidUser()) { 460 return false; 461 } 462 synchronized(mSpellCheckerMap) { 463 return isSpellCheckerEnabledLocked(); 464 } 465 } 466 467 private void startSpellCheckerServiceInnerLocked(SpellCheckerInfo info, String locale, 468 ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener, 469 int uid, Bundle bundle) { 470 if (DBG) { 471 Slog.w(TAG, "Start spell checker session inner locked."); 472 } 473 final String sciId = info.getId(); 474 final InternalServiceConnection connection = new InternalServiceConnection( 475 sciId, locale, bundle); 476 final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE); 477 serviceIntent.setComponent(info.getComponent()); 478 if (DBG) { 479 Slog.w(TAG, "bind service: " + info.getId()); 480 } 481 if (!bindCurrentSpellCheckerService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) { 482 Slog.e(TAG, "Failed to get a spell checker service."); 483 return; 484 } 485 final SpellCheckerBindGroup group = new SpellCheckerBindGroup( 486 connection, tsListener, locale, scListener, uid, bundle); 487 mSpellCheckerBindGroups.put(sciId, group); 488 } 489 490 @Override 491 public SpellCheckerInfo[] getEnabledSpellCheckers() { 492 // TODO: Make this work even for non-current users? 493 if (!calledFromValidUser()) { 494 return null; 495 } 496 if (DBG) { 497 Slog.d(TAG, "getEnabledSpellCheckers: " + mSpellCheckerList.size()); 498 for (int i = 0; i < mSpellCheckerList.size(); ++i) { 499 Slog.d(TAG, "EnabledSpellCheckers: " + mSpellCheckerList.get(i).getPackageName()); 500 } 501 } 502 return mSpellCheckerList.toArray(new SpellCheckerInfo[mSpellCheckerList.size()]); 503 } 504 505 @Override 506 public void finishSpellCheckerService(ISpellCheckerSessionListener listener) { 507 if (!calledFromValidUser()) { 508 return; 509 } 510 if (DBG) { 511 Slog.d(TAG, "FinishSpellCheckerService"); 512 } 513 synchronized(mSpellCheckerMap) { 514 final ArrayList<SpellCheckerBindGroup> removeList = 515 new ArrayList<SpellCheckerBindGroup>(); 516 for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) { 517 if (group == null) continue; 518 // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop. 519 removeList.add(group); 520 } 521 final int removeSize = removeList.size(); 522 for (int i = 0; i < removeSize; ++i) { 523 removeList.get(i).removeListener(listener); 524 } 525 } 526 } 527 528 @Override 529 public void setCurrentSpellChecker(String locale, String sciId) { 530 if (!calledFromValidUser()) { 531 return; 532 } 533 synchronized(mSpellCheckerMap) { 534 if (mContext.checkCallingOrSelfPermission( 535 android.Manifest.permission.WRITE_SECURE_SETTINGS) 536 != PackageManager.PERMISSION_GRANTED) { 537 throw new SecurityException( 538 "Requires permission " 539 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 540 } 541 setCurrentSpellCheckerLocked(sciId); 542 } 543 } 544 545 @Override 546 public void setCurrentSpellCheckerSubtype(String locale, int hashCode) { 547 if (!calledFromValidUser()) { 548 return; 549 } 550 synchronized(mSpellCheckerMap) { 551 if (mContext.checkCallingOrSelfPermission( 552 android.Manifest.permission.WRITE_SECURE_SETTINGS) 553 != PackageManager.PERMISSION_GRANTED) { 554 throw new SecurityException( 555 "Requires permission " 556 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 557 } 558 setCurrentSpellCheckerSubtypeLocked(hashCode); 559 } 560 } 561 562 @Override 563 public void setSpellCheckerEnabled(boolean enabled) { 564 if (!calledFromValidUser()) { 565 return; 566 } 567 synchronized(mSpellCheckerMap) { 568 if (mContext.checkCallingOrSelfPermission( 569 android.Manifest.permission.WRITE_SECURE_SETTINGS) 570 != PackageManager.PERMISSION_GRANTED) { 571 throw new SecurityException( 572 "Requires permission " 573 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 574 } 575 setSpellCheckerEnabledLocked(enabled); 576 } 577 } 578 579 private void setCurrentSpellCheckerLocked(String sciId) { 580 if (DBG) { 581 Slog.w(TAG, "setCurrentSpellChecker: " + sciId); 582 } 583 if (TextUtils.isEmpty(sciId) || !mSpellCheckerMap.containsKey(sciId)) return; 584 final SpellCheckerInfo currentSci = getCurrentSpellChecker(null); 585 if (currentSci != null && currentSci.getId().equals(sciId)) { 586 // Do nothing if the current spell checker is same as new spell checker. 587 return; 588 } 589 final long ident = Binder.clearCallingIdentity(); 590 try { 591 mSettings.putSelectedSpellChecker(sciId); 592 setCurrentSpellCheckerSubtypeLocked(0); 593 } finally { 594 Binder.restoreCallingIdentity(ident); 595 } 596 } 597 598 private void setCurrentSpellCheckerSubtypeLocked(int hashCode) { 599 if (DBG) { 600 Slog.w(TAG, "setCurrentSpellCheckerSubtype: " + hashCode); 601 } 602 final SpellCheckerInfo sci = getCurrentSpellChecker(null); 603 int tempHashCode = 0; 604 for (int i = 0; sci != null && i < sci.getSubtypeCount(); ++i) { 605 if(sci.getSubtypeAt(i).hashCode() == hashCode) { 606 tempHashCode = hashCode; 607 break; 608 } 609 } 610 final long ident = Binder.clearCallingIdentity(); 611 try { 612 mSettings.putSelectedSpellCheckerSubtype(tempHashCode); 613 } finally { 614 Binder.restoreCallingIdentity(ident); 615 } 616 } 617 618 private void setSpellCheckerEnabledLocked(boolean enabled) { 619 if (DBG) { 620 Slog.w(TAG, "setSpellCheckerEnabled: " + enabled); 621 } 622 final long ident = Binder.clearCallingIdentity(); 623 try { 624 mSettings.setSpellCheckerEnabled(enabled); 625 } finally { 626 Binder.restoreCallingIdentity(ident); 627 } 628 } 629 630 private boolean isSpellCheckerEnabledLocked() { 631 final long ident = Binder.clearCallingIdentity(); 632 try { 633 final boolean retval = mSettings.isSpellCheckerEnabled(); 634 if (DBG) { 635 Slog.w(TAG, "getSpellCheckerEnabled: " + retval); 636 } 637 return retval; 638 } finally { 639 Binder.restoreCallingIdentity(ident); 640 } 641 } 642 643 @Override 644 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 645 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 646 != PackageManager.PERMISSION_GRANTED) { 647 648 pw.println("Permission Denial: can't dump TextServicesManagerService from from pid=" 649 + Binder.getCallingPid() 650 + ", uid=" + Binder.getCallingUid()); 651 return; 652 } 653 654 synchronized(mSpellCheckerMap) { 655 pw.println("Current Text Services Manager state:"); 656 pw.println(" Spell Checker Map:"); 657 for (Map.Entry<String, SpellCheckerInfo> ent : mSpellCheckerMap.entrySet()) { 658 pw.print(" "); pw.print(ent.getKey()); pw.println(":"); 659 SpellCheckerInfo info = ent.getValue(); 660 pw.print(" "); pw.print("id="); pw.println(info.getId()); 661 pw.print(" "); pw.print("comp="); 662 pw.println(info.getComponent().toShortString()); 663 int NS = info.getSubtypeCount(); 664 for (int i=0; i<NS; i++) { 665 SpellCheckerSubtype st = info.getSubtypeAt(i); 666 pw.print(" "); pw.print("Subtype #"); pw.print(i); pw.println(":"); 667 pw.print(" "); pw.print("locale="); pw.println(st.getLocale()); 668 pw.print(" "); pw.print("extraValue="); 669 pw.println(st.getExtraValue()); 670 } 671 } 672 pw.println(""); 673 pw.println(" Spell Checker Bind Groups:"); 674 for (Map.Entry<String, SpellCheckerBindGroup> ent 675 : mSpellCheckerBindGroups.entrySet()) { 676 SpellCheckerBindGroup grp = ent.getValue(); 677 pw.print(" "); pw.print(ent.getKey()); pw.print(" "); 678 pw.print(grp); pw.println(":"); 679 pw.print(" "); pw.print("mInternalConnection="); 680 pw.println(grp.mInternalConnection); 681 pw.print(" "); pw.print("mSpellChecker="); 682 pw.println(grp.mSpellChecker); 683 pw.print(" "); pw.print("mBound="); pw.print(grp.mBound); 684 pw.print(" mConnected="); pw.println(grp.mConnected); 685 int NL = grp.mListeners.size(); 686 for (int i=0; i<NL; i++) { 687 InternalDeathRecipient listener = grp.mListeners.get(i); 688 pw.print(" "); pw.print("Listener #"); pw.print(i); pw.println(":"); 689 pw.print(" "); pw.print("mTsListener="); 690 pw.println(listener.mTsListener); 691 pw.print(" "); pw.print("mScListener="); 692 pw.println(listener.mScListener); 693 pw.print(" "); pw.print("mGroup="); 694 pw.println(listener.mGroup); 695 pw.print(" "); pw.print("mScLocale="); 696 pw.print(listener.mScLocale); 697 pw.print(" mUid="); pw.println(listener.mUid); 698 } 699 } 700 } 701 } 702 703 // SpellCheckerBindGroup contains active text service session listeners. 704 // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from 705 // mSpellCheckerBindGroups 706 private class SpellCheckerBindGroup { 707 private final String TAG = SpellCheckerBindGroup.class.getSimpleName(); 708 private final InternalServiceConnection mInternalConnection; 709 private final CopyOnWriteArrayList<InternalDeathRecipient> mListeners = 710 new CopyOnWriteArrayList<InternalDeathRecipient>(); 711 public boolean mBound; 712 public ISpellCheckerService mSpellChecker; 713 public boolean mConnected; 714 715 public SpellCheckerBindGroup(InternalServiceConnection connection, 716 ITextServicesSessionListener listener, String locale, 717 ISpellCheckerSessionListener scListener, int uid, Bundle bundle) { 718 mInternalConnection = connection; 719 mBound = true; 720 mConnected = false; 721 addListener(listener, locale, scListener, uid, bundle); 722 } 723 724 public void onServiceConnected(ISpellCheckerService spellChecker) { 725 if (DBG) { 726 Slog.d(TAG, "onServiceConnected"); 727 } 728 729 for (InternalDeathRecipient listener : mListeners) { 730 try { 731 final ISpellCheckerSession session = spellChecker.getISpellCheckerSession( 732 listener.mScLocale, listener.mScListener, listener.mBundle); 733 synchronized(mSpellCheckerMap) { 734 if (mListeners.contains(listener)) { 735 listener.mTsListener.onServiceConnected(session); 736 } 737 } 738 } catch (RemoteException e) { 739 Slog.e(TAG, "Exception in getting the spell checker session." 740 + "Reconnect to the spellchecker. ", e); 741 removeAll(); 742 return; 743 } 744 } 745 synchronized(mSpellCheckerMap) { 746 mSpellChecker = spellChecker; 747 mConnected = true; 748 } 749 } 750 751 public InternalDeathRecipient addListener(ITextServicesSessionListener tsListener, 752 String locale, ISpellCheckerSessionListener scListener, int uid, Bundle bundle) { 753 if (DBG) { 754 Slog.d(TAG, "addListener: " + locale); 755 } 756 InternalDeathRecipient recipient = null; 757 synchronized(mSpellCheckerMap) { 758 try { 759 final int size = mListeners.size(); 760 for (int i = 0; i < size; ++i) { 761 if (mListeners.get(i).hasSpellCheckerListener(scListener)) { 762 // do not add the lister if the group already contains this. 763 return null; 764 } 765 } 766 recipient = new InternalDeathRecipient( 767 this, tsListener, locale, scListener, uid, bundle); 768 scListener.asBinder().linkToDeath(recipient, 0); 769 mListeners.add(recipient); 770 } catch(RemoteException e) { 771 // do nothing 772 } 773 cleanLocked(); 774 } 775 return recipient; 776 } 777 778 public void removeListener(ISpellCheckerSessionListener listener) { 779 if (DBG) { 780 Slog.w(TAG, "remove listener: " + listener.hashCode()); 781 } 782 synchronized(mSpellCheckerMap) { 783 final int size = mListeners.size(); 784 final ArrayList<InternalDeathRecipient> removeList = 785 new ArrayList<InternalDeathRecipient>(); 786 for (int i = 0; i < size; ++i) { 787 final InternalDeathRecipient tempRecipient = mListeners.get(i); 788 if(tempRecipient.hasSpellCheckerListener(listener)) { 789 if (DBG) { 790 Slog.w(TAG, "found existing listener."); 791 } 792 removeList.add(tempRecipient); 793 } 794 } 795 final int removeSize = removeList.size(); 796 for (int i = 0; i < removeSize; ++i) { 797 if (DBG) { 798 Slog.w(TAG, "Remove " + removeList.get(i)); 799 } 800 final InternalDeathRecipient idr = removeList.get(i); 801 idr.mScListener.asBinder().unlinkToDeath(idr, 0); 802 mListeners.remove(idr); 803 } 804 cleanLocked(); 805 } 806 } 807 808 // cleanLocked may remove elements from mSpellCheckerBindGroups 809 private void cleanLocked() { 810 if (DBG) { 811 Slog.d(TAG, "cleanLocked"); 812 } 813 // If there are no more active listeners, clean up. Only do this 814 // once. 815 if (mBound && mListeners.isEmpty()) { 816 mBound = false; 817 final String sciId = mInternalConnection.mSciId; 818 SpellCheckerBindGroup cur = mSpellCheckerBindGroups.get(sciId); 819 if (cur == this) { 820 if (DBG) { 821 Slog.d(TAG, "Remove bind group."); 822 } 823 mSpellCheckerBindGroups.remove(sciId); 824 } 825 mContext.unbindService(mInternalConnection); 826 } 827 } 828 829 public void removeAll() { 830 Slog.e(TAG, "Remove the spell checker bind unexpectedly."); 831 synchronized(mSpellCheckerMap) { 832 final int size = mListeners.size(); 833 for (int i = 0; i < size; ++i) { 834 final InternalDeathRecipient idr = mListeners.get(i); 835 idr.mScListener.asBinder().unlinkToDeath(idr, 0); 836 } 837 mListeners.clear(); 838 cleanLocked(); 839 } 840 } 841 } 842 843 private class InternalServiceConnection implements ServiceConnection { 844 private final String mSciId; 845 private final String mLocale; 846 private final Bundle mBundle; 847 public InternalServiceConnection( 848 String id, String locale, Bundle bundle) { 849 mSciId = id; 850 mLocale = locale; 851 mBundle = bundle; 852 } 853 854 @Override 855 public void onServiceConnected(ComponentName name, IBinder service) { 856 synchronized(mSpellCheckerMap) { 857 onServiceConnectedInnerLocked(name, service); 858 } 859 } 860 861 private void onServiceConnectedInnerLocked(ComponentName name, IBinder service) { 862 if (DBG) { 863 Slog.w(TAG, "onServiceConnected: " + name); 864 } 865 final ISpellCheckerService spellChecker = 866 ISpellCheckerService.Stub.asInterface(service); 867 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId); 868 if (group != null && this == group.mInternalConnection) { 869 group.onServiceConnected(spellChecker); 870 } 871 } 872 873 @Override 874 public void onServiceDisconnected(ComponentName name) { 875 synchronized(mSpellCheckerMap) { 876 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId); 877 if (group != null && this == group.mInternalConnection) { 878 mSpellCheckerBindGroups.remove(mSciId); 879 } 880 } 881 } 882 } 883 884 private class InternalDeathRecipient implements IBinder.DeathRecipient { 885 public final ITextServicesSessionListener mTsListener; 886 public final ISpellCheckerSessionListener mScListener; 887 public final String mScLocale; 888 private final SpellCheckerBindGroup mGroup; 889 public final int mUid; 890 public final Bundle mBundle; 891 public InternalDeathRecipient(SpellCheckerBindGroup group, 892 ITextServicesSessionListener tsListener, String scLocale, 893 ISpellCheckerSessionListener scListener, int uid, Bundle bundle) { 894 mTsListener = tsListener; 895 mScListener = scListener; 896 mScLocale = scLocale; 897 mGroup = group; 898 mUid = uid; 899 mBundle = bundle; 900 } 901 902 public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) { 903 return listener.asBinder().equals(mScListener.asBinder()); 904 } 905 906 @Override 907 public void binderDied() { 908 mGroup.removeListener(mScListener); 909 } 910 } 911 912 private static class TextServicesSettings { 913 private final ContentResolver mResolver; 914 private int mCurrentUserId; 915 public TextServicesSettings(ContentResolver resolver, int userId) { 916 mResolver = resolver; 917 mCurrentUserId = userId; 918 } 919 920 public void setCurrentUserId(int userId) { 921 if (DBG) { 922 Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " 923 + userId + ", new ime = " + getSelectedSpellChecker()); 924 } 925 // TSMS settings are kept per user, so keep track of current user 926 mCurrentUserId = userId; 927 } 928 929 public int getCurrentUserId() { 930 return mCurrentUserId; 931 } 932 933 public void putSelectedSpellChecker(String sciId) { 934 Settings.Secure.putStringForUser(mResolver, 935 Settings.Secure.SELECTED_SPELL_CHECKER, sciId, mCurrentUserId); 936 } 937 938 public void putSelectedSpellCheckerSubtype(int hashCode) { 939 Settings.Secure.putStringForUser(mResolver, 940 Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, String.valueOf(hashCode), 941 mCurrentUserId); 942 } 943 944 public void setSpellCheckerEnabled(boolean enabled) { 945 Settings.Secure.putIntForUser(mResolver, 946 Settings.Secure.SPELL_CHECKER_ENABLED, enabled ? 1 : 0, mCurrentUserId); 947 } 948 949 public String getSelectedSpellChecker() { 950 return Settings.Secure.getStringForUser(mResolver, 951 Settings.Secure.SELECTED_SPELL_CHECKER, mCurrentUserId); 952 } 953 954 public String getSelectedSpellCheckerSubtype() { 955 return Settings.Secure.getStringForUser(mResolver, 956 Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, mCurrentUserId); 957 } 958 959 public boolean isSpellCheckerEnabled() { 960 return Settings.Secure.getIntForUser(mResolver, 961 Settings.Secure.SPELL_CHECKER_ENABLED, 1, mCurrentUserId) == 1; 962 } 963 } 964 965 // ---------------------------------------------------------------------- 966 // Utilities for debug 967 private static String getStackTrace() { 968 final StringBuilder sb = new StringBuilder(); 969 try { 970 throw new RuntimeException(); 971 } catch (RuntimeException e) { 972 final StackTraceElement[] frames = e.getStackTrace(); 973 // Start at 1 because the first frame is here and we don't care about it 974 for (int j = 1; j < frames.length; ++j) { 975 sb.append(frames[j].toString() + "\n"); 976 } 977 } 978 return sb.toString(); 979 } 980} 981