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