blob: 26a8cf74d245b5abc6592f41066d824abad8bb12 [file] [log] [blame]
satok988323c2011-06-22 16:38:13 +09001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
Yohei Yukawa3f8c5682018-03-01 13:10:23 -080019import static android.view.textservice.TextServicesManager.DISABLE_PER_PROFILE_SPELL_CHECKER;
20
21import com.android.internal.annotations.GuardedBy;
satok988323c2011-06-22 16:38:13 +090022import com.android.internal.content.PackageMonitor;
Yohei Yukawa174843a2015-06-26 18:02:54 -070023import com.android.internal.inputmethod.InputMethodUtils;
satok988323c2011-06-22 16:38:13 +090024import com.android.internal.textservice.ISpellCheckerService;
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -080025import com.android.internal.textservice.ISpellCheckerServiceCallback;
satok988323c2011-06-22 16:38:13 +090026import com.android.internal.textservice.ISpellCheckerSession;
27import com.android.internal.textservice.ISpellCheckerSessionListener;
28import com.android.internal.textservice.ITextServicesManager;
29import com.android.internal.textservice.ITextServicesSessionListener;
Yohei Yukawa3f8c5682018-03-01 13:10:23 -080030import com.android.internal.textservice.LazyIntToIntMap;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060031import com.android.internal.util.DumpUtils;
satok988323c2011-06-22 16:38:13 +090032
satok03b2ea12011-08-03 17:36:14 +090033import org.xmlpull.v1.XmlPullParserException;
34
Yohei Yukawaf0f16802016-03-08 16:04:58 -080035import android.annotation.NonNull;
Yohei Yukawa08ce1872016-03-16 17:22:30 -070036import android.annotation.Nullable;
Yohei Yukawa9faa2ae2016-03-07 13:42:07 -080037import android.annotation.UserIdInt;
satok988323c2011-06-22 16:38:13 +090038import android.content.ComponentName;
Satoshi Kataoka00d2d412012-09-28 20:32:33 +090039import android.content.ContentResolver;
satok988323c2011-06-22 16:38:13 +090040import android.content.Context;
41import android.content.Intent;
42import android.content.ServiceConnection;
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -070043import android.content.pm.ApplicationInfo;
satok988323c2011-06-22 16:38:13 +090044import android.content.pm.PackageManager;
45import android.content.pm.ResolveInfo;
46import android.content.pm.ServiceInfo;
Guliz Tuncay06a26242017-06-30 18:32:04 -070047import android.content.pm.UserInfo;
satok6be6d752011-07-28 20:40:38 +090048import android.os.Binder;
satok53578062011-08-03 16:08:59 +090049import android.os.Bundle;
satok988323c2011-06-22 16:38:13 +090050import android.os.IBinder;
Guliz Tuncayf982e752017-06-14 09:33:16 -070051import android.os.RemoteCallbackList;
satok988323c2011-06-22 16:38:13 +090052import android.os.RemoteException;
Satoshi Kataoka00d2d412012-09-28 20:32:33 +090053import android.os.UserHandle;
Yohei Yukawa095fa372014-10-27 14:02:23 +090054import android.os.UserManager;
satok988323c2011-06-22 16:38:13 +090055import android.provider.Settings;
satok988323c2011-06-22 16:38:13 +090056import android.service.textservice.SpellCheckerService;
satok53578062011-08-03 16:08:59 +090057import android.text.TextUtils;
satok988323c2011-06-22 16:38:13 +090058import android.util.Slog;
Guliz Tuncay06a26242017-06-30 18:32:04 -070059import android.util.SparseArray;
satok05f24702011-11-02 19:29:35 +090060import android.view.inputmethod.InputMethodManager;
61import android.view.inputmethod.InputMethodSubtype;
satok988323c2011-06-22 16:38:13 +090062import android.view.textservice.SpellCheckerInfo;
satokada8c4e2011-08-23 14:56:56 +090063import android.view.textservice.SpellCheckerSubtype;
satok988323c2011-06-22 16:38:13 +090064
Dianne Hackborn71e14da2011-10-16 16:28:10 -070065import java.io.FileDescriptor;
satok03b2ea12011-08-03 17:36:14 +090066import java.io.IOException;
Dianne Hackborn71e14da2011-10-16 16:28:10 -070067import java.io.PrintWriter;
Yohei Yukawa174843a2015-06-26 18:02:54 -070068import java.util.Arrays;
satok988323c2011-06-22 16:38:13 +090069import java.util.ArrayList;
70import java.util.HashMap;
satok988323c2011-06-22 16:38:13 +090071import java.util.List;
Yohei Yukawa174843a2015-06-26 18:02:54 -070072import java.util.Locale;
Dianne Hackborn71e14da2011-10-16 16:28:10 -070073import java.util.Map;
satok988323c2011-06-22 16:38:13 +090074
75public class TextServicesManagerService extends ITextServicesManager.Stub {
76 private static final String TAG = TextServicesManagerService.class.getSimpleName();
77 private static final boolean DBG = false;
78
79 private final Context mContext;
satok988323c2011-06-22 16:38:13 +090080 private final TextServicesMonitor mMonitor;
Guliz Tuncay06a26242017-06-30 18:32:04 -070081 private final SparseArray<TextServicesData> mUserData = new SparseArray<>();
Yohei Yukawaf0f16802016-03-08 16:04:58 -080082 @NonNull
83 private final UserManager mUserManager;
Guliz Tuncay10ae3852017-07-07 10:35:59 -070084 private final Object mLock = new Object();
satok988323c2011-06-22 16:38:13 +090085
Yohei Yukawa3f8c5682018-03-01 13:10:23 -080086 @NonNull
87 @GuardedBy("mLock")
88 private final LazyIntToIntMap mSpellCheckerOwnerUserIdMap;
89
Guliz Tuncay06a26242017-06-30 18:32:04 -070090 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;
Guliz Tuncay8e6fa022017-08-14 16:50:15 -070096 private final Context mContext;
97 private final ContentResolver mResolver;
98 public int mUpdateCount = 0;
Guliz Tuncay06a26242017-06-30 18:32:04 -070099
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700100 public TextServicesData(@UserIdInt int userId, @NonNull Context context) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700101 mUserId = userId;
102 mSpellCheckerMap = new HashMap<>();
103 mSpellCheckerList = new ArrayList<>();
104 mSpellCheckerBindGroups = new HashMap<>();
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700105 mContext = context;
106 mResolver = context.getContentResolver();
107 }
108
109 private void putString(final String key, final String str) {
110 Settings.Secure.putStringForUser(mResolver, key, str, mUserId);
111 }
112
113 @Nullable
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
120 private void putInt(final String key, final int value) {
121 Settings.Secure.putIntForUser(mResolver, key, value, mUserId);
122 }
123
124 private int getInt(final String key, final int defaultValue) {
125 return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mUserId);
126 }
127
128 private boolean getBoolean(final String key, final boolean defaultValue) {
129 return getInt(key, defaultValue ? 1 : 0) == 1;
130 }
131
132 private void putSelectedSpellChecker(@Nullable String sciId) {
Guliz Tuncay83a73302017-08-17 15:25:38 -0700133 putString(Settings.Secure.SELECTED_SPELL_CHECKER, sciId);
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700134 }
135
136 private void putSelectedSpellCheckerSubtype(int hashCode) {
137 putInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, hashCode);
138 }
139
140 @NonNull
141 private String getSelectedSpellChecker() {
142 return getString(Settings.Secure.SELECTED_SPELL_CHECKER, "");
143 }
144
145 public int getSelectedSpellCheckerSubtype(final int defaultValue) {
146 return getInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, defaultValue);
147 }
148
149 public boolean isSpellCheckerEnabled() {
150 return getBoolean(Settings.Secure.SPELL_CHECKER_ENABLED, true);
151 }
152
153 @Nullable
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
Guliz Tuncay83a73302017-08-17 15:25:38 -0700162 public void setCurrentSpellChecker(@Nullable SpellCheckerInfo sci) {
163 if (sci != null) {
164 putSelectedSpellChecker(sci.getId());
165 } else {
166 putSelectedSpellChecker("");
167 }
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700168 putSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE);
169 }
170
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
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(
Yohei Yukawae4622842018-02-26 11:31:47 +0900247 " " + "mScLocale=" + req.mLocale + " mUid=" + req.mUid);
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700248 }
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 ++j;
254 pw.println(" " + "mTsListener=" + req.mTsListener);
255 pw.println(" " + "mScListener=" + req.mScListener);
256 pw.println(
Yohei Yukawae4622842018-02-26 11:31:47 +0900257 " " + "mScLocale=" + req.mLocale + " mUid=" + req.mUid);
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700258 }
259 final int N = grp.mListeners.getRegisteredCallbackCount();
260 for (int j = 0; j < N; j++) {
261 final ISpellCheckerSessionListener mScListener =
262 grp.mListeners.getRegisteredCallbackItem(j);
263 pw.println(" " + "Listener #" + j + ":");
264 pw.println(" " + "mScListener=" + mScListener);
265 pw.println(" " + "mGroup=" + grp);
266 }
267 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700268 }
269 }
270
Yohei Yukawaa6a152e2016-03-07 13:41:15 -0800271 public static final class Lifecycle extends SystemService {
272 private TextServicesManagerService mService;
273
274 public Lifecycle(Context context) {
275 super(context);
276 mService = new TextServicesManagerService(context);
277 }
278
279 @Override
280 public void onStart() {
281 publishBinderService(Context.TEXT_SERVICES_MANAGER_SERVICE, mService);
282 }
283
284 @Override
Guliz Tuncay06a26242017-06-30 18:32:04 -0700285 public void onStopUser(@UserIdInt int userHandle) {
286 if (DBG) {
287 Slog.d(TAG, "onStopUser userId: " + userHandle);
288 }
289 mService.onStopUser(userHandle);
Yohei Yukawa9faa2ae2016-03-07 13:42:07 -0800290 }
291
292 @Override
Yohei Yukawaf0f16802016-03-08 16:04:58 -0800293 public void onUnlockUser(@UserIdInt int userHandle) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700294 if(DBG) {
295 Slog.d(TAG, "onUnlockUser userId: " + userHandle);
296 }
Yohei Yukawaf0f16802016-03-08 16:04:58 -0800297 // Called on the system server's main looper thread.
298 // TODO: Dispatch this to a worker thread as needed.
299 mService.onUnlockUser(userHandle);
300 }
Yohei Yukawaa6a152e2016-03-07 13:41:15 -0800301 }
302
Guliz Tuncay06a26242017-06-30 18:32:04 -0700303 void onStopUser(@UserIdInt int userId) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700304 synchronized (mLock) {
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800305 // Clear user ID mapping table.
306 mSpellCheckerOwnerUserIdMap.delete(userId);
307
Guliz Tuncay06a26242017-06-30 18:32:04 -0700308 // Clean per-user data
309 TextServicesData tsd = mUserData.get(userId);
310 if (tsd == null) return;
311
312 unbindServiceLocked(tsd); // Remove bind groups first
313 mUserData.remove(userId); // This needs to be done after bind groups are all removed
Yohei Yukawaf0f16802016-03-08 16:04:58 -0800314 }
315 }
316
317 void onUnlockUser(@UserIdInt int userId) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700318 synchronized (mLock) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700319 // Initialize internal state for the given user
320 initializeInternalStateLocked(userId);
satok988323c2011-06-22 16:38:13 +0900321 }
322 }
323
324 public TextServicesManagerService(Context context) {
satok988323c2011-06-22 16:38:13 +0900325 mContext = context;
Yohei Yukawaf0f16802016-03-08 16:04:58 -0800326 mUserManager = mContext.getSystemService(UserManager.class);
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800327 mSpellCheckerOwnerUserIdMap = new LazyIntToIntMap(callingUserId -> {
328 if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
329 final long token = Binder.clearCallingIdentity();
330 try {
331 final UserInfo parent = mUserManager.getProfileParent(callingUserId);
332 return (parent != null) ? parent.id : callingUserId;
333 } finally {
334 Binder.restoreCallingIdentity(token);
335 }
336 } else {
337 return callingUserId;
338 }
339 });
Yohei Yukawaf0f16802016-03-08 16:04:58 -0800340
satok988323c2011-06-22 16:38:13 +0900341 mMonitor = new TextServicesMonitor();
Guliz Tuncay06a26242017-06-30 18:32:04 -0700342 mMonitor.register(context, null, UserHandle.ALL, true);
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900343 }
344
Guliz Tuncay5e1d5d62017-07-26 11:19:08 -0700345 private void initializeInternalStateLocked(@UserIdInt int userId) {
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800346 // When DISABLE_PER_PROFILE_SPELL_CHECKER is true, we make sure here that work profile users
347 // will never have non-null TextServicesData for their user ID.
348 if (DISABLE_PER_PROFILE_SPELL_CHECKER
349 && userId != mSpellCheckerOwnerUserIdMap.get(userId)) {
350 return;
351 }
352
Guliz Tuncay06a26242017-06-30 18:32:04 -0700353 TextServicesData tsd = mUserData.get(userId);
354 if (tsd == null) {
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700355 tsd = new TextServicesData(userId, mContext);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700356 mUserData.put(userId, tsd);
357 }
358
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700359 tsd.initializeTextServicesData();
360 SpellCheckerInfo sci = tsd.getCurrentSpellChecker();
satokdf5659d2011-07-29 18:38:21 +0900361 if (sci == null) {
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700362 sci = findAvailSystemSpellCheckerLocked(null, tsd);
Guliz Tuncay83a73302017-08-17 15:25:38 -0700363 // Set the current spell checker if there is one or more system spell checkers
364 // available. In this case, "sci" is the first one in the available spell
365 // checkers.
366 setCurrentSpellCheckerLocked(sci, tsd);
satokdf5659d2011-07-29 18:38:21 +0900367 }
satok988323c2011-06-22 16:38:13 +0900368 }
369
Guliz Tuncay06a26242017-06-30 18:32:04 -0700370 private final class TextServicesMonitor extends PackageMonitor {
satok988323c2011-06-22 16:38:13 +0900371 @Override
372 public void onSomePackagesChanged() {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700373 int userId = getChangingUserId();
374 if(DBG) {
375 Slog.d(TAG, "onSomePackagesChanged: " + userId);
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900376 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700377
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700378 synchronized (mLock) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700379 TextServicesData tsd = mUserData.get(userId);
380 if (tsd == null) return;
381
satok988323c2011-06-22 16:38:13 +0900382 // TODO: Update for each locale
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700383 SpellCheckerInfo sci = tsd.getCurrentSpellChecker();
384 tsd.initializeTextServicesData();
Guliz Tuncay83a73302017-08-17 15:25:38 -0700385 // If spell checker is disabled, just return. The user should explicitly
Satoshi Kataoka02260e22013-08-02 16:22:04 +0900386 // enable the spell checker.
Guliz Tuncay83a73302017-08-17 15:25:38 -0700387 if (!tsd.isSpellCheckerEnabled()) return;
388
389 if (sci == null) {
390 sci = findAvailSystemSpellCheckerLocked(null, tsd);
391 // Set the current spell checker if there is one or more system spell checkers
392 // available. In this case, "sci" is the first one in the available spell
393 // checkers.
394 setCurrentSpellCheckerLocked(sci, tsd);
395 } else {
396 final String packageName = sci.getPackageName();
397 final int change = isPackageDisappearing(packageName);
398 if (DBG) Slog.d(TAG, "Changing package name: " + packageName);
399 if (// Package disappearing
400 change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE
401 // Package modified
402 || isPackageModified(packageName)) {
403 SpellCheckerInfo availSci =
404 findAvailSystemSpellCheckerLocked(packageName, tsd);
405 // Set the spell checker settings if different than before
406 if (availSci == null
407 || (availSci != null && !availSci.getId().equals(sci.getId()))) {
408 setCurrentSpellCheckerLocked(availSci, tsd);
409 }
satok5b9b5a92011-08-02 12:24:44 +0900410 }
satok988323c2011-06-22 16:38:13 +0900411 }
412 }
413 }
414 }
415
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900416 private boolean bindCurrentSpellCheckerService(
Guliz Tuncay06a26242017-06-30 18:32:04 -0700417 Intent service, ServiceConnection conn, int flags, @UserIdInt int userId) {
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900418 if (service == null || conn == null) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700419 Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn +
420 ", userId =" + userId);
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900421 return false;
422 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700423 return mContext.bindServiceAsUser(service, conn, flags, UserHandle.of(userId));
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900424 }
425
Guliz Tuncay06a26242017-06-30 18:32:04 -0700426 private void unbindServiceLocked(TextServicesData tsd) {
427 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = tsd.mSpellCheckerBindGroups;
428 for (SpellCheckerBindGroup scbg : spellCheckerBindGroups.values()) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700429 scbg.removeAllLocked();
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900430 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700431 spellCheckerBindGroups.clear();
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900432 }
433
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700434 private SpellCheckerInfo findAvailSystemSpellCheckerLocked(String prefPackage,
Guliz Tuncay06a26242017-06-30 18:32:04 -0700435 TextServicesData tsd) {
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700436 // Filter the spell checker list to remove spell checker services that are not pre-installed
437 ArrayList<SpellCheckerInfo> spellCheckerList = new ArrayList<>();
438 for (SpellCheckerInfo sci : tsd.mSpellCheckerList) {
439 if ((sci.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
440 spellCheckerList.add(sci);
441 }
442 }
443
Guliz Tuncay06a26242017-06-30 18:32:04 -0700444 final int spellCheckersCount = spellCheckerList.size();
satok988323c2011-06-22 16:38:13 +0900445 if (spellCheckersCount == 0) {
446 Slog.w(TAG, "no available spell checker services found");
447 return null;
448 }
449 if (prefPackage != null) {
450 for (int i = 0; i < spellCheckersCount; ++i) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700451 final SpellCheckerInfo sci = spellCheckerList.get(i);
satok988323c2011-06-22 16:38:13 +0900452 if (prefPackage.equals(sci.getPackageName())) {
satokda317ef2011-07-26 08:02:45 +0900453 if (DBG) {
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700454 Slog.d(TAG, "findAvailSystemSpellCheckerLocked: " + sci.getPackageName());
satokda317ef2011-07-26 08:02:45 +0900455 }
satok988323c2011-06-22 16:38:13 +0900456 return sci;
457 }
458 }
459 }
Yohei Yukawa174843a2015-06-26 18:02:54 -0700460
461 // Look up a spell checker based on the system locale.
462 // TODO: Still there is a room to improve in the following logic: e.g., check if the package
463 // is pre-installed or not.
464 final Locale systemLocal = mContext.getResources().getConfiguration().locale;
465 final ArrayList<Locale> suitableLocales =
466 InputMethodUtils.getSuitableLocalesForSpellChecker(systemLocal);
467 if (DBG) {
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700468 Slog.w(TAG, "findAvailSystemSpellCheckerLocked suitableLocales="
Yohei Yukawa174843a2015-06-26 18:02:54 -0700469 + Arrays.toString(suitableLocales.toArray(new Locale[suitableLocales.size()])));
470 }
471 final int localeCount = suitableLocales.size();
472 for (int localeIndex = 0; localeIndex < localeCount; ++localeIndex) {
473 final Locale locale = suitableLocales.get(localeIndex);
474 for (int spellCheckersIndex = 0; spellCheckersIndex < spellCheckersCount;
475 ++spellCheckersIndex) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700476 final SpellCheckerInfo info = spellCheckerList.get(spellCheckersIndex);
Yohei Yukawa174843a2015-06-26 18:02:54 -0700477 final int subtypeCount = info.getSubtypeCount();
478 for (int subtypeIndex = 0; subtypeIndex < subtypeCount; ++subtypeIndex) {
479 final SpellCheckerSubtype subtype = info.getSubtypeAt(subtypeIndex);
480 final Locale subtypeLocale = InputMethodUtils.constructLocaleFromString(
481 subtype.getLocale());
482 if (locale.equals(subtypeLocale)) {
483 // TODO: We may have more spell checkers that fall into this category.
484 // Ideally we should pick up the most suitable one instead of simply
485 // returning the first found one.
486 return info;
487 }
488 }
489 }
490 }
491
satok988323c2011-06-22 16:38:13 +0900492 if (spellCheckersCount > 1) {
493 Slog.w(TAG, "more than one spell checker service found, picking first");
494 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700495 return spellCheckerList.get(0);
satok988323c2011-06-22 16:38:13 +0900496 }
497
498 // TODO: Save SpellCheckerService by supported languages. Currently only one spell
499 // checker is saved.
500 @Override
501 public SpellCheckerInfo getCurrentSpellChecker(String locale) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700502 int userId = UserHandle.getCallingUserId();
503 synchronized (mLock) {
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800504 final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700505 if (tsd == null) return null;
506
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700507 return tsd.getCurrentSpellChecker();
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900508 }
Yohei Yukawa095fa372014-10-27 14:02:23 +0900509 }
510
satok3cb5b392011-08-26 11:55:21 +0900511 // TODO: Respect allowImplicitlySelectedSubtype
Satoshi Kataoka17150cf2012-05-30 20:05:44 +0900512 // TODO: Save SpellCheckerSubtype by supported languages by looking at "locale".
satokada8c4e2011-08-23 14:56:56 +0900513 @Override
satok3cb5b392011-08-26 11:55:21 +0900514 public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
515 String locale, boolean allowImplicitlySelectedSubtype) {
Yohei Yukawae3e31a82016-09-20 06:43:03 -0700516 final int subtypeHashCode;
517 final SpellCheckerInfo sci;
518 final Locale systemLocale;
Guliz Tuncay06a26242017-06-30 18:32:04 -0700519 final int userId = UserHandle.getCallingUserId();
520
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700521 synchronized (mLock) {
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800522 final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700523 if (tsd == null) return null;
524
Yohei Yukawae3e31a82016-09-20 06:43:03 -0700525 subtypeHashCode =
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700526 tsd.getSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE);
satokada8c4e2011-08-23 14:56:56 +0900527 if (DBG) {
Yohei Yukawaad150ee2016-03-16 17:22:27 -0700528 Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCode);
satokada8c4e2011-08-23 14:56:56 +0900529 }
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700530 sci = tsd.getCurrentSpellChecker();
Yohei Yukawae3e31a82016-09-20 06:43:03 -0700531 systemLocale = mContext.getResources().getConfiguration().locale;
satokada8c4e2011-08-23 14:56:56 +0900532 }
Yohei Yukawae3e31a82016-09-20 06:43:03 -0700533 if (sci == null || sci.getSubtypeCount() == 0) {
534 if (DBG) {
535 Slog.w(TAG, "Subtype not found.");
536 }
537 return null;
538 }
539 if (subtypeHashCode == SpellCheckerSubtype.SUBTYPE_ID_NONE
540 && !allowImplicitlySelectedSubtype) {
541 return null;
542 }
543 String candidateLocale = null;
544 if (subtypeHashCode == 0) {
545 // Spell checker language settings == "auto"
546 final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
547 if (imm != null) {
548 final InputMethodSubtype currentInputMethodSubtype =
549 imm.getCurrentInputMethodSubtype();
550 if (currentInputMethodSubtype != null) {
551 final String localeString = currentInputMethodSubtype.getLocale();
552 if (!TextUtils.isEmpty(localeString)) {
553 // 1. Use keyboard locale if available in the spell checker
554 candidateLocale = localeString;
555 }
556 }
557 }
558 if (candidateLocale == null) {
559 // 2. Use System locale if available in the spell checker
560 candidateLocale = systemLocale.toString();
561 }
562 }
563 SpellCheckerSubtype candidate = null;
564 for (int i = 0; i < sci.getSubtypeCount(); ++i) {
565 final SpellCheckerSubtype scs = sci.getSubtypeAt(i);
566 if (subtypeHashCode == 0) {
567 final String scsLocale = scs.getLocale();
568 if (candidateLocale.equals(scsLocale)) {
569 return scs;
570 } else if (candidate == null) {
571 if (candidateLocale.length() >= 2 && scsLocale.length() >= 2
572 && candidateLocale.startsWith(scsLocale)) {
573 // Fall back to the applicable language
574 candidate = scs;
575 }
576 }
577 } else if (scs.hashCode() == subtypeHashCode) {
578 if (DBG) {
579 Slog.w(TAG, "Return subtype " + scs.hashCode() + ", input= " + locale
580 + ", " + scs.getLocale());
581 }
582 // 3. Use the user specified spell check language
583 return scs;
584 }
585 }
586 // 4. Fall back to the applicable language and return it if not null
587 // 5. Simply just return it even if it's null which means we could find no suitable
588 // spell check languages
589 return candidate;
satokada8c4e2011-08-23 14:56:56 +0900590 }
591
satok988323c2011-06-22 16:38:13 +0900592 @Override
satok5b9b5a92011-08-02 12:24:44 +0900593 public void getSpellCheckerService(String sciId, String locale,
satok53578062011-08-03 16:08:59 +0900594 ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
595 Bundle bundle) {
satok5b9b5a92011-08-02 12:24:44 +0900596 if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) {
satok988323c2011-06-22 16:38:13 +0900597 Slog.e(TAG, "getSpellCheckerService: Invalid input.");
598 return;
599 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700600 int callingUserId = UserHandle.getCallingUserId();
601
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700602 synchronized (mLock) {
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800603 final TextServicesData tsd = getDataFromCallingUserIdLocked(callingUserId);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700604 if (tsd == null) return;
605
606 HashMap<String, SpellCheckerInfo> spellCheckerMap = tsd.mSpellCheckerMap;
607 if (!spellCheckerMap.containsKey(sciId)) {
satok988323c2011-06-22 16:38:13 +0900608 return;
609 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700610 final SpellCheckerInfo sci = spellCheckerMap.get(sciId);
611 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups =
612 tsd.mSpellCheckerBindGroups;
613 SpellCheckerBindGroup bindGroup = spellCheckerBindGroups.get(sciId);
satokdf5659d2011-07-29 18:38:21 +0900614 final int uid = Binder.getCallingUid();
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800615 if (bindGroup == null) {
616 final long ident = Binder.clearCallingIdentity();
617 try {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700618 bindGroup = startSpellCheckerServiceInnerLocked(sci, tsd);
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800619 } finally {
620 Binder.restoreCallingIdentity(ident);
621 }
622 if (bindGroup == null) {
623 // startSpellCheckerServiceInnerLocked failed.
624 return;
satok6be6d752011-07-28 20:40:38 +0900625 }
satok988323c2011-06-22 16:38:13 +0900626 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800627
628 // Start getISpellCheckerSession async IPC, or just queue the request until the spell
629 // checker service is bound.
630 bindGroup.getISpellCheckerSessionOrQueueLocked(
Guliz Tuncay5e1d5d62017-07-26 11:19:08 -0700631 new SessionRequest(uid, locale, tsListener, scListener, bundle));
satok988323c2011-06-22 16:38:13 +0900632 }
satok988323c2011-06-22 16:38:13 +0900633 }
634
satoka33c4fc2011-08-25 16:50:11 +0900635 @Override
636 public boolean isSpellCheckerEnabled() {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700637 int userId = UserHandle.getCallingUserId();
638
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700639 synchronized (mLock) {
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800640 final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700641 if (tsd == null) return false;
642
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700643 return tsd.isSpellCheckerEnabled();
satoka33c4fc2011-08-25 16:50:11 +0900644 }
645 }
646
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800647 @Nullable
Guliz Tuncay06a26242017-06-30 18:32:04 -0700648 private SpellCheckerBindGroup startSpellCheckerServiceInnerLocked(SpellCheckerInfo info,
649 TextServicesData tsd) {
satokdf5659d2011-07-29 18:38:21 +0900650 if (DBG) {
651 Slog.w(TAG, "Start spell checker session inner locked.");
652 }
satok6be6d752011-07-28 20:40:38 +0900653 final String sciId = info.getId();
Guliz Tuncay06a26242017-06-30 18:32:04 -0700654 final InternalServiceConnection connection = new InternalServiceConnection(sciId,
655 tsd.mSpellCheckerBindGroups);
satok6be6d752011-07-28 20:40:38 +0900656 final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
657 serviceIntent.setComponent(info.getComponent());
658 if (DBG) {
659 Slog.w(TAG, "bind service: " + info.getId());
660 }
Dianne Hackbornd69e4c12015-04-24 09:54:54 -0700661 if (!bindCurrentSpellCheckerService(serviceIntent, connection,
Guliz Tuncay06a26242017-06-30 18:32:04 -0700662 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT_BACKGROUND, tsd.mUserId)) {
satok6be6d752011-07-28 20:40:38 +0900663 Slog.e(TAG, "Failed to get a spell checker service.");
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800664 return null;
satok6be6d752011-07-28 20:40:38 +0900665 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800666 final SpellCheckerBindGroup group = new SpellCheckerBindGroup(connection);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700667
668 tsd.mSpellCheckerBindGroups.put(sciId, group);
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800669 return group;
satok6be6d752011-07-28 20:40:38 +0900670 }
671
satok988323c2011-06-22 16:38:13 +0900672 @Override
satok562ab582011-07-25 10:12:21 +0900673 public SpellCheckerInfo[] getEnabledSpellCheckers() {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700674 int callingUserId = UserHandle.getCallingUserId();
675
676 synchronized (mLock) {
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800677 final TextServicesData tsd = getDataFromCallingUserIdLocked(callingUserId);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700678 if (tsd == null) return null;
679
680 ArrayList<SpellCheckerInfo> spellCheckerList = tsd.mSpellCheckerList;
681 if (DBG) {
682 Slog.d(TAG, "getEnabledSpellCheckers: " + spellCheckerList.size());
683 for (int i = 0; i < spellCheckerList.size(); ++i) {
684 Slog.d(TAG,
685 "EnabledSpellCheckers: " + spellCheckerList.get(i).getPackageName());
686 }
satokda317ef2011-07-26 08:02:45 +0900687 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700688 return spellCheckerList.toArray(new SpellCheckerInfo[spellCheckerList.size()]);
satokda317ef2011-07-26 08:02:45 +0900689 }
satok562ab582011-07-25 10:12:21 +0900690 }
691
692 @Override
satok988323c2011-06-22 16:38:13 +0900693 public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
satokda317ef2011-07-26 08:02:45 +0900694 if (DBG) {
695 Slog.d(TAG, "FinishSpellCheckerService");
696 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700697 int userId = UserHandle.getCallingUserId();
698
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700699 synchronized (mLock) {
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800700 final TextServicesData tsd = getDataFromCallingUserIdLocked(userId);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700701 if (tsd == null) return;
702
Yohei Yukawa074637f2016-03-06 14:34:55 -0800703 final ArrayList<SpellCheckerBindGroup> removeList = new ArrayList<>();
Guliz Tuncay06a26242017-06-30 18:32:04 -0700704 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups =
705 tsd.mSpellCheckerBindGroups;
706 for (SpellCheckerBindGroup group : spellCheckerBindGroups.values()) {
satok988323c2011-06-22 16:38:13 +0900707 if (group == null) continue;
satok4c3fa642011-11-30 18:17:59 +0900708 // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop.
709 removeList.add(group);
710 }
711 final int removeSize = removeList.size();
712 for (int i = 0; i < removeSize; ++i) {
713 removeList.get(i).removeListener(listener);
satok988323c2011-06-22 16:38:13 +0900714 }
715 }
716 }
717
Guliz Tuncay83a73302017-08-17 15:25:38 -0700718 private void setCurrentSpellCheckerLocked(@Nullable SpellCheckerInfo sci, TextServicesData tsd) {
719 final String sciId = (sci != null) ? sci.getId() : "";
satok562ab582011-07-25 10:12:21 +0900720 if (DBG) {
satok5b9b5a92011-08-02 12:24:44 +0900721 Slog.w(TAG, "setCurrentSpellChecker: " + sciId);
satok562ab582011-07-25 10:12:21 +0900722 }
satokdf5659d2011-07-29 18:38:21 +0900723 final long ident = Binder.clearCallingIdentity();
724 try {
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700725 tsd.setCurrentSpellChecker(sci);
satoka33c4fc2011-08-25 16:50:11 +0900726 } finally {
727 Binder.restoreCallingIdentity(ident);
728 }
729 }
730
Dianne Hackborn71e14da2011-10-16 16:28:10 -0700731 @Override
732 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600733 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Dianne Hackborn71e14da2011-10-16 16:28:10 -0700734
Yohei Yukawa02f646e2018-01-24 17:50:32 -0800735 if (args.length == 0 || (args.length == 1 && args[0].equals("-a"))) {
736 // Dump all users' data
Guliz Tuncay06a26242017-06-30 18:32:04 -0700737 synchronized (mLock) {
738 pw.println("Current Text Services Manager state:");
Guliz Tuncay06a26242017-06-30 18:32:04 -0700739 pw.println(" Users:");
740 final int numOfUsers = mUserData.size();
741 for (int i = 0; i < numOfUsers; i++) {
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700742 TextServicesData tsd = mUserData.valueAt(i);
743 tsd.dump(pw);
Dianne Hackborn71e14da2011-10-16 16:28:10 -0700744 }
745 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700746 } else { // Dump a given user's data
747 if (args.length != 2 || !args[0].equals("--user")) {
748 pw.println("Invalid arguments to text services." );
749 return;
750 } else {
751 int userId = Integer.parseInt(args[1]);
752 UserInfo userInfo = mUserManager.getUserInfo(userId);
753 if (userInfo == null) {
754 pw.println("Non-existent user.");
755 return;
756 }
757 TextServicesData tsd = mUserData.get(userId);
758 if (tsd == null) {
759 pw.println("User needs to unlock first." );
760 return;
761 }
762 synchronized (mLock) {
763 pw.println("Current Text Services Manager state:");
Guliz Tuncay06a26242017-06-30 18:32:04 -0700764 pw.println(" User " + userId + ":");
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700765 tsd.dump(pw);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700766 }
767 }
768 }
769 }
770
Yohei Yukawa3f8c5682018-03-01 13:10:23 -0800771 /**
772 * @param callingUserId user ID of the calling process
773 * @return {@link TextServicesData} for the given user. {@code null} if spell checker is not
774 * temporarily / permanently available for the specified user
775 */
776 @Nullable
777 private TextServicesData getDataFromCallingUserIdLocked(@UserIdInt int callingUserId) {
778 final int spellCheckerOwnerUserId = mSpellCheckerOwnerUserIdMap.get(callingUserId);
779 final TextServicesData data = mUserData.get(spellCheckerOwnerUserId);
780 if (DISABLE_PER_PROFILE_SPELL_CHECKER) {
781 if (spellCheckerOwnerUserId != callingUserId) {
782 // Calling process is running under child profile.
783 if (data == null) {
784 return null;
785 }
786 final SpellCheckerInfo info = data.getCurrentSpellChecker();
787 if (info == null) {
788 return null;
789 }
790 final ServiceInfo serviceInfo = info.getServiceInfo();
791 if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
792 // To be conservative, non pre-installed spell checker services are not allowed
793 // to be used for child profiles.
794 return null;
795 }
796 }
797 }
798 return data;
799 }
800
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800801 private static final class SessionRequest {
Yohei Yukawae4622842018-02-26 11:31:47 +0900802 public final int mUid;
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800803 @Nullable
804 public final String mLocale;
805 @NonNull
806 public final ITextServicesSessionListener mTsListener;
807 @NonNull
808 public final ISpellCheckerSessionListener mScListener;
809 @Nullable
810 public final Bundle mBundle;
811
Yohei Yukawae4622842018-02-26 11:31:47 +0900812 SessionRequest(int uid, @Nullable String locale,
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800813 @NonNull ITextServicesSessionListener tsListener,
814 @NonNull ISpellCheckerSessionListener scListener, @Nullable Bundle bundle) {
Yohei Yukawae4622842018-02-26 11:31:47 +0900815 mUid = uid;
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800816 mLocale = locale;
817 mTsListener = tsListener;
818 mScListener = scListener;
819 mBundle = bundle;
820 }
821 }
822
satok988323c2011-06-22 16:38:13 +0900823 // SpellCheckerBindGroup contains active text service session listeners.
824 // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
825 // mSpellCheckerBindGroups
Yohei Yukawa06b4be72017-01-29 14:08:17 -0800826 private final class SpellCheckerBindGroup {
satokdf5659d2011-07-29 18:38:21 +0900827 private final String TAG = SpellCheckerBindGroup.class.getSimpleName();
satok6be6d752011-07-28 20:40:38 +0900828 private final InternalServiceConnection mInternalConnection;
Guliz Tuncayf982e752017-06-14 09:33:16 -0700829 private final InternalDeathRecipients mListeners;
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800830 private boolean mUnbindCalled;
831 private ISpellCheckerService mSpellChecker;
832 private boolean mConnected;
833 private final ArrayList<SessionRequest> mPendingSessionRequests = new ArrayList<>();
834 private final ArrayList<SessionRequest> mOnGoingSessionRequests = new ArrayList<>();
Guliz Tuncay06a26242017-06-30 18:32:04 -0700835 @NonNull
836 HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups;
837
satok988323c2011-06-22 16:38:13 +0900838
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800839 public SpellCheckerBindGroup(InternalServiceConnection connection) {
satok988323c2011-06-22 16:38:13 +0900840 mInternalConnection = connection;
Guliz Tuncayf982e752017-06-14 09:33:16 -0700841 mListeners = new InternalDeathRecipients(this);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700842 mSpellCheckerBindGroups = connection.mSpellCheckerBindGroups;
satok988323c2011-06-22 16:38:13 +0900843 }
844
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700845 public void onServiceConnectedLocked(ISpellCheckerService spellChecker) {
satokda317ef2011-07-26 08:02:45 +0900846 if (DBG) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700847 Slog.d(TAG, "onServiceConnectedLocked");
satokda317ef2011-07-26 08:02:45 +0900848 }
satok4e713f12012-02-28 16:51:15 +0900849
Yohei Yukawa4163a962017-08-07 10:17:59 -0700850 if (mUnbindCalled) {
851 return;
satok988323c2011-06-22 16:38:13 +0900852 }
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700853 mSpellChecker = spellChecker;
854 mConnected = true;
855 // Dispatch pending getISpellCheckerSession requests.
Yohei Yukawa4163a962017-08-07 10:17:59 -0700856 try {
857 final int size = mPendingSessionRequests.size();
858 for (int i = 0; i < size; ++i) {
859 final SessionRequest request = mPendingSessionRequests.get(i);
860 mSpellChecker.getISpellCheckerSession(
861 request.mLocale, request.mScListener, request.mBundle,
862 new ISpellCheckerServiceCallbackBinder(this, request));
863 mOnGoingSessionRequests.add(request);
864 }
865 mPendingSessionRequests.clear();
866 } catch(RemoteException e) {
867 // The target spell checker service is not available. Better to reset the state.
868 removeAllLocked();
869 }
870 cleanLocked();
satok988323c2011-06-22 16:38:13 +0900871 }
872
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700873 public void onServiceDisconnectedLocked() {
Guliz Tuncay787aa8c2017-06-19 17:15:40 -0700874 if (DBG) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700875 Slog.d(TAG, "onServiceDisconnectedLocked");
Guliz Tuncay787aa8c2017-06-19 17:15:40 -0700876 }
877
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700878 mSpellChecker = null;
879 mConnected = false;
Guliz Tuncay787aa8c2017-06-19 17:15:40 -0700880 }
881
satok988323c2011-06-22 16:38:13 +0900882 public void removeListener(ISpellCheckerSessionListener listener) {
satokda317ef2011-07-26 08:02:45 +0900883 if (DBG) {
satokdf5659d2011-07-29 18:38:21 +0900884 Slog.w(TAG, "remove listener: " + listener.hashCode());
satokda317ef2011-07-26 08:02:45 +0900885 }
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700886 synchronized (mLock) {
Guliz Tuncayf982e752017-06-14 09:33:16 -0700887 mListeners.unregister(listener);
satok988323c2011-06-22 16:38:13 +0900888 cleanLocked();
889 }
890 }
891
satok4c3fa642011-11-30 18:17:59 +0900892 // cleanLocked may remove elements from mSpellCheckerBindGroups
satok988323c2011-06-22 16:38:13 +0900893 private void cleanLocked() {
satokda317ef2011-07-26 08:02:45 +0900894 if (DBG) {
895 Slog.d(TAG, "cleanLocked");
896 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800897 if (mUnbindCalled) {
898 return;
satok988323c2011-06-22 16:38:13 +0900899 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800900 // If there are no more active listeners, clean up. Only do this once.
Guliz Tuncayf982e752017-06-14 09:33:16 -0700901 if (mListeners.getRegisteredCallbackCount() > 0) {
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800902 return;
903 }
904 if (!mPendingSessionRequests.isEmpty()) {
905 return;
906 }
907 if (!mOnGoingSessionRequests.isEmpty()) {
908 return;
909 }
910 final String sciId = mInternalConnection.mSciId;
911 final SpellCheckerBindGroup cur = mSpellCheckerBindGroups.get(sciId);
912 if (cur == this) {
913 if (DBG) {
914 Slog.d(TAG, "Remove bind group.");
915 }
916 mSpellCheckerBindGroups.remove(sciId);
917 }
918 mContext.unbindService(mInternalConnection);
919 mUnbindCalled = true;
satok988323c2011-06-22 16:38:13 +0900920 }
satok6be6d752011-07-28 20:40:38 +0900921
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700922 public void removeAllLocked() {
satok6be6d752011-07-28 20:40:38 +0900923 Slog.e(TAG, "Remove the spell checker bind unexpectedly.");
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700924 final int size = mListeners.getRegisteredCallbackCount();
925 for (int i = size - 1; i >= 0; --i) {
926 mListeners.unregister(mListeners.getRegisteredCallbackItem(i));
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800927 }
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700928 mPendingSessionRequests.clear();
929 mOnGoingSessionRequests.clear();
930 cleanLocked();
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800931 }
932
933 public void getISpellCheckerSessionOrQueueLocked(@NonNull SessionRequest request) {
934 if (mUnbindCalled) {
935 return;
936 }
937 if (!mConnected) {
938 mPendingSessionRequests.add(request);
939 return;
940 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800941 try {
942 mSpellChecker.getISpellCheckerSession(
943 request.mLocale, request.mScListener, request.mBundle,
944 new ISpellCheckerServiceCallbackBinder(this, request));
945 mOnGoingSessionRequests.add(request);
946 } catch(RemoteException e) {
947 // The target spell checker service is not available. Better to reset the state.
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700948 removeAllLocked();
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800949 }
950 cleanLocked();
951 }
952
953 void onSessionCreated(@Nullable final ISpellCheckerSession newSession,
954 @NonNull final SessionRequest request) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700955 synchronized (mLock) {
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800956 if (mUnbindCalled) {
957 return;
958 }
959 if (mOnGoingSessionRequests.remove(request)) {
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800960 try {
961 request.mTsListener.onServiceConnected(newSession);
Guliz Tuncayf982e752017-06-14 09:33:16 -0700962 mListeners.register(request.mScListener);
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800963 } catch (RemoteException e) {
964 // Technically this can happen if the spell checker client app is already
965 // dead. We can just forget about this request; the request is already
966 // removed from mOnGoingSessionRequests and the death recipient listener is
967 // not yet added to mListeners. There is nothing to release further.
968 }
969 }
satokdf5659d2011-07-29 18:38:21 +0900970 cleanLocked();
971 }
satok6be6d752011-07-28 20:40:38 +0900972 }
satok988323c2011-06-22 16:38:13 +0900973 }
974
Yohei Yukawa06b4be72017-01-29 14:08:17 -0800975 private final class InternalServiceConnection implements ServiceConnection {
satok988323c2011-06-22 16:38:13 +0900976 private final String mSciId;
Guliz Tuncay06a26242017-06-30 18:32:04 -0700977 @NonNull
978 private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups;
979 public InternalServiceConnection(String id,
980 @NonNull HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups) {
satok988323c2011-06-22 16:38:13 +0900981 mSciId = id;
Guliz Tuncay06a26242017-06-30 18:32:04 -0700982 mSpellCheckerBindGroups = spellCheckerBindGroups;
satok988323c2011-06-22 16:38:13 +0900983 }
984
985 @Override
986 public void onServiceConnected(ComponentName name, IBinder service) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700987 synchronized (mLock) {
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900988 onServiceConnectedInnerLocked(name, service);
989 }
990 }
991
992 private void onServiceConnectedInnerLocked(ComponentName name, IBinder service) {
993 if (DBG) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700994 Slog.w(TAG, "onServiceConnectedInnerLocked: " + name);
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900995 }
996 final ISpellCheckerService spellChecker =
997 ISpellCheckerService.Stub.asInterface(service);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700998
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900999 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
1000 if (group != null && this == group.mInternalConnection) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -07001001 group.onServiceConnectedLocked(spellChecker);
satok988323c2011-06-22 16:38:13 +09001002 }
1003 }
1004
1005 @Override
1006 public void onServiceDisconnected(ComponentName name) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -07001007 synchronized (mLock) {
Guliz Tuncay787aa8c2017-06-19 17:15:40 -07001008 onServiceDisconnectedInnerLocked(name);
1009 }
1010 }
1011
1012 private void onServiceDisconnectedInnerLocked(ComponentName name) {
1013 if (DBG) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -07001014 Slog.w(TAG, "onServiceDisconnectedInnerLocked: " + name);
Guliz Tuncay787aa8c2017-06-19 17:15:40 -07001015 }
1016 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
1017 if (group != null && this == group.mInternalConnection) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -07001018 group.onServiceDisconnectedLocked();
Dianne Hackborn71e14da2011-10-16 16:28:10 -07001019 }
satok988323c2011-06-22 16:38:13 +09001020 }
1021 }
1022
Yohei Yukawaf14fe142017-06-22 22:30:45 -07001023 private static final class InternalDeathRecipients extends
Guliz Tuncayf982e752017-06-14 09:33:16 -07001024 RemoteCallbackList<ISpellCheckerSessionListener> {
satok988323c2011-06-22 16:38:13 +09001025 private final SpellCheckerBindGroup mGroup;
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -08001026
Guliz Tuncayf982e752017-06-14 09:33:16 -07001027 public InternalDeathRecipients(SpellCheckerBindGroup group) {
satok988323c2011-06-22 16:38:13 +09001028 mGroup = group;
1029 }
1030
Guliz Tuncayf982e752017-06-14 09:33:16 -07001031 @Override
1032 public void onCallbackDied(ISpellCheckerSessionListener listener) {
Yohei Yukawaf14fe142017-06-22 22:30:45 -07001033 mGroup.removeListener(listener);
satok988323c2011-06-22 16:38:13 +09001034 }
satok988323c2011-06-22 16:38:13 +09001035 }
Satoshi Kataoka00d2d412012-09-28 20:32:33 +09001036
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -08001037 private static final class ISpellCheckerServiceCallbackBinder
1038 extends ISpellCheckerServiceCallback.Stub {
1039 @NonNull
1040 private final SpellCheckerBindGroup mBindGroup;
1041 @NonNull
1042 private final SessionRequest mRequest;
1043
1044 ISpellCheckerServiceCallbackBinder(@NonNull final SpellCheckerBindGroup bindGroup,
1045 @NonNull final SessionRequest request) {
1046 mBindGroup = bindGroup;
1047 mRequest = request;
1048 }
1049
1050 @Override
1051 public void onSessionCreated(@Nullable ISpellCheckerSession newSession) {
1052 mBindGroup.onSessionCreated(newSession, mRequest);
1053 }
1054 }
satok988323c2011-06-22 16:38:13 +09001055}