/services/java/com/android/server/TextServicesManagerService.java

https://github.com/aizuzi/platform_frameworks_base · Java · 983 lines · 864 code · 62 blank · 57 comment · 180 complexity · d1515a480b10f890ee207bed754a0e15 MD5 · raw file

  1. /*
  2. * Copyright (C) 2011 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.android.server;
  17. import com.android.internal.content.PackageMonitor;
  18. import com.android.internal.textservice.ISpellCheckerService;
  19. import com.android.internal.textservice.ISpellCheckerSession;
  20. import com.android.internal.textservice.ISpellCheckerSessionListener;
  21. import com.android.internal.textservice.ITextServicesManager;
  22. import com.android.internal.textservice.ITextServicesSessionListener;
  23. import org.xmlpull.v1.XmlPullParserException;
  24. import android.app.ActivityManagerNative;
  25. import android.app.AppGlobals;
  26. import android.app.IUserSwitchObserver;
  27. import android.content.ComponentName;
  28. import android.content.ContentResolver;
  29. import android.content.Context;
  30. import android.content.Intent;
  31. import android.content.ServiceConnection;
  32. import android.content.pm.IPackageManager;
  33. import android.content.pm.PackageManager;
  34. import android.content.pm.ResolveInfo;
  35. import android.content.pm.ServiceInfo;
  36. import android.os.Binder;
  37. import android.os.Bundle;
  38. import android.os.IBinder;
  39. import android.os.IRemoteCallback;
  40. import android.os.Process;
  41. import android.os.RemoteException;
  42. import android.os.UserHandle;
  43. import android.provider.Settings;
  44. import android.service.textservice.SpellCheckerService;
  45. import android.text.TextUtils;
  46. import android.util.Slog;
  47. import android.view.inputmethod.InputMethodManager;
  48. import android.view.inputmethod.InputMethodSubtype;
  49. import android.view.textservice.SpellCheckerInfo;
  50. import android.view.textservice.SpellCheckerSubtype;
  51. import java.io.FileDescriptor;
  52. import java.io.IOException;
  53. import java.io.PrintWriter;
  54. import java.util.ArrayList;
  55. import java.util.HashMap;
  56. import java.util.List;
  57. import java.util.Map;
  58. import java.util.concurrent.CopyOnWriteArrayList;
  59. public class TextServicesManagerService extends ITextServicesManager.Stub {
  60. private static final String TAG = TextServicesManagerService.class.getSimpleName();
  61. private static final boolean DBG = false;
  62. private final Context mContext;
  63. private boolean mSystemReady;
  64. private final TextServicesMonitor mMonitor;
  65. private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap =
  66. new HashMap<String, SpellCheckerInfo>();
  67. private final ArrayList<SpellCheckerInfo> mSpellCheckerList = new ArrayList<SpellCheckerInfo>();
  68. private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups =
  69. new HashMap<String, SpellCheckerBindGroup>();
  70. private final TextServicesSettings mSettings;
  71. public void systemRunning() {
  72. if (!mSystemReady) {
  73. mSystemReady = true;
  74. }
  75. }
  76. public TextServicesManagerService(Context context) {
  77. mSystemReady = false;
  78. mContext = context;
  79. int userId = UserHandle.USER_OWNER;
  80. try {
  81. ActivityManagerNative.getDefault().registerUserSwitchObserver(
  82. new IUserSwitchObserver.Stub() {
  83. @Override
  84. public void onUserSwitching(int newUserId, IRemoteCallback reply) {
  85. synchronized(mSpellCheckerMap) {
  86. switchUserLocked(newUserId);
  87. }
  88. if (reply != null) {
  89. try {
  90. reply.sendResult(null);
  91. } catch (RemoteException e) {
  92. }
  93. }
  94. }
  95. @Override
  96. public void onUserSwitchComplete(int newUserId) throws RemoteException {
  97. }
  98. });
  99. userId = ActivityManagerNative.getDefault().getCurrentUser().id;
  100. } catch (RemoteException e) {
  101. Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
  102. }
  103. mMonitor = new TextServicesMonitor();
  104. mMonitor.register(context, null, true);
  105. mSettings = new TextServicesSettings(context.getContentResolver(), userId);
  106. // "switchUserLocked" initializes the states for the foreground user
  107. switchUserLocked(userId);
  108. }
  109. private void switchUserLocked(int userId) {
  110. mSettings.setCurrentUserId(userId);
  111. unbindServiceLocked();
  112. buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap, mSettings);
  113. SpellCheckerInfo sci = getCurrentSpellChecker(null);
  114. if (sci == null) {
  115. sci = findAvailSpellCheckerLocked(null, null);
  116. if (sci != null) {
  117. // Set the current spell checker if there is one or more spell checkers
  118. // available. In this case, "sci" is the first one in the available spell
  119. // checkers.
  120. setCurrentSpellCheckerLocked(sci.getId());
  121. }
  122. }
  123. }
  124. private class TextServicesMonitor extends PackageMonitor {
  125. private boolean isChangingPackagesOfCurrentUser() {
  126. final int userId = getChangingUserId();
  127. final boolean retval = userId == mSettings.getCurrentUserId();
  128. if (DBG) {
  129. Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
  130. }
  131. return retval;
  132. }
  133. @Override
  134. public void onSomePackagesChanged() {
  135. if (!isChangingPackagesOfCurrentUser()) {
  136. return;
  137. }
  138. synchronized (mSpellCheckerMap) {
  139. buildSpellCheckerMapLocked(
  140. mContext, mSpellCheckerList, mSpellCheckerMap, mSettings);
  141. // TODO: Update for each locale
  142. SpellCheckerInfo sci = getCurrentSpellChecker(null);
  143. // If no spell checker is enabled, just return. The user should explicitly
  144. // enable the spell checker.
  145. if (sci == null) return;
  146. final String packageName = sci.getPackageName();
  147. final int change = isPackageDisappearing(packageName);
  148. if (// Package disappearing
  149. change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE
  150. // Package modified
  151. || isPackageModified(packageName)) {
  152. sci = findAvailSpellCheckerLocked(null, packageName);
  153. if (sci != null) {
  154. setCurrentSpellCheckerLocked(sci.getId());
  155. }
  156. }
  157. }
  158. }
  159. }
  160. private static void buildSpellCheckerMapLocked(Context context,
  161. ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map,
  162. TextServicesSettings settings) {
  163. list.clear();
  164. map.clear();
  165. final PackageManager pm = context.getPackageManager();
  166. final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
  167. new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA,
  168. settings.getCurrentUserId());
  169. final int N = services.size();
  170. for (int i = 0; i < N; ++i) {
  171. final ResolveInfo ri = services.get(i);
  172. final ServiceInfo si = ri.serviceInfo;
  173. final ComponentName compName = new ComponentName(si.packageName, si.name);
  174. if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
  175. Slog.w(TAG, "Skipping text service " + compName
  176. + ": it does not require the permission "
  177. + android.Manifest.permission.BIND_TEXT_SERVICE);
  178. continue;
  179. }
  180. if (DBG) Slog.d(TAG, "Add: " + compName);
  181. try {
  182. final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri);
  183. if (sci.getSubtypeCount() <= 0) {
  184. Slog.w(TAG, "Skipping text service " + compName
  185. + ": it does not contain subtypes.");
  186. continue;
  187. }
  188. list.add(sci);
  189. map.put(sci.getId(), sci);
  190. } catch (XmlPullParserException e) {
  191. Slog.w(TAG, "Unable to load the spell checker " + compName, e);
  192. } catch (IOException e) {
  193. Slog.w(TAG, "Unable to load the spell checker " + compName, e);
  194. }
  195. }
  196. if (DBG) {
  197. Slog.d(TAG, "buildSpellCheckerMapLocked: " + list.size() + "," + map.size());
  198. }
  199. }
  200. // ---------------------------------------------------------------------------------------
  201. // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
  202. // 1) it comes from the system process
  203. // 2) the calling process' user id is identical to the current user id TSMS thinks.
  204. private boolean calledFromValidUser() {
  205. final int uid = Binder.getCallingUid();
  206. final int userId = UserHandle.getUserId(uid);
  207. if (DBG) {
  208. Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
  209. + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
  210. + " calling userId = " + userId + ", foreground user id = "
  211. + mSettings.getCurrentUserId());
  212. try {
  213. final String[] packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
  214. for (int i = 0; i < packageNames.length; ++i) {
  215. if (DBG) {
  216. Slog.d(TAG, "--- process name for "+ uid + " = " + packageNames[i]);
  217. }
  218. }
  219. } catch (RemoteException e) {
  220. }
  221. }
  222. if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
  223. return true;
  224. } else {
  225. Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace());
  226. return false;
  227. }
  228. }
  229. private boolean bindCurrentSpellCheckerService(
  230. Intent service, ServiceConnection conn, int flags) {
  231. if (service == null || conn == null) {
  232. Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
  233. return false;
  234. }
  235. return mContext.bindServiceAsUser(service, conn, flags,
  236. new UserHandle(mSettings.getCurrentUserId()));
  237. }
  238. private void unbindServiceLocked() {
  239. for (SpellCheckerBindGroup scbg : mSpellCheckerBindGroups.values()) {
  240. scbg.removeAll();
  241. }
  242. mSpellCheckerBindGroups.clear();
  243. }
  244. // TODO: find an appropriate spell checker for specified locale
  245. private SpellCheckerInfo findAvailSpellCheckerLocked(String locale, String prefPackage) {
  246. final int spellCheckersCount = mSpellCheckerList.size();
  247. if (spellCheckersCount == 0) {
  248. Slog.w(TAG, "no available spell checker services found");
  249. return null;
  250. }
  251. if (prefPackage != null) {
  252. for (int i = 0; i < spellCheckersCount; ++i) {
  253. final SpellCheckerInfo sci = mSpellCheckerList.get(i);
  254. if (prefPackage.equals(sci.getPackageName())) {
  255. if (DBG) {
  256. Slog.d(TAG, "findAvailSpellCheckerLocked: " + sci.getPackageName());
  257. }
  258. return sci;
  259. }
  260. }
  261. }
  262. if (spellCheckersCount > 1) {
  263. Slog.w(TAG, "more than one spell checker service found, picking first");
  264. }
  265. return mSpellCheckerList.get(0);
  266. }
  267. // TODO: Save SpellCheckerService by supported languages. Currently only one spell
  268. // checker is saved.
  269. @Override
  270. public SpellCheckerInfo getCurrentSpellChecker(String locale) {
  271. // TODO: Make this work even for non-current users?
  272. if (!calledFromValidUser()) {
  273. return null;
  274. }
  275. synchronized (mSpellCheckerMap) {
  276. final String curSpellCheckerId = mSettings.getSelectedSpellChecker();
  277. if (DBG) {
  278. Slog.w(TAG, "getCurrentSpellChecker: " + curSpellCheckerId);
  279. }
  280. if (TextUtils.isEmpty(curSpellCheckerId)) {
  281. return null;
  282. }
  283. return mSpellCheckerMap.get(curSpellCheckerId);
  284. }
  285. }
  286. // TODO: Respect allowImplicitlySelectedSubtype
  287. // TODO: Save SpellCheckerSubtype by supported languages by looking at "locale".
  288. @Override
  289. public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
  290. String locale, boolean allowImplicitlySelectedSubtype) {
  291. // TODO: Make this work even for non-current users?
  292. if (!calledFromValidUser()) {
  293. return null;
  294. }
  295. synchronized (mSpellCheckerMap) {
  296. final String subtypeHashCodeStr = mSettings.getSelectedSpellCheckerSubtype();
  297. if (DBG) {
  298. Slog.w(TAG, "getCurrentSpellCheckerSubtype: " + subtypeHashCodeStr);
  299. }
  300. final SpellCheckerInfo sci = getCurrentSpellChecker(null);
  301. if (sci == null || sci.getSubtypeCount() == 0) {
  302. if (DBG) {
  303. Slog.w(TAG, "Subtype not found.");
  304. }
  305. return null;
  306. }
  307. final int hashCode;
  308. if (!TextUtils.isEmpty(subtypeHashCodeStr)) {
  309. hashCode = Integer.valueOf(subtypeHashCodeStr);
  310. } else {
  311. hashCode = 0;
  312. }
  313. if (hashCode == 0 && !allowImplicitlySelectedSubtype) {
  314. return null;
  315. }
  316. String candidateLocale = null;
  317. if (hashCode == 0) {
  318. // Spell checker language settings == "auto"
  319. final InputMethodManager imm =
  320. (InputMethodManager)mContext.getSystemService(Context.INPUT_METHOD_SERVICE);
  321. if (imm != null) {
  322. final InputMethodSubtype currentInputMethodSubtype =
  323. imm.getCurrentInputMethodSubtype();
  324. if (currentInputMethodSubtype != null) {
  325. final String localeString = currentInputMethodSubtype.getLocale();
  326. if (!TextUtils.isEmpty(localeString)) {
  327. // 1. Use keyboard locale if available in the spell checker
  328. candidateLocale = localeString;
  329. }
  330. }
  331. }
  332. if (candidateLocale == null) {
  333. // 2. Use System locale if available in the spell checker
  334. candidateLocale = mContext.getResources().getConfiguration().locale.toString();
  335. }
  336. }
  337. SpellCheckerSubtype candidate = null;
  338. for (int i = 0; i < sci.getSubtypeCount(); ++i) {
  339. final SpellCheckerSubtype scs = sci.getSubtypeAt(i);
  340. if (hashCode == 0) {
  341. final String scsLocale = scs.getLocale();
  342. if (candidateLocale.equals(scsLocale)) {
  343. return scs;
  344. } else if (candidate == null) {
  345. if (candidateLocale.length() >= 2 && scsLocale.length() >= 2
  346. && candidateLocale.startsWith(scsLocale)) {
  347. // Fall back to the applicable language
  348. candidate = scs;
  349. }
  350. }
  351. } else if (scs.hashCode() == hashCode) {
  352. if (DBG) {
  353. Slog.w(TAG, "Return subtype " + scs.hashCode() + ", input= " + locale
  354. + ", " + scs.getLocale());
  355. }
  356. // 3. Use the user specified spell check language
  357. return scs;
  358. }
  359. }
  360. // 4. Fall back to the applicable language and return it if not null
  361. // 5. Simply just return it even if it's null which means we could find no suitable
  362. // spell check languages
  363. return candidate;
  364. }
  365. }
  366. @Override
  367. public void getSpellCheckerService(String sciId, String locale,
  368. ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
  369. Bundle bundle) {
  370. if (!calledFromValidUser()) {
  371. return;
  372. }
  373. if (!mSystemReady) {
  374. return;
  375. }
  376. if (TextUtils.isEmpty(sciId) || tsListener == null || scListener == null) {
  377. Slog.e(TAG, "getSpellCheckerService: Invalid input.");
  378. return;
  379. }
  380. synchronized(mSpellCheckerMap) {
  381. if (!mSpellCheckerMap.containsKey(sciId)) {
  382. return;
  383. }
  384. final SpellCheckerInfo sci = mSpellCheckerMap.get(sciId);
  385. final int uid = Binder.getCallingUid();
  386. if (mSpellCheckerBindGroups.containsKey(sciId)) {
  387. final SpellCheckerBindGroup bindGroup = mSpellCheckerBindGroups.get(sciId);
  388. if (bindGroup != null) {
  389. final InternalDeathRecipient recipient =
  390. mSpellCheckerBindGroups.get(sciId).addListener(
  391. tsListener, locale, scListener, uid, bundle);
  392. if (recipient == null) {
  393. if (DBG) {
  394. Slog.w(TAG, "Didn't create a death recipient.");
  395. }
  396. return;
  397. }
  398. if (bindGroup.mSpellChecker == null & bindGroup.mConnected) {
  399. Slog.e(TAG, "The state of the spell checker bind group is illegal.");
  400. bindGroup.removeAll();
  401. } else if (bindGroup.mSpellChecker != null) {
  402. if (DBG) {
  403. Slog.w(TAG, "Existing bind found. Return a spell checker session now. "
  404. + "Listeners count = " + bindGroup.mListeners.size());
  405. }
  406. try {
  407. final ISpellCheckerSession session =
  408. bindGroup.mSpellChecker.getISpellCheckerSession(
  409. recipient.mScLocale, recipient.mScListener, bundle);
  410. if (session != null) {
  411. tsListener.onServiceConnected(session);
  412. return;
  413. } else {
  414. if (DBG) {
  415. Slog.w(TAG, "Existing bind already expired. ");
  416. }
  417. bindGroup.removeAll();
  418. }
  419. } catch (RemoteException e) {
  420. Slog.e(TAG, "Exception in getting spell checker session: " + e);
  421. bindGroup.removeAll();
  422. }
  423. }
  424. }
  425. }
  426. final long ident = Binder.clearCallingIdentity();
  427. try {
  428. startSpellCheckerServiceInnerLocked(
  429. sci, locale, tsListener, scListener, uid, bundle);
  430. } finally {
  431. Binder.restoreCallingIdentity(ident);
  432. }
  433. }
  434. return;
  435. }
  436. @Override
  437. public boolean isSpellCheckerEnabled() {
  438. if (!calledFromValidUser()) {
  439. return false;
  440. }
  441. synchronized(mSpellCheckerMap) {
  442. return isSpellCheckerEnabledLocked();
  443. }
  444. }
  445. private void startSpellCheckerServiceInnerLocked(SpellCheckerInfo info, String locale,
  446. ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener,
  447. int uid, Bundle bundle) {
  448. if (DBG) {
  449. Slog.w(TAG, "Start spell checker session inner locked.");
  450. }
  451. final String sciId = info.getId();
  452. final InternalServiceConnection connection = new InternalServiceConnection(
  453. sciId, locale, bundle);
  454. final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
  455. serviceIntent.setComponent(info.getComponent());
  456. if (DBG) {
  457. Slog.w(TAG, "bind service: " + info.getId());
  458. }
  459. if (!bindCurrentSpellCheckerService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
  460. Slog.e(TAG, "Failed to get a spell checker service.");
  461. return;
  462. }
  463. final SpellCheckerBindGroup group = new SpellCheckerBindGroup(
  464. connection, tsListener, locale, scListener, uid, bundle);
  465. mSpellCheckerBindGroups.put(sciId, group);
  466. }
  467. @Override
  468. public SpellCheckerInfo[] getEnabledSpellCheckers() {
  469. // TODO: Make this work even for non-current users?
  470. if (!calledFromValidUser()) {
  471. return null;
  472. }
  473. if (DBG) {
  474. Slog.d(TAG, "getEnabledSpellCheckers: " + mSpellCheckerList.size());
  475. for (int i = 0; i < mSpellCheckerList.size(); ++i) {
  476. Slog.d(TAG, "EnabledSpellCheckers: " + mSpellCheckerList.get(i).getPackageName());
  477. }
  478. }
  479. return mSpellCheckerList.toArray(new SpellCheckerInfo[mSpellCheckerList.size()]);
  480. }
  481. @Override
  482. public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
  483. if (!calledFromValidUser()) {
  484. return;
  485. }
  486. if (DBG) {
  487. Slog.d(TAG, "FinishSpellCheckerService");
  488. }
  489. synchronized(mSpellCheckerMap) {
  490. final ArrayList<SpellCheckerBindGroup> removeList =
  491. new ArrayList<SpellCheckerBindGroup>();
  492. for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) {
  493. if (group == null) continue;
  494. // Use removeList to avoid modifying mSpellCheckerBindGroups in this loop.
  495. removeList.add(group);
  496. }
  497. final int removeSize = removeList.size();
  498. for (int i = 0; i < removeSize; ++i) {
  499. removeList.get(i).removeListener(listener);
  500. }
  501. }
  502. }
  503. @Override
  504. public void setCurrentSpellChecker(String locale, String sciId) {
  505. if (!calledFromValidUser()) {
  506. return;
  507. }
  508. synchronized(mSpellCheckerMap) {
  509. if (mContext.checkCallingOrSelfPermission(
  510. android.Manifest.permission.WRITE_SECURE_SETTINGS)
  511. != PackageManager.PERMISSION_GRANTED) {
  512. throw new SecurityException(
  513. "Requires permission "
  514. + android.Manifest.permission.WRITE_SECURE_SETTINGS);
  515. }
  516. setCurrentSpellCheckerLocked(sciId);
  517. }
  518. }
  519. @Override
  520. public void setCurrentSpellCheckerSubtype(String locale, int hashCode) {
  521. if (!calledFromValidUser()) {
  522. return;
  523. }
  524. synchronized(mSpellCheckerMap) {
  525. if (mContext.checkCallingOrSelfPermission(
  526. android.Manifest.permission.WRITE_SECURE_SETTINGS)
  527. != PackageManager.PERMISSION_GRANTED) {
  528. throw new SecurityException(
  529. "Requires permission "
  530. + android.Manifest.permission.WRITE_SECURE_SETTINGS);
  531. }
  532. setCurrentSpellCheckerSubtypeLocked(hashCode);
  533. }
  534. }
  535. @Override
  536. public void setSpellCheckerEnabled(boolean enabled) {
  537. if (!calledFromValidUser()) {
  538. return;
  539. }
  540. synchronized(mSpellCheckerMap) {
  541. if (mContext.checkCallingOrSelfPermission(
  542. android.Manifest.permission.WRITE_SECURE_SETTINGS)
  543. != PackageManager.PERMISSION_GRANTED) {
  544. throw new SecurityException(
  545. "Requires permission "
  546. + android.Manifest.permission.WRITE_SECURE_SETTINGS);
  547. }
  548. setSpellCheckerEnabledLocked(enabled);
  549. }
  550. }
  551. private void setCurrentSpellCheckerLocked(String sciId) {
  552. if (DBG) {
  553. Slog.w(TAG, "setCurrentSpellChecker: " + sciId);
  554. }
  555. if (TextUtils.isEmpty(sciId) || !mSpellCheckerMap.containsKey(sciId)) return;
  556. final SpellCheckerInfo currentSci = getCurrentSpellChecker(null);
  557. if (currentSci != null && currentSci.getId().equals(sciId)) {
  558. // Do nothing if the current spell checker is same as new spell checker.
  559. return;
  560. }
  561. final long ident = Binder.clearCallingIdentity();
  562. try {
  563. mSettings.putSelectedSpellChecker(sciId);
  564. setCurrentSpellCheckerSubtypeLocked(0);
  565. } finally {
  566. Binder.restoreCallingIdentity(ident);
  567. }
  568. }
  569. private void setCurrentSpellCheckerSubtypeLocked(int hashCode) {
  570. if (DBG) {
  571. Slog.w(TAG, "setCurrentSpellCheckerSubtype: " + hashCode);
  572. }
  573. final SpellCheckerInfo sci = getCurrentSpellChecker(null);
  574. int tempHashCode = 0;
  575. for (int i = 0; sci != null && i < sci.getSubtypeCount(); ++i) {
  576. if(sci.getSubtypeAt(i).hashCode() == hashCode) {
  577. tempHashCode = hashCode;
  578. break;
  579. }
  580. }
  581. final long ident = Binder.clearCallingIdentity();
  582. try {
  583. mSettings.putSelectedSpellCheckerSubtype(tempHashCode);
  584. } finally {
  585. Binder.restoreCallingIdentity(ident);
  586. }
  587. }
  588. private void setSpellCheckerEnabledLocked(boolean enabled) {
  589. if (DBG) {
  590. Slog.w(TAG, "setSpellCheckerEnabled: " + enabled);
  591. }
  592. final long ident = Binder.clearCallingIdentity();
  593. try {
  594. mSettings.setSpellCheckerEnabled(enabled);
  595. } finally {
  596. Binder.restoreCallingIdentity(ident);
  597. }
  598. }
  599. private boolean isSpellCheckerEnabledLocked() {
  600. final long ident = Binder.clearCallingIdentity();
  601. try {
  602. final boolean retval = mSettings.isSpellCheckerEnabled();
  603. if (DBG) {
  604. Slog.w(TAG, "getSpellCheckerEnabled: " + retval);
  605. }
  606. return retval;
  607. } finally {
  608. Binder.restoreCallingIdentity(ident);
  609. }
  610. }
  611. @Override
  612. protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
  613. if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
  614. != PackageManager.PERMISSION_GRANTED) {
  615. pw.println("Permission Denial: can't dump TextServicesManagerService from from pid="
  616. + Binder.getCallingPid()
  617. + ", uid=" + Binder.getCallingUid());
  618. return;
  619. }
  620. synchronized(mSpellCheckerMap) {
  621. pw.println("Current Text Services Manager state:");
  622. pw.println(" Spell Checker Map:");
  623. for (Map.Entry<String, SpellCheckerInfo> ent : mSpellCheckerMap.entrySet()) {
  624. pw.print(" "); pw.print(ent.getKey()); pw.println(":");
  625. SpellCheckerInfo info = ent.getValue();
  626. pw.print(" "); pw.print("id="); pw.println(info.getId());
  627. pw.print(" "); pw.print("comp=");
  628. pw.println(info.getComponent().toShortString());
  629. int NS = info.getSubtypeCount();
  630. for (int i=0; i<NS; i++) {
  631. SpellCheckerSubtype st = info.getSubtypeAt(i);
  632. pw.print(" "); pw.print("Subtype #"); pw.print(i); pw.println(":");
  633. pw.print(" "); pw.print("locale="); pw.println(st.getLocale());
  634. pw.print(" "); pw.print("extraValue=");
  635. pw.println(st.getExtraValue());
  636. }
  637. }
  638. pw.println("");
  639. pw.println(" Spell Checker Bind Groups:");
  640. for (Map.Entry<String, SpellCheckerBindGroup> ent
  641. : mSpellCheckerBindGroups.entrySet()) {
  642. SpellCheckerBindGroup grp = ent.getValue();
  643. pw.print(" "); pw.print(ent.getKey()); pw.print(" ");
  644. pw.print(grp); pw.println(":");
  645. pw.print(" "); pw.print("mInternalConnection=");
  646. pw.println(grp.mInternalConnection);
  647. pw.print(" "); pw.print("mSpellChecker=");
  648. pw.println(grp.mSpellChecker);
  649. pw.print(" "); pw.print("mBound="); pw.print(grp.mBound);
  650. pw.print(" mConnected="); pw.println(grp.mConnected);
  651. int NL = grp.mListeners.size();
  652. for (int i=0; i<NL; i++) {
  653. InternalDeathRecipient listener = grp.mListeners.get(i);
  654. pw.print(" "); pw.print("Listener #"); pw.print(i); pw.println(":");
  655. pw.print(" "); pw.print("mTsListener=");
  656. pw.println(listener.mTsListener);
  657. pw.print(" "); pw.print("mScListener=");
  658. pw.println(listener.mScListener);
  659. pw.print(" "); pw.print("mGroup=");
  660. pw.println(listener.mGroup);
  661. pw.print(" "); pw.print("mScLocale=");
  662. pw.print(listener.mScLocale);
  663. pw.print(" mUid="); pw.println(listener.mUid);
  664. }
  665. }
  666. }
  667. }
  668. // SpellCheckerBindGroup contains active text service session listeners.
  669. // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
  670. // mSpellCheckerBindGroups
  671. private class SpellCheckerBindGroup {
  672. private final String TAG = SpellCheckerBindGroup.class.getSimpleName();
  673. private final InternalServiceConnection mInternalConnection;
  674. private final CopyOnWriteArrayList<InternalDeathRecipient> mListeners =
  675. new CopyOnWriteArrayList<InternalDeathRecipient>();
  676. public boolean mBound;
  677. public ISpellCheckerService mSpellChecker;
  678. public boolean mConnected;
  679. public SpellCheckerBindGroup(InternalServiceConnection connection,
  680. ITextServicesSessionListener listener, String locale,
  681. ISpellCheckerSessionListener scListener, int uid, Bundle bundle) {
  682. mInternalConnection = connection;
  683. mBound = true;
  684. mConnected = false;
  685. addListener(listener, locale, scListener, uid, bundle);
  686. }
  687. public void onServiceConnected(ISpellCheckerService spellChecker) {
  688. if (DBG) {
  689. Slog.d(TAG, "onServiceConnected");
  690. }
  691. for (InternalDeathRecipient listener : mListeners) {
  692. try {
  693. final ISpellCheckerSession session = spellChecker.getISpellCheckerSession(
  694. listener.mScLocale, listener.mScListener, listener.mBundle);
  695. synchronized(mSpellCheckerMap) {
  696. if (mListeners.contains(listener)) {
  697. listener.mTsListener.onServiceConnected(session);
  698. }
  699. }
  700. } catch (RemoteException e) {
  701. Slog.e(TAG, "Exception in getting the spell checker session."
  702. + "Reconnect to the spellchecker. ", e);
  703. removeAll();
  704. return;
  705. }
  706. }
  707. synchronized(mSpellCheckerMap) {
  708. mSpellChecker = spellChecker;
  709. mConnected = true;
  710. }
  711. }
  712. public InternalDeathRecipient addListener(ITextServicesSessionListener tsListener,
  713. String locale, ISpellCheckerSessionListener scListener, int uid, Bundle bundle) {
  714. if (DBG) {
  715. Slog.d(TAG, "addListener: " + locale);
  716. }
  717. InternalDeathRecipient recipient = null;
  718. synchronized(mSpellCheckerMap) {
  719. try {
  720. final int size = mListeners.size();
  721. for (int i = 0; i < size; ++i) {
  722. if (mListeners.get(i).hasSpellCheckerListener(scListener)) {
  723. // do not add the lister if the group already contains this.
  724. return null;
  725. }
  726. }
  727. recipient = new InternalDeathRecipient(
  728. this, tsListener, locale, scListener, uid, bundle);
  729. scListener.asBinder().linkToDeath(recipient, 0);
  730. mListeners.add(recipient);
  731. } catch(RemoteException e) {
  732. // do nothing
  733. }
  734. cleanLocked();
  735. }
  736. return recipient;
  737. }
  738. public void removeListener(ISpellCheckerSessionListener listener) {
  739. if (DBG) {
  740. Slog.w(TAG, "remove listener: " + listener.hashCode());
  741. }
  742. synchronized(mSpellCheckerMap) {
  743. final int size = mListeners.size();
  744. final ArrayList<InternalDeathRecipient> removeList =
  745. new ArrayList<InternalDeathRecipient>();
  746. for (int i = 0; i < size; ++i) {
  747. final InternalDeathRecipient tempRecipient = mListeners.get(i);
  748. if(tempRecipient.hasSpellCheckerListener(listener)) {
  749. if (DBG) {
  750. Slog.w(TAG, "found existing listener.");
  751. }
  752. removeList.add(tempRecipient);
  753. }
  754. }
  755. final int removeSize = removeList.size();
  756. for (int i = 0; i < removeSize; ++i) {
  757. if (DBG) {
  758. Slog.w(TAG, "Remove " + removeList.get(i));
  759. }
  760. final InternalDeathRecipient idr = removeList.get(i);
  761. idr.mScListener.asBinder().unlinkToDeath(idr, 0);
  762. mListeners.remove(idr);
  763. }
  764. cleanLocked();
  765. }
  766. }
  767. // cleanLocked may remove elements from mSpellCheckerBindGroups
  768. private void cleanLocked() {
  769. if (DBG) {
  770. Slog.d(TAG, "cleanLocked");
  771. }
  772. // If there are no more active listeners, clean up. Only do this
  773. // once.
  774. if (mBound && mListeners.isEmpty()) {
  775. mBound = false;
  776. final String sciId = mInternalConnection.mSciId;
  777. SpellCheckerBindGroup cur = mSpellCheckerBindGroups.get(sciId);
  778. if (cur == this) {
  779. if (DBG) {
  780. Slog.d(TAG, "Remove bind group.");
  781. }
  782. mSpellCheckerBindGroups.remove(sciId);
  783. }
  784. mContext.unbindService(mInternalConnection);
  785. }
  786. }
  787. public void removeAll() {
  788. Slog.e(TAG, "Remove the spell checker bind unexpectedly.");
  789. synchronized(mSpellCheckerMap) {
  790. final int size = mListeners.size();
  791. for (int i = 0; i < size; ++i) {
  792. final InternalDeathRecipient idr = mListeners.get(i);
  793. idr.mScListener.asBinder().unlinkToDeath(idr, 0);
  794. }
  795. mListeners.clear();
  796. cleanLocked();
  797. }
  798. }
  799. }
  800. private class InternalServiceConnection implements ServiceConnection {
  801. private final String mSciId;
  802. private final String mLocale;
  803. private final Bundle mBundle;
  804. public InternalServiceConnection(
  805. String id, String locale, Bundle bundle) {
  806. mSciId = id;
  807. mLocale = locale;
  808. mBundle = bundle;
  809. }
  810. @Override
  811. public void onServiceConnected(ComponentName name, IBinder service) {
  812. synchronized(mSpellCheckerMap) {
  813. onServiceConnectedInnerLocked(name, service);
  814. }
  815. }
  816. private void onServiceConnectedInnerLocked(ComponentName name, IBinder service) {
  817. if (DBG) {
  818. Slog.w(TAG, "onServiceConnected: " + name);
  819. }
  820. final ISpellCheckerService spellChecker =
  821. ISpellCheckerService.Stub.asInterface(service);
  822. final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
  823. if (group != null && this == group.mInternalConnection) {
  824. group.onServiceConnected(spellChecker);
  825. }
  826. }
  827. @Override
  828. public void onServiceDisconnected(ComponentName name) {
  829. synchronized(mSpellCheckerMap) {
  830. final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
  831. if (group != null && this == group.mInternalConnection) {
  832. mSpellCheckerBindGroups.remove(mSciId);
  833. }
  834. }
  835. }
  836. }
  837. private class InternalDeathRecipient implements IBinder.DeathRecipient {
  838. public final ITextServicesSessionListener mTsListener;
  839. public final ISpellCheckerSessionListener mScListener;
  840. public final String mScLocale;
  841. private final SpellCheckerBindGroup mGroup;
  842. public final int mUid;
  843. public final Bundle mBundle;
  844. public InternalDeathRecipient(SpellCheckerBindGroup group,
  845. ITextServicesSessionListener tsListener, String scLocale,
  846. ISpellCheckerSessionListener scListener, int uid, Bundle bundle) {
  847. mTsListener = tsListener;
  848. mScListener = scListener;
  849. mScLocale = scLocale;
  850. mGroup = group;
  851. mUid = uid;
  852. mBundle = bundle;
  853. }
  854. public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) {
  855. return listener.asBinder().equals(mScListener.asBinder());
  856. }
  857. @Override
  858. public void binderDied() {
  859. mGroup.removeListener(mScListener);
  860. }
  861. }
  862. private static class TextServicesSettings {
  863. private final ContentResolver mResolver;
  864. private int mCurrentUserId;
  865. public TextServicesSettings(ContentResolver resolver, int userId) {
  866. mResolver = resolver;
  867. mCurrentUserId = userId;
  868. }
  869. public void setCurrentUserId(int userId) {
  870. if (DBG) {
  871. Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
  872. + userId + ", new ime = " + getSelectedSpellChecker());
  873. }
  874. // TSMS settings are kept per user, so keep track of current user
  875. mCurrentUserId = userId;
  876. }
  877. public int getCurrentUserId() {
  878. return mCurrentUserId;
  879. }
  880. public void putSelectedSpellChecker(String sciId) {
  881. Settings.Secure.putStringForUser(mResolver,
  882. Settings.Secure.SELECTED_SPELL_CHECKER, sciId, mCurrentUserId);
  883. }
  884. public void putSelectedSpellCheckerSubtype(int hashCode) {
  885. Settings.Secure.putStringForUser(mResolver,
  886. Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, String.valueOf(hashCode),
  887. mCurrentUserId);
  888. }
  889. public void setSpellCheckerEnabled(boolean enabled) {
  890. Settings.Secure.putIntForUser(mResolver,
  891. Settings.Secure.SPELL_CHECKER_ENABLED, enabled ? 1 : 0, mCurrentUserId);
  892. }
  893. public String getSelectedSpellChecker() {
  894. return Settings.Secure.getStringForUser(mResolver,
  895. Settings.Secure.SELECTED_SPELL_CHECKER, mCurrentUserId);
  896. }
  897. public String getSelectedSpellCheckerSubtype() {
  898. return Settings.Secure.getStringForUser(mResolver,
  899. Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, mCurrentUserId);
  900. }
  901. public boolean isSpellCheckerEnabled() {
  902. return Settings.Secure.getIntForUser(mResolver,
  903. Settings.Secure.SPELL_CHECKER_ENABLED, 1, mCurrentUserId) == 1;
  904. }
  905. }
  906. // ----------------------------------------------------------------------
  907. // Utilities for debug
  908. private static String getStackTrace() {
  909. final StringBuilder sb = new StringBuilder();
  910. try {
  911. throw new RuntimeException();
  912. } catch (RuntimeException e) {
  913. final StackTraceElement[] frames = e.getStackTrace();
  914. // Start at 1 because the first frame is here and we don't care about it
  915. for (int j = 1; j < frames.length; ++j) {
  916. sb.append(frames[j].toString() + "\n");
  917. }
  918. }
  919. return sb.toString();
  920. }
  921. }