summaryrefslogtreecommitdiffstats
path: root/services/java/com
diff options
context:
space:
mode:
authorsatok <satok@google.com>2011-07-21 22:59:48 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2011-07-21 22:59:48 -0700
commit7793719ca97571f5b3636d5a89cb067c71d627bd (patch)
tree99c4feecc245c3676534d6367f181fa4c683a6b7 /services/java/com
parent4ac052688a3011e3a174c78d75898e6888075720 (diff)
parent988323c57bd25a58f05dfa492d9b9c8ab62c5153 (diff)
downloadframeworks_base-7793719ca97571f5b3636d5a89cb067c71d627bd.zip
frameworks_base-7793719ca97571f5b3636d5a89cb067c71d627bd.tar.gz
frameworks_base-7793719ca97571f5b3636d5a89cb067c71d627bd.tar.bz2
Merge "Create TextServiceManager and SpellCheckerService"
Diffstat (limited to 'services/java/com')
-rw-r--r--services/java/com/android/server/SystemServer.java11
-rw-r--r--services/java/com/android/server/TextServicesManagerService.java346
2 files changed, 357 insertions, 0 deletions
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 8c7e279..76665915 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -230,6 +230,7 @@ class ServerThread extends Thread {
WallpaperManagerService wallpaper = null;
LocationManagerService location = null;
CountryDetectorService countryDetector = null;
+ TextServicesManagerService tsms = null;
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
try {
@@ -273,6 +274,14 @@ class ServerThread extends Thread {
}
try {
+ Slog.i(TAG, "Text Service Manager Service");
+ tsms = new TextServicesManagerService(context);
+ ServiceManager.addService(Context.TEXT_SERVICES_MANAGER_SERVICE, tsms);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting Text Service Manager Service", e);
+ }
+
+ try {
Slog.i(TAG, "NetworkStats Service");
networkStats = new NetworkStatsService(context, networkManagement, alarm);
ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats);
@@ -538,6 +547,7 @@ class ServerThread extends Thread {
final LocationManagerService locationF = location;
final CountryDetectorService countryDetectorF = countryDetector;
final NetworkTimeUpdateService networkTimeUpdaterF = networkTimeUpdater;
+ final TextServicesManagerService textServiceManagerServiceF = tsms;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@@ -571,6 +581,7 @@ class ServerThread extends Thread {
if (countryDetectorF != null) countryDetectorF.systemReady();
if (throttleF != null) throttleF.systemReady();
if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady();
+ if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady();
}
});
diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java
new file mode 100644
index 0000000..4a0c837
--- /dev/null
+++ b/services/java/com/android/server/TextServicesManagerService.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import com.android.internal.content.PackageMonitor;
+import com.android.internal.textservice.ISpellCheckerService;
+import com.android.internal.textservice.ISpellCheckerSession;
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesManager;
+import com.android.internal.textservice.ITextServicesSessionListener;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.service.textservice.SpellCheckerService;
+import android.util.Log;
+import android.util.Slog;
+import android.view.textservice.SpellCheckerInfo;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+public class TextServicesManagerService extends ITextServicesManager.Stub {
+ private static final String TAG = TextServicesManagerService.class.getSimpleName();
+ private static final boolean DBG = false;
+
+ private final Context mContext;
+ private boolean mSystemReady;
+ private final TextServicesMonitor mMonitor;
+ private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap =
+ new HashMap<String, SpellCheckerInfo>();
+ private final ArrayList<SpellCheckerInfo> mSpellCheckerList = new ArrayList<SpellCheckerInfo>();
+ private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups =
+ new HashMap<String, SpellCheckerBindGroup>();
+
+ public void systemReady() {
+ if (!mSystemReady) {
+ mSystemReady = true;
+ }
+ }
+
+ public TextServicesManagerService(Context context) {
+ mSystemReady = false;
+ mContext = context;
+ mMonitor = new TextServicesMonitor();
+ mMonitor.register(context, true);
+ synchronized (mSpellCheckerMap) {
+ buildSpellCheckerMapLocked(context, mSpellCheckerList, mSpellCheckerMap);
+ }
+ }
+
+ private class TextServicesMonitor extends PackageMonitor {
+ @Override
+ public void onSomePackagesChanged() {
+ synchronized (mSpellCheckerMap) {
+ buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap);
+ // TODO: Update for each locale
+ SpellCheckerInfo sci = getCurrentSpellChecker(null);
+ if (sci == null) {
+ sci = findAvailSpellCheckerLocked(null, null);
+ if (sci == null) return;
+ // Set the current spell checker if there is one or more spell checkers
+ // available. In this case, "sci" is the first one in the available spell
+ // checkers.
+ setCurrentSpellChecker(sci);
+ }
+ final String packageName = sci.getPackageName();
+ final int change = isPackageDisappearing(packageName);
+ if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) {
+ // Package disappearing
+ setCurrentSpellChecker(findAvailSpellCheckerLocked(null, packageName));
+ } else if (isPackageModified(packageName)) {
+ // Package modified
+ setCurrentSpellChecker(findAvailSpellCheckerLocked(null, packageName));
+ }
+ }
+ }
+ }
+
+ private static void buildSpellCheckerMapLocked(Context context,
+ ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map) {
+ list.clear();
+ map.clear();
+ final PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> services = pm.queryIntentServices(
+ new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
+ final int N = services.size();
+ for (int i = 0; i < N; ++i) {
+ final ResolveInfo ri = services.get(i);
+ final ServiceInfo si = ri.serviceInfo;
+ final ComponentName compName = new ComponentName(si.packageName, si.name);
+ if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
+ Slog.w(TAG, "Skipping text service " + compName
+ + ": it does not require the permission "
+ + android.Manifest.permission.BIND_TEXT_SERVICE);
+ continue;
+ }
+ if (DBG) Slog.d(TAG, "Add: " + compName);
+ final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri);
+ list.add(sci);
+ map.put(sci.getId(), sci);
+ }
+ }
+
+ // TODO: find an appropriate spell checker for specified locale
+ private SpellCheckerInfo findAvailSpellCheckerLocked(String locale, String prefPackage) {
+ final int spellCheckersCount = mSpellCheckerList.size();
+ if (spellCheckersCount == 0) {
+ Slog.w(TAG, "no available spell checker services found");
+ return null;
+ }
+ if (prefPackage != null) {
+ for (int i = 0; i < spellCheckersCount; ++i) {
+ final SpellCheckerInfo sci = mSpellCheckerList.get(i);
+ if (prefPackage.equals(sci.getPackageName())) {
+ return sci;
+ }
+ }
+ }
+ if (spellCheckersCount > 1) {
+ Slog.w(TAG, "more than one spell checker service found, picking first");
+ }
+ return mSpellCheckerList.get(0);
+ }
+
+ // TODO: Save SpellCheckerService by supported languages. Currently only one spell
+ // checker is saved.
+ @Override
+ public SpellCheckerInfo getCurrentSpellChecker(String locale) {
+ synchronized (mSpellCheckerMap) {
+ final String curSpellCheckerId =
+ Settings.Secure.getString(mContext.getContentResolver(),
+ Settings.Secure.SPELL_CHECKER_SERVICE);
+ if (TextUtils.isEmpty(curSpellCheckerId)) {
+ return null;
+ }
+ return mSpellCheckerMap.get(curSpellCheckerId);
+ }
+ }
+
+ @Override
+ public void getSpellCheckerService(SpellCheckerInfo info, String locale,
+ ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener) {
+ if (!mSystemReady) {
+ return;
+ }
+ if (info == null || tsListener == null) {
+ Slog.e(TAG, "getSpellCheckerService: Invalid input.");
+ return;
+ }
+ final String sciId = info.getId();
+ synchronized(mSpellCheckerMap) {
+ if (!mSpellCheckerMap.containsKey(sciId)) {
+ return;
+ }
+ if (mSpellCheckerBindGroups.containsKey(sciId)) {
+ mSpellCheckerBindGroups.get(sciId).addListener(tsListener, locale, scListener);
+ return;
+ }
+ final InternalServiceConnection connection = new InternalServiceConnection(
+ sciId, locale, scListener);
+ final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
+ serviceIntent.setComponent(info.getComponent());
+ if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
+ Slog.e(TAG, "Failed to get a spell checker service.");
+ return;
+ }
+ final SpellCheckerBindGroup group = new SpellCheckerBindGroup(
+ connection, tsListener, locale, scListener);
+ mSpellCheckerBindGroups.put(sciId, group);
+ }
+ return;
+ }
+
+ @Override
+ public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
+ synchronized(mSpellCheckerMap) {
+ for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) {
+ if (group == null) continue;
+ group.removeListener(listener);
+ }
+ }
+ }
+
+ private void setCurrentSpellChecker(SpellCheckerInfo sci) {
+ if (sci == null || mSpellCheckerMap.containsKey(sci.getId())) return;
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.SPELL_CHECKER_SERVICE, sci == null ? "" : sci.getId());
+ }
+
+ // SpellCheckerBindGroup contains active text service session listeners.
+ // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
+ // mSpellCheckerBindGroups
+ private class SpellCheckerBindGroup {
+ final InternalServiceConnection mInternalConnection;
+ final ArrayList<InternalDeathRecipient> mListeners =
+ new ArrayList<InternalDeathRecipient>();
+
+ public SpellCheckerBindGroup(InternalServiceConnection connection,
+ ITextServicesSessionListener listener, String locale,
+ ISpellCheckerSessionListener scListener) {
+ mInternalConnection = connection;
+ addListener(listener, locale, scListener);
+ }
+
+ public void onServiceConnected(ISpellCheckerService spellChecker) {
+ synchronized(mSpellCheckerMap) {
+ for (InternalDeathRecipient listener : mListeners) {
+ try {
+ final ISpellCheckerSession session = spellChecker.getISpellCheckerSession(
+ listener.mScLocale, listener.mScListener);
+ listener.mTsListener.onServiceConnected(session);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ }
+
+ public void addListener(ITextServicesSessionListener tsListener, String locale,
+ ISpellCheckerSessionListener scListener) {
+ synchronized(mSpellCheckerMap) {
+ try {
+ final int size = mListeners.size();
+ for (int i = 0; i < size; ++i) {
+ if (mListeners.get(i).hasSpellCheckerListener(scListener)) {
+ // do not add the lister if the group already contains this.
+ return;
+ }
+ }
+ final InternalDeathRecipient recipient = new InternalDeathRecipient(
+ this, tsListener, locale, scListener);
+ scListener.asBinder().linkToDeath(recipient, 0);
+ mListeners.add(new InternalDeathRecipient(
+ this, tsListener, locale, scListener));
+ } catch(RemoteException e) {
+ // do nothing
+ }
+ cleanLocked();
+ }
+ }
+
+ public void removeListener(ISpellCheckerSessionListener listener) {
+ synchronized(mSpellCheckerMap) {
+ final int size = mListeners.size();
+ final ArrayList<InternalDeathRecipient> removeList =
+ new ArrayList<InternalDeathRecipient>();
+ for (int i = 0; i < size; ++i) {
+ final InternalDeathRecipient tempRecipient = mListeners.get(i);
+ if(tempRecipient.hasSpellCheckerListener(listener)) {
+ removeList.add(tempRecipient);
+ }
+ }
+ final int removeSize = removeList.size();
+ for (int i = 0; i < removeSize; ++i) {
+ mListeners.remove(removeList.get(i));
+ }
+ cleanLocked();
+ }
+ }
+
+ private void cleanLocked() {
+ if (mListeners.isEmpty()) {
+ mSpellCheckerBindGroups.remove(this);
+ // Unbind service when there is no active clients.
+ mContext.unbindService(mInternalConnection);
+ }
+ }
+ }
+
+ private class InternalServiceConnection implements ServiceConnection {
+ private final ISpellCheckerSessionListener mListener;
+ private final String mSciId;
+ private final String mLocale;
+ public InternalServiceConnection(
+ String id, String locale, ISpellCheckerSessionListener listener) {
+ mSciId = id;
+ mLocale = locale;
+ mListener = listener;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized(mSpellCheckerMap) {
+ ISpellCheckerService spellChecker = ISpellCheckerService.Stub.asInterface(service);
+ final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
+ if (group != null) {
+ group.onServiceConnected(spellChecker);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mSpellCheckerBindGroups.remove(mSciId);
+ }
+ }
+
+ private class InternalDeathRecipient implements IBinder.DeathRecipient {
+ public final ITextServicesSessionListener mTsListener;
+ public final ISpellCheckerSessionListener mScListener;
+ public final String mScLocale;
+ private final SpellCheckerBindGroup mGroup;
+ public InternalDeathRecipient(SpellCheckerBindGroup group,
+ ITextServicesSessionListener tsListener, String scLocale,
+ ISpellCheckerSessionListener scListener) {
+ mTsListener = tsListener;
+ mScListener = scListener;
+ mScLocale = scLocale;
+ mGroup = group;
+ }
+
+ public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) {
+ return mScListener.equals(listener);
+ }
+
+ @Override
+ public void binderDied() {
+ mGroup.removeListener(mScListener);
+ }
+ }
+}