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