blob: 7cd34065c427ff85501bf5e0510bea4a7e62d358 [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
19import com.android.internal.content.PackageMonitor;
Yohei Yukawa174843a2015-06-26 18:02:54 -070020import com.android.internal.inputmethod.InputMethodUtils;
satok988323c2011-06-22 16:38:13 +090021import com.android.internal.textservice.ISpellCheckerService;
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -080022import com.android.internal.textservice.ISpellCheckerServiceCallback;
satok988323c2011-06-22 16:38:13 +090023import com.android.internal.textservice.ISpellCheckerSession;
24import com.android.internal.textservice.ISpellCheckerSessionListener;
25import com.android.internal.textservice.ITextServicesManager;
26import com.android.internal.textservice.ITextServicesSessionListener;
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -060027import com.android.internal.util.DumpUtils;
satok988323c2011-06-22 16:38:13 +090028
satok03b2ea12011-08-03 17:36:14 +090029import org.xmlpull.v1.XmlPullParserException;
30
Yohei Yukawaf0f16802016-03-08 16:04:58 -080031import android.annotation.NonNull;
Yohei Yukawa08ce1872016-03-16 17:22:30 -070032import android.annotation.Nullable;
Yohei Yukawa9faa2ae2016-03-07 13:42:07 -080033import android.annotation.UserIdInt;
satok988323c2011-06-22 16:38:13 +090034import android.content.ComponentName;
Satoshi Kataoka00d2d412012-09-28 20:32:33 +090035import android.content.ContentResolver;
satok988323c2011-06-22 16:38:13 +090036import android.content.Context;
37import android.content.Intent;
38import android.content.ServiceConnection;
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -070039import android.content.pm.ApplicationInfo;
satok988323c2011-06-22 16:38:13 +090040import android.content.pm.PackageManager;
41import android.content.pm.ResolveInfo;
42import android.content.pm.ServiceInfo;
Guliz Tuncay06a26242017-06-30 18:32:04 -070043import android.content.pm.UserInfo;
satok6be6d752011-07-28 20:40:38 +090044import android.os.Binder;
satok53578062011-08-03 16:08:59 +090045import android.os.Bundle;
satok988323c2011-06-22 16:38:13 +090046import android.os.IBinder;
Guliz Tuncayf982e752017-06-14 09:33:16 -070047import android.os.RemoteCallbackList;
satok988323c2011-06-22 16:38:13 +090048import android.os.RemoteException;
Satoshi Kataoka00d2d412012-09-28 20:32:33 +090049import android.os.UserHandle;
Yohei Yukawa095fa372014-10-27 14:02:23 +090050import android.os.UserManager;
satok988323c2011-06-22 16:38:13 +090051import android.provider.Settings;
satok988323c2011-06-22 16:38:13 +090052import android.service.textservice.SpellCheckerService;
satok53578062011-08-03 16:08:59 +090053import android.text.TextUtils;
satok988323c2011-06-22 16:38:13 +090054import android.util.Slog;
Guliz Tuncay06a26242017-06-30 18:32:04 -070055import android.util.SparseArray;
satok05f24702011-11-02 19:29:35 +090056import android.view.inputmethod.InputMethodManager;
57import android.view.inputmethod.InputMethodSubtype;
satok988323c2011-06-22 16:38:13 +090058import android.view.textservice.SpellCheckerInfo;
satokada8c4e2011-08-23 14:56:56 +090059import android.view.textservice.SpellCheckerSubtype;
satok988323c2011-06-22 16:38:13 +090060
Dianne Hackborn71e14da2011-10-16 16:28:10 -070061import java.io.FileDescriptor;
satok03b2ea12011-08-03 17:36:14 +090062import java.io.IOException;
Dianne Hackborn71e14da2011-10-16 16:28:10 -070063import java.io.PrintWriter;
Yohei Yukawa174843a2015-06-26 18:02:54 -070064import java.util.Arrays;
satok988323c2011-06-22 16:38:13 +090065import java.util.ArrayList;
66import java.util.HashMap;
satok988323c2011-06-22 16:38:13 +090067import java.util.List;
Yohei Yukawa174843a2015-06-26 18:02:54 -070068import java.util.Locale;
Dianne Hackborn71e14da2011-10-16 16:28:10 -070069import java.util.Map;
satok988323c2011-06-22 16:38:13 +090070
71public class TextServicesManagerService extends ITextServicesManager.Stub {
72 private static final String TAG = TextServicesManagerService.class.getSimpleName();
73 private static final boolean DBG = false;
74
75 private final Context mContext;
satok988323c2011-06-22 16:38:13 +090076 private final TextServicesMonitor mMonitor;
Guliz Tuncay06a26242017-06-30 18:32:04 -070077 private final SparseArray<TextServicesData> mUserData = new SparseArray<>();
Yohei Yukawaf0f16802016-03-08 16:04:58 -080078 @NonNull
79 private final UserManager mUserManager;
Guliz Tuncay10ae3852017-07-07 10:35:59 -070080 private final Object mLock = new Object();
satok988323c2011-06-22 16:38:13 +090081
Guliz Tuncay06a26242017-06-30 18:32:04 -070082 private static class TextServicesData {
83 @UserIdInt
84 private final int mUserId;
85 private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap;
86 private final ArrayList<SpellCheckerInfo> mSpellCheckerList;
87 private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups;
Guliz Tuncay8e6fa022017-08-14 16:50:15 -070088 private final Context mContext;
89 private final ContentResolver mResolver;
90 public int mUpdateCount = 0;
Guliz Tuncay06a26242017-06-30 18:32:04 -070091
Guliz Tuncay8e6fa022017-08-14 16:50:15 -070092 public TextServicesData(@UserIdInt int userId, @NonNull Context context) {
Guliz Tuncay06a26242017-06-30 18:32:04 -070093 mUserId = userId;
94 mSpellCheckerMap = new HashMap<>();
95 mSpellCheckerList = new ArrayList<>();
96 mSpellCheckerBindGroups = new HashMap<>();
Guliz Tuncay8e6fa022017-08-14 16:50:15 -070097 mContext = context;
98 mResolver = context.getContentResolver();
99 }
100
101 private void putString(final String key, final String str) {
102 Settings.Secure.putStringForUser(mResolver, key, str, mUserId);
103 }
104
105 @Nullable
106 private String getString(@NonNull final String key, @Nullable final String defaultValue) {
107 final String result;
108 result = Settings.Secure.getStringForUser(mResolver, key, mUserId);
109 return result != null ? result : defaultValue;
110 }
111
112 private void putInt(final String key, final int value) {
113 Settings.Secure.putIntForUser(mResolver, key, value, mUserId);
114 }
115
116 private int getInt(final String key, final int defaultValue) {
117 return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mUserId);
118 }
119
120 private boolean getBoolean(final String key, final boolean defaultValue) {
121 return getInt(key, defaultValue ? 1 : 0) == 1;
122 }
123
124 private void putSelectedSpellChecker(@Nullable String sciId) {
Guliz Tuncay83a73302017-08-17 15:25:38 -0700125 putString(Settings.Secure.SELECTED_SPELL_CHECKER, sciId);
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700126 }
127
128 private void putSelectedSpellCheckerSubtype(int hashCode) {
129 putInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, hashCode);
130 }
131
132 @NonNull
133 private String getSelectedSpellChecker() {
134 return getString(Settings.Secure.SELECTED_SPELL_CHECKER, "");
135 }
136
137 public int getSelectedSpellCheckerSubtype(final int defaultValue) {
138 return getInt(Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, defaultValue);
139 }
140
141 public boolean isSpellCheckerEnabled() {
142 return getBoolean(Settings.Secure.SPELL_CHECKER_ENABLED, true);
143 }
144
145 @Nullable
146 public SpellCheckerInfo getCurrentSpellChecker() {
147 final String curSpellCheckerId = getSelectedSpellChecker();
148 if (TextUtils.isEmpty(curSpellCheckerId)) {
149 return null;
150 }
151 return mSpellCheckerMap.get(curSpellCheckerId);
152 }
153
Guliz Tuncay83a73302017-08-17 15:25:38 -0700154 public void setCurrentSpellChecker(@Nullable SpellCheckerInfo sci) {
155 if (sci != null) {
156 putSelectedSpellChecker(sci.getId());
157 } else {
158 putSelectedSpellChecker("");
159 }
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700160 putSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE);
161 }
162
163 private void initializeTextServicesData() {
164 if (DBG) {
165 Slog.d(TAG, "initializeTextServicesData for user: " + mUserId);
166 }
167 mSpellCheckerList.clear();
168 mSpellCheckerMap.clear();
169 mUpdateCount++;
170 final PackageManager pm = mContext.getPackageManager();
171 // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the
172 // default behavior of PackageManager is exactly what we want. It by default picks up
173 // appropriate services depending on the unlock state for the specified user.
174 final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
175 new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA,
176 mUserId);
177 final int N = services.size();
178 for (int i = 0; i < N; ++i) {
179 final ResolveInfo ri = services.get(i);
180 final ServiceInfo si = ri.serviceInfo;
181 final ComponentName compName = new ComponentName(si.packageName, si.name);
182 if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
183 Slog.w(TAG, "Skipping text service " + compName
184 + ": it does not require the permission "
185 + android.Manifest.permission.BIND_TEXT_SERVICE);
186 continue;
187 }
188 if (DBG) Slog.d(TAG, "Add: " + compName + " for user: " + mUserId);
189 try {
190 final SpellCheckerInfo sci = new SpellCheckerInfo(mContext, ri);
191 if (sci.getSubtypeCount() <= 0) {
192 Slog.w(TAG, "Skipping text service " + compName
193 + ": it does not contain subtypes.");
194 continue;
195 }
196 mSpellCheckerList.add(sci);
197 mSpellCheckerMap.put(sci.getId(), sci);
198 } catch (XmlPullParserException e) {
199 Slog.w(TAG, "Unable to load the spell checker " + compName, e);
200 } catch (IOException e) {
201 Slog.w(TAG, "Unable to load the spell checker " + compName, e);
202 }
203 }
204 if (DBG) {
205 Slog.d(TAG, "initializeSpellCheckerMap: " + mSpellCheckerList.size() + ","
206 + mSpellCheckerMap.size());
207 }
208 }
209
210 private void dump(PrintWriter pw) {
211 int spellCheckerIndex = 0;
212 pw.println(" User #" + mUserId);
213 pw.println(" Spell Checkers:");
214 pw.println(" Spell Checkers: " + "mUpdateCount=" + mUpdateCount);
215 for (final SpellCheckerInfo info : mSpellCheckerMap.values()) {
216 pw.println(" Spell Checker #" + spellCheckerIndex);
217 info.dump(pw, " ");
218 ++spellCheckerIndex;
219 }
220
221 pw.println("");
222 pw.println(" Spell Checker Bind Groups:");
223 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = mSpellCheckerBindGroups;
224 for (final Map.Entry<String, SpellCheckerBindGroup> ent
225 : spellCheckerBindGroups.entrySet()) {
226 final SpellCheckerBindGroup grp = ent.getValue();
227 pw.println(" " + ent.getKey() + " " + grp + ":");
228 pw.println(" " + "mInternalConnection=" + grp.mInternalConnection);
229 pw.println(" " + "mSpellChecker=" + grp.mSpellChecker);
230 pw.println(" " + "mUnbindCalled=" + grp.mUnbindCalled);
231 pw.println(" " + "mConnected=" + grp.mConnected);
232 final int numPendingSessionRequests = grp.mPendingSessionRequests.size();
233 for (int j = 0; j < numPendingSessionRequests; j++) {
234 final SessionRequest req = grp.mPendingSessionRequests.get(j);
235 pw.println(" " + "Pending Request #" + j + ":");
236 pw.println(" " + "mTsListener=" + req.mTsListener);
237 pw.println(" " + "mScListener=" + req.mScListener);
238 pw.println(
239 " " + "mScLocale=" + req.mLocale + " mUid=" + req.mUserId);
240 }
241 final int numOnGoingSessionRequests = grp.mOnGoingSessionRequests.size();
242 for (int j = 0; j < numOnGoingSessionRequests; j++) {
243 final SessionRequest req = grp.mOnGoingSessionRequests.get(j);
244 pw.println(" " + "On going Request #" + j + ":");
245 ++j;
246 pw.println(" " + "mTsListener=" + req.mTsListener);
247 pw.println(" " + "mScListener=" + req.mScListener);
248 pw.println(
249 " " + "mScLocale=" + req.mLocale + " mUid=" + req.mUserId);
250 }
251 final int N = grp.mListeners.getRegisteredCallbackCount();
252 for (int j = 0; j < N; j++) {
253 final ISpellCheckerSessionListener mScListener =
254 grp.mListeners.getRegisteredCallbackItem(j);
255 pw.println(" " + "Listener #" + j + ":");
256 pw.println(" " + "mScListener=" + mScListener);
257 pw.println(" " + "mGroup=" + grp);
258 }
259 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700260 }
261 }
262
Yohei Yukawaa6a152e2016-03-07 13:41:15 -0800263 public static final class Lifecycle extends SystemService {
264 private TextServicesManagerService mService;
265
266 public Lifecycle(Context context) {
267 super(context);
268 mService = new TextServicesManagerService(context);
269 }
270
271 @Override
272 public void onStart() {
273 publishBinderService(Context.TEXT_SERVICES_MANAGER_SERVICE, mService);
274 }
275
276 @Override
Guliz Tuncay06a26242017-06-30 18:32:04 -0700277 public void onStopUser(@UserIdInt int userHandle) {
278 if (DBG) {
279 Slog.d(TAG, "onStopUser userId: " + userHandle);
280 }
281 mService.onStopUser(userHandle);
Yohei Yukawa9faa2ae2016-03-07 13:42:07 -0800282 }
283
284 @Override
Yohei Yukawaf0f16802016-03-08 16:04:58 -0800285 public void onUnlockUser(@UserIdInt int userHandle) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700286 if(DBG) {
287 Slog.d(TAG, "onUnlockUser userId: " + userHandle);
288 }
Yohei Yukawaf0f16802016-03-08 16:04:58 -0800289 // Called on the system server's main looper thread.
290 // TODO: Dispatch this to a worker thread as needed.
291 mService.onUnlockUser(userHandle);
292 }
Yohei Yukawaa6a152e2016-03-07 13:41:15 -0800293 }
294
Guliz Tuncay06a26242017-06-30 18:32:04 -0700295 void onStopUser(@UserIdInt int userId) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700296 synchronized (mLock) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700297 // Clean per-user data
298 TextServicesData tsd = mUserData.get(userId);
299 if (tsd == null) return;
300
301 unbindServiceLocked(tsd); // Remove bind groups first
302 mUserData.remove(userId); // This needs to be done after bind groups are all removed
Yohei Yukawaf0f16802016-03-08 16:04:58 -0800303 }
304 }
305
306 void onUnlockUser(@UserIdInt int userId) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700307 synchronized (mLock) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700308 // Initialize internal state for the given user
309 initializeInternalStateLocked(userId);
satok988323c2011-06-22 16:38:13 +0900310 }
311 }
312
313 public TextServicesManagerService(Context context) {
satok988323c2011-06-22 16:38:13 +0900314 mContext = context;
Yohei Yukawaf0f16802016-03-08 16:04:58 -0800315 mUserManager = mContext.getSystemService(UserManager.class);
316
satok988323c2011-06-22 16:38:13 +0900317 mMonitor = new TextServicesMonitor();
Guliz Tuncay06a26242017-06-30 18:32:04 -0700318 mMonitor.register(context, null, UserHandle.ALL, true);
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900319 }
320
Guliz Tuncay5e1d5d62017-07-26 11:19:08 -0700321 private void initializeInternalStateLocked(@UserIdInt int userId) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700322 TextServicesData tsd = mUserData.get(userId);
323 if (tsd == null) {
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700324 tsd = new TextServicesData(userId, mContext);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700325 mUserData.put(userId, tsd);
326 }
327
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700328 tsd.initializeTextServicesData();
329 SpellCheckerInfo sci = tsd.getCurrentSpellChecker();
satokdf5659d2011-07-29 18:38:21 +0900330 if (sci == null) {
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700331 sci = findAvailSystemSpellCheckerLocked(null, tsd);
Guliz Tuncay83a73302017-08-17 15:25:38 -0700332 // Set the current spell checker if there is one or more system spell checkers
333 // available. In this case, "sci" is the first one in the available spell
334 // checkers.
335 setCurrentSpellCheckerLocked(sci, tsd);
satokdf5659d2011-07-29 18:38:21 +0900336 }
satok988323c2011-06-22 16:38:13 +0900337 }
338
Guliz Tuncay06a26242017-06-30 18:32:04 -0700339 private final class TextServicesMonitor extends PackageMonitor {
satok988323c2011-06-22 16:38:13 +0900340 @Override
341 public void onSomePackagesChanged() {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700342 int userId = getChangingUserId();
343 if(DBG) {
344 Slog.d(TAG, "onSomePackagesChanged: " + userId);
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900345 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700346
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700347 synchronized (mLock) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700348 TextServicesData tsd = mUserData.get(userId);
349 if (tsd == null) return;
350
satok988323c2011-06-22 16:38:13 +0900351 // TODO: Update for each locale
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700352 SpellCheckerInfo sci = tsd.getCurrentSpellChecker();
353 tsd.initializeTextServicesData();
Guliz Tuncay83a73302017-08-17 15:25:38 -0700354 // If spell checker is disabled, just return. The user should explicitly
Satoshi Kataoka02260e22013-08-02 16:22:04 +0900355 // enable the spell checker.
Guliz Tuncay83a73302017-08-17 15:25:38 -0700356 if (!tsd.isSpellCheckerEnabled()) return;
357
358 if (sci == null) {
359 sci = findAvailSystemSpellCheckerLocked(null, tsd);
360 // Set the current spell checker if there is one or more system spell checkers
361 // available. In this case, "sci" is the first one in the available spell
362 // checkers.
363 setCurrentSpellCheckerLocked(sci, tsd);
364 } else {
365 final String packageName = sci.getPackageName();
366 final int change = isPackageDisappearing(packageName);
367 if (DBG) Slog.d(TAG, "Changing package name: " + packageName);
368 if (// Package disappearing
369 change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE
370 // Package modified
371 || isPackageModified(packageName)) {
372 SpellCheckerInfo availSci =
373 findAvailSystemSpellCheckerLocked(packageName, tsd);
374 // Set the spell checker settings if different than before
375 if (availSci == null
376 || (availSci != null && !availSci.getId().equals(sci.getId()))) {
377 setCurrentSpellCheckerLocked(availSci, tsd);
378 }
satok5b9b5a92011-08-02 12:24:44 +0900379 }
satok988323c2011-06-22 16:38:13 +0900380 }
381 }
382 }
383 }
384
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900385 private boolean bindCurrentSpellCheckerService(
Guliz Tuncay06a26242017-06-30 18:32:04 -0700386 Intent service, ServiceConnection conn, int flags, @UserIdInt int userId) {
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900387 if (service == null || conn == null) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700388 Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn +
389 ", userId =" + userId);
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900390 return false;
391 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700392 return mContext.bindServiceAsUser(service, conn, flags, UserHandle.of(userId));
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900393 }
394
Guliz Tuncay06a26242017-06-30 18:32:04 -0700395 private void unbindServiceLocked(TextServicesData tsd) {
396 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups = tsd.mSpellCheckerBindGroups;
397 for (SpellCheckerBindGroup scbg : spellCheckerBindGroups.values()) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700398 scbg.removeAllLocked();
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900399 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700400 spellCheckerBindGroups.clear();
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900401 }
402
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700403 private SpellCheckerInfo findAvailSystemSpellCheckerLocked(String prefPackage,
Guliz Tuncay06a26242017-06-30 18:32:04 -0700404 TextServicesData tsd) {
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700405 // Filter the spell checker list to remove spell checker services that are not pre-installed
406 ArrayList<SpellCheckerInfo> spellCheckerList = new ArrayList<>();
407 for (SpellCheckerInfo sci : tsd.mSpellCheckerList) {
408 if ((sci.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
409 spellCheckerList.add(sci);
410 }
411 }
412
Guliz Tuncay06a26242017-06-30 18:32:04 -0700413 final int spellCheckersCount = spellCheckerList.size();
satok988323c2011-06-22 16:38:13 +0900414 if (spellCheckersCount == 0) {
415 Slog.w(TAG, "no available spell checker services found");
416 return null;
417 }
418 if (prefPackage != null) {
419 for (int i = 0; i < spellCheckersCount; ++i) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700420 final SpellCheckerInfo sci = spellCheckerList.get(i);
satok988323c2011-06-22 16:38:13 +0900421 if (prefPackage.equals(sci.getPackageName())) {
satokda317ef2011-07-26 08:02:45 +0900422 if (DBG) {
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700423 Slog.d(TAG, "findAvailSystemSpellCheckerLocked: " + sci.getPackageName());
satokda317ef2011-07-26 08:02:45 +0900424 }
satok988323c2011-06-22 16:38:13 +0900425 return sci;
426 }
427 }
428 }
Yohei Yukawa174843a2015-06-26 18:02:54 -0700429
430 // Look up a spell checker based on the system locale.
431 // TODO: Still there is a room to improve in the following logic: e.g., check if the package
432 // is pre-installed or not.
433 final Locale systemLocal = mContext.getResources().getConfiguration().locale;
434 final ArrayList<Locale> suitableLocales =
435 InputMethodUtils.getSuitableLocalesForSpellChecker(systemLocal);
436 if (DBG) {
Guliz Tuncay0f0a37b2017-08-16 12:02:31 -0700437 Slog.w(TAG, "findAvailSystemSpellCheckerLocked suitableLocales="
Yohei Yukawa174843a2015-06-26 18:02:54 -0700438 + Arrays.toString(suitableLocales.toArray(new Locale[suitableLocales.size()])));
439 }
440 final int localeCount = suitableLocales.size();
441 for (int localeIndex = 0; localeIndex < localeCount; ++localeIndex) {
442 final Locale locale = suitableLocales.get(localeIndex);
443 for (int spellCheckersIndex = 0; spellCheckersIndex < spellCheckersCount;
444 ++spellCheckersIndex) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700445 final SpellCheckerInfo info = spellCheckerList.get(spellCheckersIndex);
Yohei Yukawa174843a2015-06-26 18:02:54 -0700446 final int subtypeCount = info.getSubtypeCount();
447 for (int subtypeIndex = 0; subtypeIndex < subtypeCount; ++subtypeIndex) {
448 final SpellCheckerSubtype subtype = info.getSubtypeAt(subtypeIndex);
449 final Locale subtypeLocale = InputMethodUtils.constructLocaleFromString(
450 subtype.getLocale());
451 if (locale.equals(subtypeLocale)) {
452 // TODO: We may have more spell checkers that fall into this category.
453 // Ideally we should pick up the most suitable one instead of simply
454 // returning the first found one.
455 return info;
456 }
457 }
458 }
459 }
460
satok988323c2011-06-22 16:38:13 +0900461 if (spellCheckersCount > 1) {
462 Slog.w(TAG, "more than one spell checker service found, picking first");
463 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700464 return spellCheckerList.get(0);
satok988323c2011-06-22 16:38:13 +0900465 }
466
467 // TODO: Save SpellCheckerService by supported languages. Currently only one spell
468 // checker is saved.
469 @Override
470 public SpellCheckerInfo getCurrentSpellChecker(String locale) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700471 int userId = UserHandle.getCallingUserId();
472 synchronized (mLock) {
473 TextServicesData tsd = mUserData.get(userId);
474 if (tsd == null) return null;
475
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700476 return tsd.getCurrentSpellChecker();
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900477 }
Yohei Yukawa095fa372014-10-27 14:02:23 +0900478 }
479
satok3cb5b392011-08-26 11:55:21 +0900480 // TODO: Respect allowImplicitlySelectedSubtype
Satoshi Kataoka17150cf2012-05-30 20:05:44 +0900481 // TODO: Save SpellCheckerSubtype by supported languages by looking at "locale".
satokada8c4e2011-08-23 14:56:56 +0900482 @Override
satok3cb5b392011-08-26 11:55:21 +0900483 public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
484 String locale, boolean allowImplicitlySelectedSubtype) {
Yohei Yukawae3e31a82016-09-20 06:43:03 -0700485 final int subtypeHashCode;
486 final SpellCheckerInfo sci;
487 final Locale systemLocale;
Guliz Tuncay06a26242017-06-30 18:32:04 -0700488 final int userId = UserHandle.getCallingUserId();
489
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700490 synchronized (mLock) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700491 TextServicesData tsd = mUserData.get(userId);
492 if (tsd == null) return null;
493
Yohei Yukawae3e31a82016-09-20 06:43:03 -0700494 subtypeHashCode =
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700495 tsd.getSelectedSpellCheckerSubtype(SpellCheckerSubtype.SUBTYPE_ID_NONE);
satokada8c4e2011-08-23 14:56:56 +0900496 if (DBG) {
Yohei Yukawaad150ee2016-03-16 17:22:27 -0700497 Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCode);
satokada8c4e2011-08-23 14:56:56 +0900498 }
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700499 sci = tsd.getCurrentSpellChecker();
Yohei Yukawae3e31a82016-09-20 06:43:03 -0700500 systemLocale = mContext.getResources().getConfiguration().locale;
satokada8c4e2011-08-23 14:56:56 +0900501 }
Yohei Yukawae3e31a82016-09-20 06:43:03 -0700502 if (sci == null || sci.getSubtypeCount() == 0) {
503 if (DBG) {
504 Slog.w(TAG, "Subtype not found.");
505 }
506 return null;
507 }
508 if (subtypeHashCode == SpellCheckerSubtype.SUBTYPE_ID_NONE
509 && !allowImplicitlySelectedSubtype) {
510 return null;
511 }
512 String candidateLocale = null;
513 if (subtypeHashCode == 0) {
514 // Spell checker language settings == "auto"
515 final InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
516 if (imm != null) {
517 final InputMethodSubtype currentInputMethodSubtype =
518 imm.getCurrentInputMethodSubtype();
519 if (currentInputMethodSubtype != null) {
520 final String localeString = currentInputMethodSubtype.getLocale();
521 if (!TextUtils.isEmpty(localeString)) {
522 // 1. Use keyboard locale if available in the spell checker
523 candidateLocale = localeString;
524 }
525 }
526 }
527 if (candidateLocale == null) {
528 // 2. Use System locale if available in the spell checker
529 candidateLocale = systemLocale.toString();
530 }
531 }
532 SpellCheckerSubtype candidate = null;
533 for (int i = 0; i < sci.getSubtypeCount(); ++i) {
534 final SpellCheckerSubtype scs = sci.getSubtypeAt(i);
535 if (subtypeHashCode == 0) {
536 final String scsLocale = scs.getLocale();
537 if (candidateLocale.equals(scsLocale)) {
538 return scs;
539 } else if (candidate == null) {
540 if (candidateLocale.length() >= 2 && scsLocale.length() >= 2
541 && candidateLocale.startsWith(scsLocale)) {
542 // Fall back to the applicable language
543 candidate = scs;
544 }
545 }
546 } else if (scs.hashCode() == subtypeHashCode) {
547 if (DBG) {
548 Slog.w(TAG, "Return subtype " + scs.hashCode() + ", input= " + locale
549 + ", " + scs.getLocale());
550 }
551 // 3. Use the user specified spell check language
552 return scs;
553 }
554 }
555 // 4. Fall back to the applicable language and return it if not null
556 // 5. Simply just return it even if it's null which means we could find no suitable
557 // spell check languages
558 return candidate;
satokada8c4e2011-08-23 14:56:56 +0900559 }
560
satok988323c2011-06-22 16:38:13 +0900561 @Override
satok5b9b5a92011-08-02 12:24:44 +0900562 public void getSpellCheckerService(String sciId, String locale,
satok53578062011-08-03 16:08:59 +0900563 ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
564 Bundle bundle) {
satok5b9b5a92011-08-02 12:24:44 +0900565 if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) {
satok988323c2011-06-22 16:38:13 +0900566 Slog.e(TAG, "getSpellCheckerService: Invalid input.");
567 return;
568 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700569 int callingUserId = UserHandle.getCallingUserId();
570
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700571 synchronized (mLock) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700572 TextServicesData tsd = mUserData.get(callingUserId);
573 if (tsd == null) return;
574
575 HashMap<String, SpellCheckerInfo> spellCheckerMap = tsd.mSpellCheckerMap;
576 if (!spellCheckerMap.containsKey(sciId)) {
satok988323c2011-06-22 16:38:13 +0900577 return;
578 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700579 final SpellCheckerInfo sci = spellCheckerMap.get(sciId);
580 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups =
581 tsd.mSpellCheckerBindGroups;
582 SpellCheckerBindGroup bindGroup = spellCheckerBindGroups.get(sciId);
satokdf5659d2011-07-29 18:38:21 +0900583 final int uid = Binder.getCallingUid();
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800584 if (bindGroup == null) {
585 final long ident = Binder.clearCallingIdentity();
586 try {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700587 bindGroup = startSpellCheckerServiceInnerLocked(sci, tsd);
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800588 } finally {
589 Binder.restoreCallingIdentity(ident);
590 }
591 if (bindGroup == null) {
592 // startSpellCheckerServiceInnerLocked failed.
593 return;
satok6be6d752011-07-28 20:40:38 +0900594 }
satok988323c2011-06-22 16:38:13 +0900595 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800596
597 // Start getISpellCheckerSession async IPC, or just queue the request until the spell
598 // checker service is bound.
599 bindGroup.getISpellCheckerSessionOrQueueLocked(
Guliz Tuncay5e1d5d62017-07-26 11:19:08 -0700600 new SessionRequest(uid, locale, tsListener, scListener, bundle));
satok988323c2011-06-22 16:38:13 +0900601 }
satok988323c2011-06-22 16:38:13 +0900602 }
603
satoka33c4fc2011-08-25 16:50:11 +0900604 @Override
605 public boolean isSpellCheckerEnabled() {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700606 int userId = UserHandle.getCallingUserId();
607
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700608 synchronized (mLock) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700609 TextServicesData tsd = mUserData.get(userId);
610 if (tsd == null) return false;
611
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700612 return tsd.isSpellCheckerEnabled();
satoka33c4fc2011-08-25 16:50:11 +0900613 }
614 }
615
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800616 @Nullable
Guliz Tuncay06a26242017-06-30 18:32:04 -0700617 private SpellCheckerBindGroup startSpellCheckerServiceInnerLocked(SpellCheckerInfo info,
618 TextServicesData tsd) {
satokdf5659d2011-07-29 18:38:21 +0900619 if (DBG) {
620 Slog.w(TAG, "Start spell checker session inner locked.");
621 }
satok6be6d752011-07-28 20:40:38 +0900622 final String sciId = info.getId();
Guliz Tuncay06a26242017-06-30 18:32:04 -0700623 final InternalServiceConnection connection = new InternalServiceConnection(sciId,
624 tsd.mSpellCheckerBindGroups);
satok6be6d752011-07-28 20:40:38 +0900625 final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
626 serviceIntent.setComponent(info.getComponent());
627 if (DBG) {
628 Slog.w(TAG, "bind service: " + info.getId());
629 }
Dianne Hackbornd69e4c12015-04-24 09:54:54 -0700630 if (!bindCurrentSpellCheckerService(serviceIntent, connection,
Guliz Tuncay06a26242017-06-30 18:32:04 -0700631 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT_BACKGROUND, tsd.mUserId)) {
satok6be6d752011-07-28 20:40:38 +0900632 Slog.e(TAG, "Failed to get a spell checker service.");
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800633 return null;
satok6be6d752011-07-28 20:40:38 +0900634 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800635 final SpellCheckerBindGroup group = new SpellCheckerBindGroup(connection);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700636
637 tsd.mSpellCheckerBindGroups.put(sciId, group);
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800638 return group;
satok6be6d752011-07-28 20:40:38 +0900639 }
640
satok988323c2011-06-22 16:38:13 +0900641 @Override
satok562ab582011-07-25 10:12:21 +0900642 public SpellCheckerInfo[] getEnabledSpellCheckers() {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700643 int callingUserId = UserHandle.getCallingUserId();
644
645 synchronized (mLock) {
646 TextServicesData tsd = mUserData.get(callingUserId);
647 if (tsd == null) return null;
648
649 ArrayList<SpellCheckerInfo> spellCheckerList = tsd.mSpellCheckerList;
650 if (DBG) {
651 Slog.d(TAG, "getEnabledSpellCheckers: " + spellCheckerList.size());
652 for (int i = 0; i < spellCheckerList.size(); ++i) {
653 Slog.d(TAG,
654 "EnabledSpellCheckers: " + spellCheckerList.get(i).getPackageName());
655 }
satokda317ef2011-07-26 08:02:45 +0900656 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700657 return spellCheckerList.toArray(new SpellCheckerInfo[spellCheckerList.size()]);
satokda317ef2011-07-26 08:02:45 +0900658 }
satok562ab582011-07-25 10:12:21 +0900659 }
660
661 @Override
satok988323c2011-06-22 16:38:13 +0900662 public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
satokda317ef2011-07-26 08:02:45 +0900663 if (DBG) {
664 Slog.d(TAG, "FinishSpellCheckerService");
665 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700666 int userId = UserHandle.getCallingUserId();
667
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700668 synchronized (mLock) {
Guliz Tuncay06a26242017-06-30 18:32:04 -0700669 TextServicesData tsd = mUserData.get(userId);
670 if (tsd == null) return;
671
Yohei Yukawa074637f2016-03-06 14:34:55 -0800672 final ArrayList<SpellCheckerBindGroup> removeList = new ArrayList<>();
Guliz Tuncay06a26242017-06-30 18:32:04 -0700673 HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups =
674 tsd.mSpellCheckerBindGroups;
675 for (SpellCheckerBindGroup group : spellCheckerBindGroups.values()) {
satok988323c2011-06-22 16:38:13 +0900676 if (group == null) continue;
satok4c3fa642011-11-30 18:17:59 +0900677 // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop.
678 removeList.add(group);
679 }
680 final int removeSize = removeList.size();
681 for (int i = 0; i < removeSize; ++i) {
682 removeList.get(i).removeListener(listener);
satok988323c2011-06-22 16:38:13 +0900683 }
684 }
685 }
686
Guliz Tuncay83a73302017-08-17 15:25:38 -0700687 private void setCurrentSpellCheckerLocked(@Nullable SpellCheckerInfo sci, TextServicesData tsd) {
688 final String sciId = (sci != null) ? sci.getId() : "";
satok562ab582011-07-25 10:12:21 +0900689 if (DBG) {
satok5b9b5a92011-08-02 12:24:44 +0900690 Slog.w(TAG, "setCurrentSpellChecker: " + sciId);
satok562ab582011-07-25 10:12:21 +0900691 }
satokdf5659d2011-07-29 18:38:21 +0900692 final long ident = Binder.clearCallingIdentity();
693 try {
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700694 tsd.setCurrentSpellChecker(sci);
satoka33c4fc2011-08-25 16:50:11 +0900695 } finally {
696 Binder.restoreCallingIdentity(ident);
697 }
698 }
699
Dianne Hackborn71e14da2011-10-16 16:28:10 -0700700 @Override
701 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Jeff Sharkeyfe9a53b2017-03-31 14:08:23 -0600702 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
Dianne Hackborn71e14da2011-10-16 16:28:10 -0700703
Guliz Tuncay06a26242017-06-30 18:32:04 -0700704 if (args.length == 0) { // Dump all users' data
705 synchronized (mLock) {
706 pw.println("Current Text Services Manager state:");
Guliz Tuncay06a26242017-06-30 18:32:04 -0700707 pw.println(" Users:");
708 final int numOfUsers = mUserData.size();
709 for (int i = 0; i < numOfUsers; i++) {
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700710 TextServicesData tsd = mUserData.valueAt(i);
711 tsd.dump(pw);
Dianne Hackborn71e14da2011-10-16 16:28:10 -0700712 }
713 }
Guliz Tuncay06a26242017-06-30 18:32:04 -0700714 } else { // Dump a given user's data
715 if (args.length != 2 || !args[0].equals("--user")) {
716 pw.println("Invalid arguments to text services." );
717 return;
718 } else {
719 int userId = Integer.parseInt(args[1]);
720 UserInfo userInfo = mUserManager.getUserInfo(userId);
721 if (userInfo == null) {
722 pw.println("Non-existent user.");
723 return;
724 }
725 TextServicesData tsd = mUserData.get(userId);
726 if (tsd == null) {
727 pw.println("User needs to unlock first." );
728 return;
729 }
730 synchronized (mLock) {
731 pw.println("Current Text Services Manager state:");
Guliz Tuncay06a26242017-06-30 18:32:04 -0700732 pw.println(" User " + userId + ":");
Guliz Tuncay8e6fa022017-08-14 16:50:15 -0700733 tsd.dump(pw);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700734 }
735 }
736 }
737 }
738
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800739 private static final class SessionRequest {
740 @UserIdInt
741 public final int mUserId;
742 @Nullable
743 public final String mLocale;
744 @NonNull
745 public final ITextServicesSessionListener mTsListener;
746 @NonNull
747 public final ISpellCheckerSessionListener mScListener;
748 @Nullable
749 public final Bundle mBundle;
750
751 SessionRequest(@UserIdInt final int userId, @Nullable String locale,
752 @NonNull ITextServicesSessionListener tsListener,
753 @NonNull ISpellCheckerSessionListener scListener, @Nullable Bundle bundle) {
754 mUserId = userId;
755 mLocale = locale;
756 mTsListener = tsListener;
757 mScListener = scListener;
758 mBundle = bundle;
759 }
760 }
761
satok988323c2011-06-22 16:38:13 +0900762 // SpellCheckerBindGroup contains active text service session listeners.
763 // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
764 // mSpellCheckerBindGroups
Yohei Yukawa06b4be72017-01-29 14:08:17 -0800765 private final class SpellCheckerBindGroup {
satokdf5659d2011-07-29 18:38:21 +0900766 private final String TAG = SpellCheckerBindGroup.class.getSimpleName();
satok6be6d752011-07-28 20:40:38 +0900767 private final InternalServiceConnection mInternalConnection;
Guliz Tuncayf982e752017-06-14 09:33:16 -0700768 private final InternalDeathRecipients mListeners;
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800769 private boolean mUnbindCalled;
770 private ISpellCheckerService mSpellChecker;
771 private boolean mConnected;
772 private final ArrayList<SessionRequest> mPendingSessionRequests = new ArrayList<>();
773 private final ArrayList<SessionRequest> mOnGoingSessionRequests = new ArrayList<>();
Guliz Tuncay06a26242017-06-30 18:32:04 -0700774 @NonNull
775 HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups;
776
satok988323c2011-06-22 16:38:13 +0900777
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800778 public SpellCheckerBindGroup(InternalServiceConnection connection) {
satok988323c2011-06-22 16:38:13 +0900779 mInternalConnection = connection;
Guliz Tuncayf982e752017-06-14 09:33:16 -0700780 mListeners = new InternalDeathRecipients(this);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700781 mSpellCheckerBindGroups = connection.mSpellCheckerBindGroups;
satok988323c2011-06-22 16:38:13 +0900782 }
783
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700784 public void onServiceConnectedLocked(ISpellCheckerService spellChecker) {
satokda317ef2011-07-26 08:02:45 +0900785 if (DBG) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700786 Slog.d(TAG, "onServiceConnectedLocked");
satokda317ef2011-07-26 08:02:45 +0900787 }
satok4e713f12012-02-28 16:51:15 +0900788
Yohei Yukawa4163a962017-08-07 10:17:59 -0700789 if (mUnbindCalled) {
790 return;
satok988323c2011-06-22 16:38:13 +0900791 }
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700792 mSpellChecker = spellChecker;
793 mConnected = true;
794 // Dispatch pending getISpellCheckerSession requests.
Yohei Yukawa4163a962017-08-07 10:17:59 -0700795 try {
796 final int size = mPendingSessionRequests.size();
797 for (int i = 0; i < size; ++i) {
798 final SessionRequest request = mPendingSessionRequests.get(i);
799 mSpellChecker.getISpellCheckerSession(
800 request.mLocale, request.mScListener, request.mBundle,
801 new ISpellCheckerServiceCallbackBinder(this, request));
802 mOnGoingSessionRequests.add(request);
803 }
804 mPendingSessionRequests.clear();
805 } catch(RemoteException e) {
806 // The target spell checker service is not available. Better to reset the state.
807 removeAllLocked();
808 }
809 cleanLocked();
satok988323c2011-06-22 16:38:13 +0900810 }
811
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700812 public void onServiceDisconnectedLocked() {
Guliz Tuncay787aa8c2017-06-19 17:15:40 -0700813 if (DBG) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700814 Slog.d(TAG, "onServiceDisconnectedLocked");
Guliz Tuncay787aa8c2017-06-19 17:15:40 -0700815 }
816
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700817 mSpellChecker = null;
818 mConnected = false;
Guliz Tuncay787aa8c2017-06-19 17:15:40 -0700819 }
820
satok988323c2011-06-22 16:38:13 +0900821 public void removeListener(ISpellCheckerSessionListener listener) {
satokda317ef2011-07-26 08:02:45 +0900822 if (DBG) {
satokdf5659d2011-07-29 18:38:21 +0900823 Slog.w(TAG, "remove listener: " + listener.hashCode());
satokda317ef2011-07-26 08:02:45 +0900824 }
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700825 synchronized (mLock) {
Guliz Tuncayf982e752017-06-14 09:33:16 -0700826 mListeners.unregister(listener);
satok988323c2011-06-22 16:38:13 +0900827 cleanLocked();
828 }
829 }
830
satok4c3fa642011-11-30 18:17:59 +0900831 // cleanLocked may remove elements from mSpellCheckerBindGroups
satok988323c2011-06-22 16:38:13 +0900832 private void cleanLocked() {
satokda317ef2011-07-26 08:02:45 +0900833 if (DBG) {
834 Slog.d(TAG, "cleanLocked");
835 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800836 if (mUnbindCalled) {
837 return;
satok988323c2011-06-22 16:38:13 +0900838 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800839 // If there are no more active listeners, clean up. Only do this once.
Guliz Tuncayf982e752017-06-14 09:33:16 -0700840 if (mListeners.getRegisteredCallbackCount() > 0) {
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800841 return;
842 }
843 if (!mPendingSessionRequests.isEmpty()) {
844 return;
845 }
846 if (!mOnGoingSessionRequests.isEmpty()) {
847 return;
848 }
849 final String sciId = mInternalConnection.mSciId;
850 final SpellCheckerBindGroup cur = mSpellCheckerBindGroups.get(sciId);
851 if (cur == this) {
852 if (DBG) {
853 Slog.d(TAG, "Remove bind group.");
854 }
855 mSpellCheckerBindGroups.remove(sciId);
856 }
857 mContext.unbindService(mInternalConnection);
858 mUnbindCalled = true;
satok988323c2011-06-22 16:38:13 +0900859 }
satok6be6d752011-07-28 20:40:38 +0900860
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700861 public void removeAllLocked() {
satok6be6d752011-07-28 20:40:38 +0900862 Slog.e(TAG, "Remove the spell checker bind unexpectedly.");
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700863 final int size = mListeners.getRegisteredCallbackCount();
864 for (int i = size - 1; i >= 0; --i) {
865 mListeners.unregister(mListeners.getRegisteredCallbackItem(i));
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800866 }
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700867 mPendingSessionRequests.clear();
868 mOnGoingSessionRequests.clear();
869 cleanLocked();
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800870 }
871
872 public void getISpellCheckerSessionOrQueueLocked(@NonNull SessionRequest request) {
873 if (mUnbindCalled) {
874 return;
875 }
876 if (!mConnected) {
877 mPendingSessionRequests.add(request);
878 return;
879 }
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800880 try {
881 mSpellChecker.getISpellCheckerSession(
882 request.mLocale, request.mScListener, request.mBundle,
883 new ISpellCheckerServiceCallbackBinder(this, request));
884 mOnGoingSessionRequests.add(request);
885 } catch(RemoteException e) {
886 // The target spell checker service is not available. Better to reset the state.
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700887 removeAllLocked();
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800888 }
889 cleanLocked();
890 }
891
892 void onSessionCreated(@Nullable final ISpellCheckerSession newSession,
893 @NonNull final SessionRequest request) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700894 synchronized (mLock) {
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800895 if (mUnbindCalled) {
896 return;
897 }
898 if (mOnGoingSessionRequests.remove(request)) {
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800899 try {
900 request.mTsListener.onServiceConnected(newSession);
Guliz Tuncayf982e752017-06-14 09:33:16 -0700901 mListeners.register(request.mScListener);
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800902 } catch (RemoteException e) {
903 // Technically this can happen if the spell checker client app is already
904 // dead. We can just forget about this request; the request is already
905 // removed from mOnGoingSessionRequests and the death recipient listener is
906 // not yet added to mListeners. There is nothing to release further.
907 }
908 }
satokdf5659d2011-07-29 18:38:21 +0900909 cleanLocked();
910 }
satok6be6d752011-07-28 20:40:38 +0900911 }
satok988323c2011-06-22 16:38:13 +0900912 }
913
Yohei Yukawa06b4be72017-01-29 14:08:17 -0800914 private final class InternalServiceConnection implements ServiceConnection {
satok988323c2011-06-22 16:38:13 +0900915 private final String mSciId;
Guliz Tuncay06a26242017-06-30 18:32:04 -0700916 @NonNull
917 private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups;
918 public InternalServiceConnection(String id,
919 @NonNull HashMap<String, SpellCheckerBindGroup> spellCheckerBindGroups) {
satok988323c2011-06-22 16:38:13 +0900920 mSciId = id;
Guliz Tuncay06a26242017-06-30 18:32:04 -0700921 mSpellCheckerBindGroups = spellCheckerBindGroups;
satok988323c2011-06-22 16:38:13 +0900922 }
923
924 @Override
925 public void onServiceConnected(ComponentName name, IBinder service) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700926 synchronized (mLock) {
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900927 onServiceConnectedInnerLocked(name, service);
928 }
929 }
930
931 private void onServiceConnectedInnerLocked(ComponentName name, IBinder service) {
932 if (DBG) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700933 Slog.w(TAG, "onServiceConnectedInnerLocked: " + name);
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900934 }
935 final ISpellCheckerService spellChecker =
936 ISpellCheckerService.Stub.asInterface(service);
Guliz Tuncay06a26242017-06-30 18:32:04 -0700937
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900938 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
939 if (group != null && this == group.mInternalConnection) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700940 group.onServiceConnectedLocked(spellChecker);
satok988323c2011-06-22 16:38:13 +0900941 }
942 }
943
944 @Override
945 public void onServiceDisconnected(ComponentName name) {
Guliz Tuncay10ae3852017-07-07 10:35:59 -0700946 synchronized (mLock) {
Guliz Tuncay787aa8c2017-06-19 17:15:40 -0700947 onServiceDisconnectedInnerLocked(name);
948 }
949 }
950
951 private void onServiceDisconnectedInnerLocked(ComponentName name) {
952 if (DBG) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700953 Slog.w(TAG, "onServiceDisconnectedInnerLocked: " + name);
Guliz Tuncay787aa8c2017-06-19 17:15:40 -0700954 }
955 final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
956 if (group != null && this == group.mInternalConnection) {
Yohei Yukawa1854cb52017-08-07 10:17:46 -0700957 group.onServiceDisconnectedLocked();
Dianne Hackborn71e14da2011-10-16 16:28:10 -0700958 }
satok988323c2011-06-22 16:38:13 +0900959 }
960 }
961
Yohei Yukawaf14fe142017-06-22 22:30:45 -0700962 private static final class InternalDeathRecipients extends
Guliz Tuncayf982e752017-06-14 09:33:16 -0700963 RemoteCallbackList<ISpellCheckerSessionListener> {
satok988323c2011-06-22 16:38:13 +0900964 private final SpellCheckerBindGroup mGroup;
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800965
Guliz Tuncayf982e752017-06-14 09:33:16 -0700966 public InternalDeathRecipients(SpellCheckerBindGroup group) {
satok988323c2011-06-22 16:38:13 +0900967 mGroup = group;
968 }
969
Guliz Tuncayf982e752017-06-14 09:33:16 -0700970 @Override
971 public void onCallbackDied(ISpellCheckerSessionListener listener) {
Yohei Yukawaf14fe142017-06-22 22:30:45 -0700972 mGroup.removeListener(listener);
satok988323c2011-06-22 16:38:13 +0900973 }
satok988323c2011-06-22 16:38:13 +0900974 }
Satoshi Kataoka00d2d412012-09-28 20:32:33 +0900975
Yohei Yukawa7fa65ee2017-02-08 11:54:05 -0800976 private static final class ISpellCheckerServiceCallbackBinder
977 extends ISpellCheckerServiceCallback.Stub {
978 @NonNull
979 private final SpellCheckerBindGroup mBindGroup;
980 @NonNull
981 private final SessionRequest mRequest;
982
983 ISpellCheckerServiceCallbackBinder(@NonNull final SpellCheckerBindGroup bindGroup,
984 @NonNull final SessionRequest request) {
985 mBindGroup = bindGroup;
986 mRequest = request;
987 }
988
989 @Override
990 public void onSessionCreated(@Nullable ISpellCheckerSession newSession) {
991 mBindGroup.onSessionCreated(newSession, mRequest);
992 }
993 }
satok988323c2011-06-22 16:38:13 +0900994}