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 static android.view.textservice.TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER; 20 21import com.android.internal.annotations.GuardedBy; 22import com.android.internal.content.PackageMonitor; 23import com.android.internal.inputmethod.InputMethodUtils; 24import com.android.internal.textservice.ISpellCheckerService; 25import com.android.internal.textservice.ISpellCheckerServiceCallback; 26import com.android.internal.textservice.ISpellCheckerSession; 27import com.android.internal.textservice.ISpellCheckerSessionListener; 28import com.android.internal.textservice.ITextServicesManager; 29import com.android.internal.textservice.ITextServicesSessionListener; 30import com.android.internal.textservice.LazyIntToIntMap; 31import com.android.internal.util.DumpUtils; 32 33import org.xmlpull.v1.XmlPullParserException; 34 35import android.annotation.NonNull; 36import android.annotation.Nullable; 37import android.annotation.UserIdInt; 38import android.content.ComponentName; 39import android.content.ContentResolver; 40import android.content.Context; 41import android.content.Intent; 42import android.content.ServiceConnection; 43import android.content.pm.ApplicationInfo; 44import android.content.pm.PackageManager; 45import android.content.pm.ResolveInfo; 46import android.content.pm.ServiceInfo; 47import android.content.pm.UserInfo; 48import android.os.Binder; 49import android.os.Bundle; 50import android.os.IBinder; 51import android.os.RemoteCallbackList; 52import android.os.RemoteException; 53import android.os.UserHandle; 54import android.os.UserManager; 55import android.provider.Settings; 56import android.service.textservice.SpellCheckerService; 57import android.text.TextUtils; 58import android.util.Slog; 59import android.util.SparseArray; 60import android.view.inputmethod.InputMethodManager; 61import android.view.inputmethod.InputMethodSubtype; 62import android.view.textservice.SpellCheckerInfo; 63import android.view.textservice.SpellCheckerSubtype; 64 65import java.io.FileDescriptor; 66import java.io.IOException; 67import java.io.PrintWriter; 68import java.util.Arrays; 69import java.util.ArrayList; 70import java.util.HashMap; 71import java.util.List; 72import java.util.Locale; 73import java.util.Map; 74import java.util.function.Predicate; 75 76public class TextServicesManagerService extends ITextServicesManager.Stub { 77 private static final String TAG = TextServicesManagerService.class.getSimpleName(); 78 private static final boolean DBG = false; 79 80 private final Context mContext; 81 private final TextServicesMonitor mMonitor; 82 private final SparseArray<TextServicesData> mUserData = new SparseArray<>(); 83 @NonNull 84 private final UserManager mUserManager; 85 private final Object mLock = new Object(); 86 87 @NonNull 88 @GuardedBy("mLock") 89 private final LazyIntToIntMap mSpellCheckerOwnerUserIdMap; 90 91 private static class TextServicesData { 92 @UserIdInt 93 private final int mUserId; 94 private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap; 95 private final ArrayList<SpellCheckerInfo> mSpellCheckerList; 96 private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups; 97 private final Context mContext; 98 private final ContentResolver mResolver; 99 public int mUpdateCount = 0; 100 101 public TextServicesData(@UserIdInt int userId, @NonNull Context context) { 102 mUserId = userId; 103 mSpellCheckerMap = new HashMap<>(); 104 mSpellCheckerList = new ArrayList<>(); 105 mSpellCheckerBindGroups = new HashMap<>(); 106 mContext = context; 107 mResolver = context.getContentResolver(); 108 } 109 110 private void putString(final String key, final String str) { 111 Settings.Secure.putStringForUser(mResolver, key, str, mUserId); 112 } 113 114 @Nullable 115 private String getString(@NonNull final String key, @Nullable final String defaultValue) { 116 final String result; 117 result = Settings.Secure.getStringForUser(mResolver, key, mUserId); 118 return result != null ? result : defaultValue; 119 } 120 121 private void putInt(final String key, final int value) { 122 Settings.Secure.putIntForUser(mResolver, key, value, mUserId); 123 } 124 125 private int getInt(final String key, final int defaultValue) { 126 return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mUserId); 127 } 128 129 private boolean getBoolean(final String key, final boolean defaultValue) { 130 return getInt(key, defaultValue ? 1 : 0) == 1; 131 } 132 133 private void putSelectedSpellChecker(@Nullable String sciId) { 134 putString(Settings.Secure.SELECTED_SPELL_CHECKER, sciId); 135 } 136 137 private void putSelectedSpellCheckerSubtype(int hashCode) { 138 putInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, hashCode); 139 } 140 141 @NonNull 142 private String getSelectedSpellChecker() { 143 return getString(Settings.Secure.SELECTED_SPELL_CHECKER, ""); 144 } 145 146 public int getSelectedSpellCheckerSubtype(final int defaultValue) { 147 return getInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, defaultValue); 148 } 149 150 public boolean isSpellCheckerEnabled() { 151 return getBoolean(Settings.Secure.SPELL_CHECKER_ENABLED, true); 152 } 153 154 @Nullable 155 public SpellCheckerInfo getCurrentSpellChecker() { 156 final String curSpellCheckerId = getSelectedSpellChecker(); 157 if (TextUtils.isEmpty(curSpellCheckerId)) { 158 return null; 159 } 160 return mSpellCheckerMap.get(curSpellCheckerId); 161 } 162 163 public void setCurrentSpellChecker(@Nullable SpellCheckerInfo sci) { 164 if (sci != null) { 165 putSelectedSpellChecker(sci.getId()); 166 } else { 167 putSelectedSpellChecker(""); 168 } 169 putSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE); 170 } 171 172 private void initializeTextServicesData() { 173 if (DBG) { 174 Slog.d(TAG, "initializeTextServicesData for user: " + mUserId); 175 } 176 mSpellCheckerList.clear(); 177 mSpellCheckerMap.clear(); 178 mUpdateCount++; 179 final PackageManager pm = mContext.getPackageManager(); 180 // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the 181 // default behavior of PackageManager is exactly what we want. It by default picks up 182 // appropriate services depending on the unlock state for the specified user. 183 final List<ResolveInfo> services = pm.queryIntentServicesAsUser( 184 new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA, 185 mUserId); 186 final int N = services.size(); 187 for (int i = 0; i < N; ++i) { 188 final ResolveInfo ri = services.get(i); 189 final ServiceInfo si = ri.serviceInfo; 190 final ComponentName compName = new ComponentName(si.packageName, si.name); 191 if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) { 192 Slog.w(TAG, "Skipping text service " + compName 193 + ": it does not require the permission " 194 + android.Manifest.permission.BIND_TEXT_SERVICE); 195 continue; 196 } 197 if (DBG) Slog.d(TAG, "Add: " + compName + " for user: " + mUserId); 198 try { 199 final SpellCheckerInfo sci = new SpellCheckerInfo(mContext, ri); 200 if (sci.getSubtypeCount() <= 0) { 201 Slog.w(TAG, "Skipping text service " + compName 202 + ": it does not contain subtypes."); 203 continue; 204 } 205 mSpellCheckerList.add(sci); 206 mSpellCheckerMap.put(sci.getId(), sci); 207 } catch (XmlPullParserException e) { 208 Slog.w(TAG, "Unable to load the spell checker " + compName, e); 209 } catch (IOException e) { 210 Slog.w(TAG, "Unable to load the spell checker " + compName, e); 211 } 212 } 213 if (DBG) { 214 Slog.d(TAG, "initializeSpellCheckerMap: " + mSpellCheckerList.size() + "," 215 + mSpellCheckerMap.size()); 216 } 217 } 218 219 private void dump(PrintWriter pw) { 220 int spellCheckerIndex = 0; 221 pw.println(" User #" + mUserId); 222 pw.println(" Spell Checkers:"); 223 pw.println(" Spell Checkers: " + "mUpdateCount=" + mUpdateCount); 224 for (final SpellCheckerInfo info : mSpellCheckerMap.values()) { 225 pw.println(" Spell Checker #" + spellCheckerIndex); 226 info.dump(pw, " "); 227 ++spellCheckerIndex; 228 } 229 230 pw.println(""); 231 pw.println(" Spell Checker Bind Groups:"); 232 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = mSpellCheckerBindGroups; 233 for (final Map.Entry<String, SpellCheckerBindGroup> ent 234 : spellCheckerBindGroups.entrySet()) { 235 final SpellCheckerBindGroup grp = ent.getValue(); 236 pw.println(" " + ent.getKey() + " " + grp + ":"); 237 pw.println(" " + "mInternalConnection=" + grp.mInternalConnection); 238 pw.println(" " + "mSpellChecker=" + grp.mSpellChecker); 239 pw.println(" " + "mUnbindCalled=" + grp.mUnbindCalled); 240 pw.println(" " + "mConnected=" + grp.mConnected); 241 final int numPendingSessionRequests = grp.mPendingSessionRequests.size(); 242 for (int j = 0; j < numPendingSessionRequests; j++) { 243 final SessionRequest req = grp.mPendingSessionRequests.get(j); 244 pw.println(" " + "Pending Request #" + j + ":"); 245 pw.println(" " + "mTsListener=" + req.mTsListener); 246 pw.println(" " + "mScListener=" + req.mScListener); 247 pw.println( 248 " " + "mScLocale=" + req.mLocale + " mUid=" + req.mUid); 249 } 250 final int numOnGoingSessionRequests = grp.mOnGoingSessionRequests.size(); 251 for (int j = 0; j < numOnGoingSessionRequests; j++) { 252 final SessionRequest req = grp.mOnGoingSessionRequests.get(j); 253 pw.println(" " + "On going Request #" + j + ":"); 254 ++j; 255 pw.println(" " + "mTsListener=" + req.mTsListener); 256 pw.println(" " + "mScListener=" + req.mScListener); 257 pw.println( 258 " " + "mScLocale=" + req.mLocale + " mUid=" + req.mUid); 259 } 260 final int N = grp.mListeners.getRegisteredCallbackCount(); 261 for (int j = 0; j < N; j++) { 262 final ISpellCheckerSessionListener mScListener = 263 grp.mListeners.getRegisteredCallbackItem(j); 264 pw.println(" " + "Listener #" + j + ":"); 265 pw.println(" " + "mScListener=" + mScListener); 266 pw.println(" " + "mGroup=" + grp); 267 } 268 } 269 } 270 } 271 272 public static final class Lifecycle extends SystemService { 273 private TextServicesManagerService mService; 274 275 public Lifecycle(Context context) { 276 super(context); 277 mService = new TextServicesManagerService(context); 278 } 279 280 @Override 281 public void onStart() { 282 publishBinderService(Context.TEXT_SERVICES_MANAGER_SERVICE, mService); 283 } 284 285 @Override 286 public void onStopUser(@UserIdInt int userHandle) { 287 if (DBG) { 288 Slog.d(TAG, "onStopUser userId: " + userHandle); 289 } 290 mService.onStopUser(userHandle); 291 } 292 293 @Override 294 public void onUnlockUser(@UserIdInt int userHandle) { 295 if(DBG) { 296 Slog.d(TAG, "onUnlockUser userId: " + userHandle); 297 } 298 // Called on the system server's main looper thread. 299 // TODO: Dispatch this to a worker thread as needed. 300 mService.onUnlockUser(userHandle); 301 } 302 } 303 304 void onStopUser(@UserIdInt int userId) { 305 synchronized (mLock) { 306 // Clear user ID mapping table. 307 mSpellCheckerOwnerUserIdMap.delete(userId); 308 309 // Clean per-user data 310 TextServicesData tsd = mUserData.get(userId); 311 if (tsd == null) return; 312 313 unbindServiceLocked(tsd); // Remove bind groups first 314 mUserData.remove(userId); // This needs to be done after bind groups are all removed 315 } 316 } 317 318 void onUnlockUser(@UserIdInt int userId) { 319 synchronized (mLock) { 320 // Initialize internal state for the given user 321 initializeInternalStateLocked(userId); 322 } 323 } 324 325 public TextServicesManagerService(Context context) { 326 mContext = context; 327 mUserManager = mContext.getSystemService(UserManager.class); 328 mSpellCheckerOwnerUserIdMap = new LazyIntToIntMap(callingUserId -> { 329 if (DISABLE_PER_PROFILE_SPELL_CHECKER) { 330 final long token = Binder.clearCallingIdentity(); 331 try { 332 final UserInfo parent = mUserManager.getProfileParent(callingUserId); 333 return (parent != null) ? parent.id : callingUserId; 334 } finally { 335 Binder.restoreCallingIdentity(token); 336 } 337 } else { 338 return callingUserId; 339 } 340 }); 341 342 mMonitor = new TextServicesMonitor(); 343 mMonitor.register(context, null, UserHandle.ALL, true); 344 } 345 346 private void initializeInternalStateLocked(@UserIdInt int userId) { 347 // When DISABLE_PER_PROFILE_SPELL_CHECKER is true, we make sure here that work profile users 348 // will never have non-null TextServicesData for their user ID. 349 if (DISABLE_PER_PROFILE_SPELL_CHECKER 350 && userId != mSpellCheckerOwnerUserIdMap.get(userId)) { 351 return; 352 } 353 354 TextServicesData tsd = mUserData.get(userId); 355 if (tsd == null) { 356 tsd = new TextServicesData(userId, mContext); 357 mUserData.put(userId, tsd); 358 } 359 360 tsd.initializeTextServicesData(); 361 SpellCheckerInfo sci = tsd.getCurrentSpellChecker(); 362 if (sci == null) { 363 sci = findAvailSystemSpellCheckerLocked(null, tsd); 364 // Set the current spell checker if there is one or more system spell checkers 365 // available. In this case, "sci" is the first one in the available spell 366 // checkers. 367 setCurrentSpellCheckerLocked(sci, tsd); 368 } 369 } 370 371 private final class TextServicesMonitor extends PackageMonitor { 372 @Override 373 public void onSomePackagesChanged() { 374 int userId = getChangingUserId(); 375 if(DBG) { 376 Slog.d(TAG, "onSomePackagesChanged: " + userId); 377 } 378 379 synchronized (mLock) { 380 TextServicesData tsd = mUserData.get(userId); 381 if (tsd == null) return; 382 383 // TODO: Update for each locale 384 SpellCheckerInfo sci = tsd.getCurrentSpellChecker(); 385 tsd.initializeTextServicesData(); 386 // If spell checker is disabled, just return. The user should explicitly 387 // enable the spell checker. 388 if (!tsd.isSpellCheckerEnabled()) return; 389 390 if (sci == null) { 391 sci = findAvailSystemSpellCheckerLocked(null, tsd); 392 // Set the current spell checker if there is one or more system spell checkers 393 // available. In this case, "sci" is the first one in the available spell 394 // checkers. 395 setCurrentSpellCheckerLocked(sci, tsd); 396 } else { 397 final String packageName = sci.getPackageName(); 398 final int change = isPackageDisappearing(packageName); 399 if (DBG) Slog.d(TAG, "Changing package name: " + packageName); 400 if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) { 401 SpellCheckerInfo availSci = 402 findAvailSystemSpellCheckerLocked(packageName, tsd); 403 // Set the spell checker settings if different than before 404 if (availSci == null 405 || (availSci != null && !availSci.getId().equals(sci.getId()))) { 406 setCurrentSpellCheckerLocked(availSci, tsd); 407 } 408 } 409 } 410 } 411 } 412 } 413 414 private boolean bindCurrentSpellCheckerService( 415 Intent service, ServiceConnection conn, int flags, @UserIdInt int userId) { 416 if (service == null || conn == null) { 417 Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn + 418 ", userId =" + userId); 419 return false; 420 } 421 return mContext.bindServiceAsUser(service, conn, flags, UserHandle.of(userId)); 422 } 423 424 private void unbindServiceLocked(TextServicesData tsd) { 425 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = tsd.mSpellCheckerBindGroups; 426 for (SpellCheckerBindGroup scbg : spellCheckerBindGroups.values()) { 427 scbg.removeAllLocked(); 428 } 429 spellCheckerBindGroups.clear(); 430 } 431 432 private SpellCheckerInfo findAvailSystemSpellCheckerLocked(String prefPackage, 433 TextServicesData tsd) { 434 // Filter the spell checker list to remove spell checker services that are not pre-installed 435 ArrayList<SpellCheckerInfo> spellCheckerList = new ArrayList<>(); 436 for (SpellCheckerInfo sci : tsd.mSpellCheckerList) { 437 if ((sci.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 438 spellCheckerList.add(sci); 439 } 440 } 441 442 final int spellCheckersCount = spellCheckerList.size(); 443 if (spellCheckersCount == 0) { 444 Slog.w(TAG, "no available spell checker services found"); 445 return null; 446 } 447 if (prefPackage != null) { 448 for (int i = 0; i < spellCheckersCount; ++i) { 449 final SpellCheckerInfo sci = spellCheckerList.get(i); 450 if (prefPackage.equals(sci.getPackageName())) { 451 if (DBG) { 452 Slog.d(TAG, "findAvailSystemSpellCheckerLocked: " + sci.getPackageName()); 453 } 454 return sci; 455 } 456 } 457 } 458 459 // Look up a spell checker based on the system locale. 460 // TODO: Still there is a room to improve in the following logic: e.g., check if the package 461 // is pre-installed or not. 462 final Locale systemLocal = mContext.getResources().getConfiguration().locale; 463 final ArrayList<Locale> suitableLocales = 464 InputMethodUtils.getSuitableLocalesForSpellChecker(systemLocal); 465 if (DBG) { 466 Slog.w(TAG, "findAvailSystemSpellCheckerLocked suitableLocales=" 467 + Arrays.toString(suitableLocales.toArray(new Locale[suitableLocales.size()]))); 468 } 469 final int localeCount = suitableLocales.size(); 470 for (int localeIndex = 0; localeIndex < localeCount; ++localeIndex) { 471 final Locale locale = suitableLocales.get(localeIndex); 472 for (int spellCheckersIndex = 0; spellCheckersIndex < spellCheckersCount; 473 ++spellCheckersIndex) { 474 final SpellCheckerInfo info = spellCheckerList.get(spellCheckersIndex); 475 final int subtypeCount = info.getSubtypeCount(); 476 for (int subtypeIndex = 0; subtypeIndex < subtypeCount; ++subtypeIndex) { 477 final SpellCheckerSubtype subtype = info.getSubtypeAt(subtypeIndex); 478 final Locale subtypeLocale = InputMethodUtils.constructLocaleFromString( 479 subtype.getLocale()); 480 if (locale.equals(subtypeLocale)) { 481 // TODO: We may have more spell checkers that fall into this category. 482 // Ideally we should pick up the most suitable one instead of simply 483 // returning the first found one. 484 return info; 485 } 486 } 487 } 488 } 489 490 if (spellCheckersCount > 1) { 491 Slog.w(TAG, "more than one spell checker service found, picking first"); 492 } 493 return spellCheckerList.get(0); 494 } 495 496 // TODO: Save SpellCheckerService by supported languages. Currently only one spell 497 // checker is saved. 498 @Override 499 public SpellCheckerInfo getCurrentSpellChecker(String locale) { 500 int userId = UserHandle.getCallingUserId(); 501 synchronized (mLock) { 502 final TextServicesData tsd = getDataFromCallingUserIdLocked(userId); 503 if (tsd == null) return null; 504 505 return tsd.getCurrentSpellChecker(); 506 } 507 } 508 509 // TODO: Respect allowImplicitlySelectedSubtype 510 // TODO: Save SpellCheckerSubtype by supported languages by looking at "locale". 511 @Override 512 public SpellCheckerSubtype getCurrentSpellCheckerSubtype( 513 String locale, boolean allowImplicitlySelectedSubtype) { 514 final int subtypeHashCode; 515 final SpellCheckerInfo sci; 516 final Locale systemLocale; 517 final int userId = UserHandle.getCallingUserId(); 518 519 synchronized (mLock) { 520 final TextServicesData tsd = getDataFromCallingUserIdLocked(userId); 521 if (tsd == null) return null; 522 523 subtypeHashCode = 524 tsd.getSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE); 525 if (DBG) { 526 Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCode); 527 } 528 sci = tsd.getCurrentSpellChecker(); 529 systemLocale = mContext.getResources().getConfiguration().locale; 530 } 531 if (sci == null || sci.getSubtypeCount() == 0) { 532 if (DBG) { 533 Slog.w(TAG, "Subtype not found."); 534 } 535 return null; 536 } 537 if (subtypeHashCode == SpellCheckerSubtype.SUBTYPE_ID_NONE 538 && !allowImplicitlySelectedSubtype) { 539 return null; 540 } 541 String candidateLocale = null; 542 if (subtypeHashCode == 0) { 543 // Spell checker language settings == "auto" 544 final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); 545 if (imm != null) { 546 final InputMethodSubtype currentInputMethodSubtype = 547 imm.getCurrentInputMethodSubtype(); 548 if (currentInputMethodSubtype != null) { 549 final String localeString = currentInputMethodSubtype.getLocale(); 550 if (!TextUtils.isEmpty(localeString)) { 551 // 1. Use keyboard locale if available in the spell checker 552 candidateLocale = localeString; 553 } 554 } 555 } 556 if (candidateLocale == null) { 557 // 2. Use System locale if available in the spell checker 558 candidateLocale = systemLocale.toString(); 559 } 560 } 561 SpellCheckerSubtype candidate = null; 562 for (int i = 0; i < sci.getSubtypeCount(); ++i) { 563 final SpellCheckerSubtype scs = sci.getSubtypeAt(i); 564 if (subtypeHashCode == 0) { 565 final String scsLocale = scs.getLocale(); 566 if (candidateLocale.equals(scsLocale)) { 567 return scs; 568 } else if (candidate == null) { 569 if (candidateLocale.length() >= 2 && scsLocale.length() >= 2 570 && candidateLocale.startsWith(scsLocale)) { 571 // Fall back to the applicable language 572 candidate = scs; 573 } 574 } 575 } else if (scs.hashCode() == subtypeHashCode) { 576 if (DBG) { 577 Slog.w(TAG, "Return subtype " + scs.hashCode() + ", input= " + locale 578 + ", " + scs.getLocale()); 579 } 580 // 3. Use the user specified spell check language 581 return scs; 582 } 583 } 584 // 4. Fall back to the applicable language and return it if not null 585 // 5. Simply just return it even if it's null which means we could find no suitable 586 // spell check languages 587 return candidate; 588 } 589 590 @Override 591 public void getSpellCheckerService(String sciId, String locale, 592 ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener, 593 Bundle bundle) { 594 if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) { 595 Slog.e(TAG, "getSpellCheckerService: Invalid input."); 596 return; 597 } 598 int callingUserId = UserHandle.getCallingUserId(); 599 600 synchronized (mLock) { 601 final TextServicesData tsd = getDataFromCallingUserIdLocked(callingUserId); 602 if (tsd == null) return; 603 604 HashMap<String, SpellCheckerInfo> spellCheckerMap = tsd.mSpellCheckerMap; 605 if (!spellCheckerMap.containsKey(sciId)) { 606 return; 607 } 608 final SpellCheckerInfo sci = spellCheckerMap.get(sciId); 609 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = 610 tsd.mSpellCheckerBindGroups; 611 SpellCheckerBindGroup bindGroup = spellCheckerBindGroups.get(sciId); 612 final int uid = Binder.getCallingUid(); 613 if (bindGroup == null) { 614 final long ident = Binder.clearCallingIdentity(); 615 try { 616 bindGroup = startSpellCheckerServiceInnerLocked(sci, tsd); 617 } finally { 618 Binder.restoreCallingIdentity(ident); 619 } 620 if (bindGroup == null) { 621 // startSpellCheckerServiceInnerLocked failed. 622 return; 623 } 624 } 625 626 // Start getISpellCheckerSession async IPC, or just queue the request until the spell 627 // checker service is bound. 628 bindGroup.getISpellCheckerSessionOrQueueLocked( 629 new SessionRequest(uid, locale, tsListener, scListener, bundle)); 630 } 631 } 632 633 @Override 634 public boolean isSpellCheckerEnabled() { 635 int userId = UserHandle.getCallingUserId(); 636 637 synchronized (mLock) { 638 final TextServicesData tsd = getDataFromCallingUserIdLocked(userId); 639 if (tsd == null) return false; 640 641 return tsd.isSpellCheckerEnabled(); 642 } 643 } 644 645 @Nullable 646 private SpellCheckerBindGroup startSpellCheckerServiceInnerLocked(SpellCheckerInfo info, 647 TextServicesData tsd) { 648 if (DBG) { 649 Slog.w(TAG, "Start spell checker session inner locked."); 650 } 651 final String sciId = info.getId(); 652 final InternalServiceConnection connection = new InternalServiceConnection(sciId, 653 tsd.mSpellCheckerBindGroups); 654 final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE); 655 serviceIntent.setComponent(info.getComponent()); 656 if (DBG) { 657 Slog.w(TAG, "bind service: " + info.getId()); 658 } 659 if (!bindCurrentSpellCheckerService(serviceIntent, connection, 660 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT_BACKGROUND, tsd.mUserId)) { 661 Slog.e(TAG, "Failed to get a spell checker service."); 662 return null; 663 } 664 final SpellCheckerBindGroup group = new SpellCheckerBindGroup(connection); 665 666 tsd.mSpellCheckerBindGroups.put(sciId, group); 667 return group; 668 } 669 670 @Override 671 public SpellCheckerInfo[] getEnabledSpellCheckers() { 672 int callingUserId = UserHandle.getCallingUserId(); 673 674 synchronized (mLock) { 675 final TextServicesData tsd = getDataFromCallingUserIdLocked(callingUserId); 676 if (tsd == null) return null; 677 678 ArrayList<SpellCheckerInfo> spellCheckerList = tsd.mSpellCheckerList; 679 if (DBG) { 680 Slog.d(TAG, "getEnabledSpellCheckers: " + spellCheckerList.size()); 681 for (int i = 0; i < spellCheckerList.size(); ++i) { 682 Slog.d(TAG, 683 "EnabledSpellCheckers: " + spellCheckerList.get(i).getPackageName()); 684 } 685 } 686 return spellCheckerList.toArray(new SpellCheckerInfo[spellCheckerList.size()]); 687 } 688 } 689 690 @Override 691 public void finishSpellCheckerService(ISpellCheckerSessionListener listener) { 692 if (DBG) { 693 Slog.d(TAG, "FinishSpellCheckerService"); 694 } 695 int userId = UserHandle.getCallingUserId(); 696 697 synchronized (mLock) { 698 final TextServicesData tsd = getDataFromCallingUserIdLocked(userId); 699 if (tsd == null) return; 700 701 final ArrayList<SpellCheckerBindGroup> removeList = new ArrayList<>(); 702 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = 703 tsd.mSpellCheckerBindGroups; 704 for (SpellCheckerBindGroup group : spellCheckerBindGroups.values()) { 705 if (group == null) continue; 706 // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop. 707 removeList.add(group); 708 } 709 final int removeSize = removeList.size(); 710 for (int i = 0; i < removeSize; ++i) { 711 removeList.get(i).removeListener(listener); 712 } 713 } 714 } 715 716 private void setCurrentSpellCheckerLocked(@Nullable SpellCheckerInfo sci, TextServicesData tsd) { 717 final String sciId = (sci != null) ? sci.getId() : ""; 718 if (DBG) { 719 Slog.w(TAG, "setCurrentSpellChecker: " + sciId); 720 } 721 final long ident = Binder.clearCallingIdentity(); 722 try { 723 tsd.setCurrentSpellChecker(sci); 724 } finally { 725 Binder.restoreCallingIdentity(ident); 726 } 727 } 728 729 @Override 730 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 731 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 732 733 if (args.length == 0 || (args.length == 1 && args[0].equals("-a"))) { 734 // Dump all users' data 735 synchronized (mLock) { 736 pw.println("Current Text Services Manager state:"); 737 pw.println(" Users:"); 738 final int numOfUsers = mUserData.size(); 739 for (int i = 0; i < numOfUsers; i++) { 740 TextServicesData tsd = mUserData.valueAt(i); 741 tsd.dump(pw); 742 } 743 } 744 } else { // Dump a given user's data 745 if (args.length != 2 || !args[0].equals("--user")) { 746 pw.println("Invalid arguments to text services." ); 747 return; 748 } else { 749 int userId = Integer.parseInt(args[1]); 750 UserInfo userInfo = mUserManager.getUserInfo(userId); 751 if (userInfo == null) { 752 pw.println("Non-existent user."); 753 return; 754 } 755 TextServicesData tsd = mUserData.get(userId); 756 if (tsd == null) { 757 pw.println("User needs to unlock first." ); 758 return; 759 } 760 synchronized (mLock) { 761 pw.println("Current Text Services Manager state:"); 762 pw.println(" User " + userId + ":"); 763 tsd.dump(pw); 764 } 765 } 766 } 767 } 768 769 /** 770 * @param callingUserId user ID of the calling process 771 * @return {@link TextServicesData} for the given user. {@code null} if spell checker is not 772 * temporarily / permanently available for the specified user 773 */ 774 @Nullable 775 private TextServicesData getDataFromCallingUserIdLocked(@UserIdInt int callingUserId) { 776 final int spellCheckerOwnerUserId = mSpellCheckerOwnerUserIdMap.get(callingUserId); 777 final TextServicesData data = mUserData.get(spellCheckerOwnerUserId); 778 if (DISABLE_PER_PROFILE_SPELL_CHECKER) { 779 if (spellCheckerOwnerUserId != callingUserId) { 780 // Calling process is running under child profile. 781 if (data == null) { 782 return null; 783 } 784 final SpellCheckerInfo info = data.getCurrentSpellChecker(); 785 if (info == null) { 786 return null; 787 } 788 final ServiceInfo serviceInfo = info.getServiceInfo(); 789 if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 790 // To be conservative, non pre-installed spell checker services are not allowed 791 // to be used for child profiles. 792 return null; 793 } 794 } 795 } 796 return data; 797 } 798 799 private static final class SessionRequest { 800 public final int mUid; 801 @Nullable 802 public final String mLocale; 803 @NonNull 804 public final ITextServicesSessionListener mTsListener; 805 @NonNull 806 public final ISpellCheckerSessionListener mScListener; 807 @Nullable 808 public final Bundle mBundle; 809 810 SessionRequest(int uid, @Nullable String locale, 811 @NonNull ITextServicesSessionListener tsListener, 812 @NonNull ISpellCheckerSessionListener scListener, @Nullable Bundle bundle) { 813 mUid = uid; 814 mLocale = locale; 815 mTsListener = tsListener; 816 mScListener = scListener; 817 mBundle = bundle; 818 } 819 } 820 821 // SpellCheckerBindGroup contains active text service session listeners. 822 // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from 823 // mSpellCheckerBindGroups 824 private final class SpellCheckerBindGroup { 825 private final String TAG = SpellCheckerBindGroup.class.getSimpleName(); 826 private final InternalServiceConnection mInternalConnection; 827 private final InternalDeathRecipients mListeners; 828 private boolean mUnbindCalled; 829 private ISpellCheckerService mSpellChecker; 830 private boolean mConnected; 831 private final ArrayList<SessionRequest> mPendingSessionRequests = new ArrayList<>(); 832 private final ArrayList<SessionRequest> mOnGoingSessionRequests = new ArrayList<>(); 833 @NonNull 834 HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups; 835 836 837 public SpellCheckerBindGroup(InternalServiceConnection connection) { 838 mInternalConnection = connection; 839 mListeners = new InternalDeathRecipients(this); 840 mSpellCheckerBindGroups = connection.mSpellCheckerBindGroups; 841 } 842 843 public void onServiceConnectedLocked(ISpellCheckerService spellChecker) { 844 if (DBG) { 845 Slog.d(TAG, "onServiceConnectedLocked"); 846 } 847 848 if (mUnbindCalled) { 849 return; 850 } 851 mSpellChecker = spellChecker; 852 mConnected = true; 853 // Dispatch pending getISpellCheckerSession requests. 854 try { 855 final int size = mPendingSessionRequests.size(); 856 for (int i = 0; i < size; ++i) { 857 final SessionRequest request = mPendingSessionRequests.get(i); 858 mSpellChecker.getISpellCheckerSession( 859 request.mLocale, request.mScListener, request.mBundle, 860 new ISpellCheckerServiceCallbackBinder(this, request)); 861 mOnGoingSessionRequests.add(request); 862 } 863 mPendingSessionRequests.clear(); 864 } catch(RemoteException e) { 865 // The target spell checker service is not available. Better to reset the state. 866 removeAllLocked(); 867 } 868 cleanLocked(); 869 } 870 871 public void onServiceDisconnectedLocked() { 872 if (DBG) { 873 Slog.d(TAG, "onServiceDisconnectedLocked"); 874 } 875 876 mSpellChecker = null; 877 mConnected = false; 878 } 879 880 public void removeListener(ISpellCheckerSessionListener listener) { 881 if (DBG) { 882 Slog.w(TAG, "remove listener: " + listener.hashCode()); 883 } 884 synchronized (mLock) { 885 mListeners.unregister(listener); 886 final IBinder scListenerBinder = listener.asBinder(); 887 final Predicate<SessionRequest> removeCondition = 888 request -> request.mScListener.asBinder() == scListenerBinder; 889 mPendingSessionRequests.removeIf(removeCondition); 890 mOnGoingSessionRequests.removeIf(removeCondition); 891 cleanLocked(); 892 } 893 } 894 895 // cleanLocked may remove elements from mSpellCheckerBindGroups 896 private void cleanLocked() { 897 if (DBG) { 898 Slog.d(TAG, "cleanLocked"); 899 } 900 if (mUnbindCalled) { 901 return; 902 } 903 // If there are no more active listeners, clean up. Only do this once. 904 if (mListeners.getRegisteredCallbackCount() > 0) { 905 return; 906 } 907 if (!mPendingSessionRequests.isEmpty()) { 908 return; 909 } 910 if (!mOnGoingSessionRequests.isEmpty()) { 911 return; 912 } 913 final String sciId = mInternalConnection.mSciId; 914 final SpellCheckerBindGroup cur = mSpellCheckerBindGroups.get(sciId); 915 if (cur == this) { 916 if (DBG) { 917 Slog.d(TAG, "Remove bind group."); 918 } 919 mSpellCheckerBindGroups.remove(sciId); 920 } 921 mContext.unbindService(mInternalConnection); 922 mUnbindCalled = true; 923 } 924 925 public void removeAllLocked() { 926 Slog.e(TAG, "Remove the spell checker bind unexpectedly."); 927 final int size = mListeners.getRegisteredCallbackCount(); 928 for (int i = size - 1; i >= 0; --i) { 929 mListeners.unregister(mListeners.getRegisteredCallbackItem(i)); 930 } 931 mPendingSessionRequests.clear(); 932 mOnGoingSessionRequests.clear(); 933 cleanLocked(); 934 } 935 936 public void getISpellCheckerSessionOrQueueLocked(@NonNull SessionRequest request) { 937 if (mUnbindCalled) { 938 return; 939 } 940 mListeners.register(request.mScListener); 941 if (!mConnected) { 942 mPendingSessionRequests.add(request); 943 return; 944 } 945 try { 946 mSpellChecker.getISpellCheckerSession( 947 request.mLocale, request.mScListener, request.mBundle, 948 new ISpellCheckerServiceCallbackBinder(this, request)); 949 mOnGoingSessionRequests.add(request); 950 } catch(RemoteException e) { 951 // The target spell checker service is not available. Better to reset the state. 952 removeAllLocked(); 953 } 954 cleanLocked(); 955 } 956 957 void onSessionCreated(@Nullable final ISpellCheckerSession newSession, 958 @NonNull final SessionRequest request) { 959 synchronized (mLock) { 960 if (mUnbindCalled) { 961 return; 962 } 963 if (mOnGoingSessionRequests.remove(request)) { 964 try { 965 request.mTsListener.onServiceConnected(newSession); 966 } catch (RemoteException e) { 967 // Technically this can happen if the spell checker client app is already 968 // dead. We can just forget about this request; the request is already 969 // removed from mOnGoingSessionRequests and the death recipient listener is 970 // not yet added to mListeners. There is nothing to release further. 971 } 972 } 973 cleanLocked(); 974 } 975 } 976 } 977 978 private final class InternalServiceConnection implements ServiceConnection { 979 private final String mSciId; 980 @NonNull 981 private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups; 982 public InternalServiceConnection(String id, 983 @NonNull HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups) { 984 mSciId = id; 985 mSpellCheckerBindGroups = spellCheckerBindGroups; 986 } 987 988 @Override 989 public void onServiceConnected(ComponentName name, IBinder service) { 990 synchronized (mLock) { 991 onServiceConnectedInnerLocked(name, service); 992 } 993 } 994 995 private void onServiceConnectedInnerLocked(ComponentName name, IBinder service) { 996 if (DBG) { 997 Slog.w(TAG, "onServiceConnectedInnerLocked: " + name); 998 } 999 final ISpellCheckerService spellChecker = 1000 ISpellCheckerService.Stub.asInterface(service); 1001 1002 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId); 1003 if (group != null && this == group.mInternalConnection) { 1004 group.onServiceConnectedLocked(spellChecker); 1005 } 1006 } 1007 1008 @Override 1009 public void onServiceDisconnected(ComponentName name) { 1010 synchronized (mLock) { 1011 onServiceDisconnectedInnerLocked(name); 1012 } 1013 } 1014 1015 private void onServiceDisconnectedInnerLocked(ComponentName name) { 1016 if (DBG) { 1017 Slog.w(TAG, "onServiceDisconnectedInnerLocked: " + name); 1018 } 1019 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId); 1020 if (group != null && this == group.mInternalConnection) { 1021 group.onServiceDisconnectedLocked(); 1022 } 1023 } 1024 } 1025 1026 private static final class InternalDeathRecipients extends 1027 RemoteCallbackList<ISpellCheckerSessionListener> { 1028 private final SpellCheckerBindGroup mGroup; 1029 1030 public InternalDeathRecipients(SpellCheckerBindGroup group) { 1031 mGroup = group; 1032 } 1033 1034 @Override 1035 public void onCallbackDied(ISpellCheckerSessionListener listener) { 1036 mGroup.removeListener(listener); 1037 } 1038 } 1039 1040 private static final class ISpellCheckerServiceCallbackBinder 1041 extends ISpellCheckerServiceCallback.Stub { 1042 @NonNull 1043 private final SpellCheckerBindGroup mBindGroup; 1044 @NonNull 1045 private final SessionRequest mRequest; 1046 1047 ISpellCheckerServiceCallbackBinder(@NonNull final SpellCheckerBindGroup bindGroup, 1048 @NonNull final SessionRequest request) { 1049 mBindGroup = bindGroup; 1050 mRequest = request; 1051 } 1052 1053 @Override 1054 public void onSessionCreated(@Nullable ISpellCheckerSession newSession) { 1055 mBindGroup.onSessionCreated(newSession, mRequest); 1056 } 1057 } 1058} 1059