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