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