Create TextServiceManager and SpellCheckerService

Bug: 4176026

This CL inherits https://android-git.corp.google.com/g/112600

Spec of TextServiceManager
- Chooses the most applicable TextService(e.g. SpellCheckerService, WordBreakIteratorService..)
    for each locale

Spec of SpellCheckerService
- Returns whether the given string is a correct word or not
- Returns Suggestions for the given string

Change-Id: Iaa425c7915fe70767ad0b17bf6c6fbcd2a1200b2
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d2323e7..6289730 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -83,6 +83,7 @@
 import android.view.WindowManagerImpl;
 import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.InputMethodManager;
+import android.view.textservice.TextServicesManager;
 import android.accounts.AccountManager;
 import android.accounts.IAccountManager;
 import android.app.admin.DevicePolicyManager;
@@ -320,6 +321,11 @@
                     return InputMethodManager.getInstance(ctx);
                 }});
 
+        registerService(TEXT_SERVICES_MANAGER_SERVICE, new ServiceFetcher() {
+                public Object createService(ContextImpl ctx) {
+                    return TextServicesManager.getInstance();
+                }});
+
         registerService(KEYGUARD_SERVICE, new ServiceFetcher() {
                 public Object getService(ContextImpl ctx) {
                     // TODO: why isn't this caching it?  It wasn't
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index fed6d81..0a2253c8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1612,6 +1612,15 @@
 
     /**
      * Use with {@link #getSystemService} to retrieve a
+     * {@link android.view.textservice.TextServicesManager} for accessing
+     * text services.
+     *
+     * @see #getSystemService
+     */
+    public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
+
+    /**
+     * Use with {@link #getSystemService} to retrieve a
      * {@link android.appwidget.AppWidgetManager} for accessing AppWidgets.
      *
      * @hide
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cb87e94..1cd46de 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -16,8 +16,6 @@
 
 package android.provider;
 
-
-
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.app.SearchManager;
@@ -48,7 +46,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 
-
 /**
  * The Settings provider contains global system-level device preferences.
  */
@@ -3737,6 +3734,15 @@
          */
         public static final String VOICE_RECOGNITION_SERVICE = "voice_recognition_service";
 
+
+        /**
+         * The {@link ComponentName} string of the service to be used as the spell checker
+         * service which is one of the services managed by the text service manager.
+         *
+         * @hide
+         */
+        public static final String SPELL_CHECKER_SERVICE = "spell_checker_service";
+
         /**
          * What happens when the user presses the Power button while in-call
          * and the screen is on.<br/>
diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java
new file mode 100644
index 0000000..6ac99ca
--- /dev/null
+++ b/core/java/android/service/textservice/SpellCheckerService.java
@@ -0,0 +1,140 @@
+/*
+ * 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 android.service.textservice;
+
+import com.android.internal.textservice.ISpellCheckerService;
+import com.android.internal.textservice.ISpellCheckerSession;
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.textservice.SuggestionsInfo;
+import android.view.textservice.TextInfo;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * SpellCheckerService provides an abstract base class for a spell checker.
+ * This class combines a service to the system with the spell checker service interface that
+ * spell checker must implement.
+ */
+public abstract class SpellCheckerService extends Service {
+    private static final String TAG = SpellCheckerService.class.getSimpleName();
+    public static final String SERVICE_INTERFACE = SpellCheckerService.class.getName();
+
+    private final SpellCheckerServiceBinder mBinder = new SpellCheckerServiceBinder(this);
+
+    /**
+     * Get suggestions for specified text in TextInfo.
+     * This function will run on the incoming IPC thread. So, this is not called on the main thread,
+     * but will be called in series on another thread.
+     * @param textInfo the text metadata
+     * @param suggestionsLimit the number of limit of suggestions returned
+     * @param locale the locale for getting suggestions
+     * @return SuggestionInfo which contains suggestions for textInfo
+     */
+    public abstract SuggestionsInfo getSuggestions(
+            TextInfo textInfo, int suggestionsLimit, String locale);
+
+    /**
+     * A batch process of onGetSuggestions.
+     * This function will run on the incoming IPC thread. So, this is not called on the main thread,
+     * but will be called in series on another thread.
+     * @param textInfos an array of the text metadata
+     * @param locale the locale for getting suggestions
+     * @param suggestionsLimit the number of limit of suggestions returned
+     * @param sequentialWords true if textInfos can be treated as sequential words.
+     * @return an array of SuggestionInfo of onGetSuggestions
+     */
+    public SuggestionsInfo[] getSuggestionsMultiple(
+            TextInfo[] textInfos, String locale, int suggestionsLimit, boolean sequentialWords) {
+        final int length = textInfos.length;
+        final SuggestionsInfo[] retval = new SuggestionsInfo[length];
+        for (int i = 0; i < length; ++i) {
+            retval[i] = getSuggestions(textInfos[i], suggestionsLimit, locale);
+            retval[i].setCookieAndSequence(textInfos[i].getCookie(), textInfos[i].getSequence());
+        }
+        return retval;
+    }
+
+    /**
+     * Request to abort all tasks executed in SpellChecker.
+     * This function will run on the incoming IPC thread. So, this is not called on the main thread,
+     * but will be called in series on another thread.
+     */
+    public void cancel() {}
+
+    /**
+     * Implement to return the implementation of the internal spell checker
+     * service interface. Subclasses should not override.
+     */
+    @Override
+    public final IBinder onBind(final Intent intent) {
+        return mBinder;
+    }
+
+    private static class SpellCheckerSessionImpl extends ISpellCheckerSession.Stub {
+        private final WeakReference<SpellCheckerService> mInternalServiceRef;
+        private final String mLocale;
+        private final ISpellCheckerSessionListener mListener;
+
+        public SpellCheckerSessionImpl(
+                SpellCheckerService service, String locale, ISpellCheckerSessionListener listener) {
+            mInternalServiceRef = new WeakReference<SpellCheckerService>(service);
+            mLocale = locale;
+            mListener = listener;
+        }
+
+        @Override
+        public void getSuggestionsMultiple(
+                TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
+            final SpellCheckerService service = mInternalServiceRef.get();
+            if (service == null) return;
+            try {
+                mListener.onGetSuggestions(
+                        service.getSuggestionsMultiple(textInfos, mLocale,
+                                suggestionsLimit, sequentialWords));
+            } catch (RemoteException e) {
+            }
+        }
+
+        @Override
+        public void cancel() {
+            final SpellCheckerService service = mInternalServiceRef.get();
+            if (service == null) return;
+            service.cancel();
+        }
+    }
+
+    private static class SpellCheckerServiceBinder extends ISpellCheckerService.Stub {
+        private final WeakReference<SpellCheckerService> mInternalServiceRef;
+
+        public SpellCheckerServiceBinder(SpellCheckerService service) {
+            mInternalServiceRef = new WeakReference<SpellCheckerService>(service);
+        }
+
+        @Override
+        public ISpellCheckerSession getISpellCheckerSession(
+                String locale, ISpellCheckerSessionListener listener) {
+            final SpellCheckerService service = mInternalServiceRef.get();
+            if (service == null) return null;
+            return new SpellCheckerSessionImpl(service, locale, listener);
+        }
+    }
+}
diff --git a/core/java/android/service/textservice/SpellCheckerSession.java b/core/java/android/service/textservice/SpellCheckerSession.java
new file mode 100644
index 0000000..a575220
--- /dev/null
+++ b/core/java/android/service/textservice/SpellCheckerSession.java
@@ -0,0 +1,271 @@
+/*
+ * 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 android.service.textservice;
+
+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.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.textservice.SuggestionsInfo;
+import android.view.textservice.TextInfo;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * The SpellCheckerSession interface provides the per client functionality of SpellCheckerService.
+ */
+public class SpellCheckerSession {
+    private static final String TAG = SpellCheckerSession.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private static final int MSG_ON_GET_SUGGESTION_MULTIPLE = 1;
+
+    private final InternalListener mInternalListener;
+    private final ITextServicesManager mTextServicesManager;
+    private final SpellCheckerSessionListenerImpl mSpellCheckerSessionListenerImpl;
+
+    private boolean mIsUsed;
+    private SpellCheckerSessionListener mSpellCheckerSessionListener;
+
+    /** Handler that will execute the main tasks */
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_ON_GET_SUGGESTION_MULTIPLE:
+                    handleOnGetSuggestionsMultiple((SuggestionsInfo[]) msg.obj);
+                    break;
+            }
+        }
+    };
+
+    /**
+     * Constructor
+     * @hide
+     */
+    public SpellCheckerSession(ITextServicesManager tsm, SpellCheckerSessionListener listener) {
+        if (listener == null || tsm == null) {
+            throw new NullPointerException();
+        }
+        mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler);
+        mInternalListener = new InternalListener();
+        mTextServicesManager = tsm;
+        mIsUsed = true;
+        mSpellCheckerSessionListener = listener;
+    }
+
+    /**
+     * @return true if the connection to a text service of this session is disconnected and not
+     * alive.
+     */
+    public boolean isSessionDisconnected() {
+        return mSpellCheckerSessionListenerImpl.isDisconnected();
+    }
+
+    /**
+     * Finish this session and allow TextServicesManagerService to disconnect the bound spell
+     * checker.
+     */
+    public void close() {
+        mIsUsed = false;
+        try {
+            mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
+        } catch (RemoteException e) {
+            // do nothing
+        }
+    }
+
+    /**
+     * Get candidate strings for a substring of the specified text.
+     * @param textInfo text metadata for a spell checker
+     * @param suggestionsLimit the number of limit of suggestions returned
+     */
+    public void getSuggestions(TextInfo textInfo, int suggestionsLimit) {
+        getSuggestions(new TextInfo[] {textInfo}, suggestionsLimit, false);
+    }
+
+    /**
+     * A batch process of getSuggestions
+     * @param textInfos an array of text metadata for a spell checker
+     * @param suggestionsLimit the number of limit of suggestions returned
+     * @param sequentialWords true if textInfos can be treated as sequential words.
+     */
+    public void getSuggestions(
+            TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
+        // TODO: Handle multiple words suggestions by using WordBreakIterator
+        mSpellCheckerSessionListenerImpl.getSuggestionsMultiple(
+                textInfos, suggestionsLimit, sequentialWords);
+    }
+
+    private void handleOnGetSuggestionsMultiple(SuggestionsInfo[] suggestionInfos) {
+        mSpellCheckerSessionListener.onGetSuggestions(suggestionInfos);
+    }
+
+    private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub {
+        private static final int TASK_CANCEL = 1;
+        private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
+        private final Queue<SpellCheckerParams> mPendingTasks =
+                new LinkedList<SpellCheckerParams>();
+        private final Handler mHandler;
+
+        private boolean mOpened;
+        private ISpellCheckerSession mISpellCheckerSession;
+
+        public SpellCheckerSessionListenerImpl(Handler handler) {
+            mOpened = false;
+            mHandler = handler;
+        }
+
+        private static class SpellCheckerParams {
+            public final int mWhat;
+            public final TextInfo[] mTextInfos;
+            public final int mSuggestionsLimit;
+            public final boolean mSequentialWords;
+            public SpellCheckerParams(int what, TextInfo[] textInfos, int suggestionsLimit,
+                    boolean sequentialWords) {
+                mWhat = what;
+                mTextInfos = textInfos;
+                mSuggestionsLimit = suggestionsLimit;
+                mSequentialWords = sequentialWords;
+            }
+        }
+
+        private void processTask(SpellCheckerParams scp) {
+            switch (scp.mWhat) {
+                case TASK_CANCEL:
+                    processCancel();
+                    break;
+                case TASK_GET_SUGGESTIONS_MULTIPLE:
+                    processGetSuggestionsMultiple(scp);
+                    break;
+            }
+        }
+
+        public synchronized void onServiceConnected(ISpellCheckerSession session) {
+            mISpellCheckerSession = session;
+            mOpened = true;
+            if (DBG)
+                Log.d(TAG, "onServiceConnected - Success");
+            while (!mPendingTasks.isEmpty()) {
+                processTask(mPendingTasks.poll());
+            }
+        }
+
+        public void getSuggestionsMultiple(
+                TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
+            processOrEnqueueTask(
+                    new SpellCheckerParams(TASK_GET_SUGGESTIONS_MULTIPLE, textInfos,
+                            suggestionsLimit, sequentialWords));
+        }
+
+        public boolean isDisconnected() {
+            return mOpened && mISpellCheckerSession == null;
+        }
+
+        public boolean checkOpenConnection() {
+            if (mISpellCheckerSession != null) {
+                return true;
+            }
+            Log.e(TAG, "not connected to the spellchecker service.");
+            return false;
+        }
+
+        private void processOrEnqueueTask(SpellCheckerParams scp) {
+            if (mISpellCheckerSession == null) {
+                mPendingTasks.offer(scp);
+            } else {
+                processTask(scp);
+            }
+        }
+
+        private void processCancel() {
+            if (!checkOpenConnection()) {
+                return;
+            }
+            try {
+                mISpellCheckerSession.cancel();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to cancel " + e);
+            }
+        }
+
+        private void processGetSuggestionsMultiple(SpellCheckerParams scp) {
+            if (!checkOpenConnection()) {
+                return;
+            }
+            try {
+                mISpellCheckerSession.getSuggestionsMultiple(
+                        scp.mTextInfos, scp.mSuggestionsLimit, scp.mSequentialWords);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to get suggestions " + e);
+            }
+        }
+
+        @Override
+        public void onGetSuggestions(SuggestionsInfo[] results) {
+            mHandler.sendMessage(Message.obtain(mHandler, MSG_ON_GET_SUGGESTION_MULTIPLE, results));
+        }
+    }
+
+    /**
+     * Callback for getting results from text services
+     */
+    public interface SpellCheckerSessionListener {
+        /**
+         * Callback for "getSuggestions"
+         * @param results an array of results of getSuggestions
+         */
+        public void onGetSuggestions(SuggestionsInfo[] results);
+    }
+
+    private class InternalListener extends ITextServicesSessionListener.Stub {
+        @Override
+        public void onServiceConnected(ISpellCheckerSession session) {
+            mSpellCheckerSessionListenerImpl.onServiceConnected(session);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        if (mIsUsed) {
+            Log.e(TAG, "SpellCheckerSession was not finished properly." +
+                    "You should call finishShession() when you finished to use a spell checker.");
+            close();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public ITextServicesSessionListener getTextServicesSessionListener() {
+        return mInternalListener;
+    }
+
+    /**
+     * @hide
+     */
+    public ISpellCheckerSessionListener getSpellCheckerSessionListener() {
+        return mSpellCheckerSessionListenerImpl;
+    }
+}
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.aidl b/core/java/android/view/textservice/SpellCheckerInfo.aidl
new file mode 100644
index 0000000..eb5dfcc
--- /dev/null
+++ b/core/java/android/view/textservice/SpellCheckerInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.textservice;
+
+parcelable SpellCheckerInfo;
diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java
new file mode 100644
index 0000000..1205adf
--- /dev/null
+++ b/core/java/android/view/textservice/SpellCheckerInfo.java
@@ -0,0 +1,112 @@
+/*
+ * 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 android.view.textservice;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class is used to specify meta information of an spell checker.
+ */
+public final class SpellCheckerInfo implements Parcelable {
+    private final ResolveInfo mService;
+    private final String mId;
+
+    /**
+     * Constructor.
+     * @hide
+     */
+    public SpellCheckerInfo(Context context, ResolveInfo service) {
+        mService = service;
+        ServiceInfo si = service.serviceInfo;
+        mId = new ComponentName(si.packageName, si.name).flattenToShortString();
+    }
+
+    /**
+     * Constructor.
+     * @hide
+     */
+    public SpellCheckerInfo(Parcel source) {
+        mId = source.readString();
+        mService = ResolveInfo.CREATOR.createFromParcel(source);
+    }
+
+    /**
+     * Return a unique ID for this spell checker.  The ID is generated from
+     * the package and class name implementing the method.
+     */
+    public String getId() {
+        return mId;
+    }
+
+
+    /**
+     * Return the component of the service that implements.
+     */
+    public ComponentName getComponent() {
+        return new ComponentName(
+                mService.serviceInfo.packageName, mService.serviceInfo.name);
+    }
+
+    /**
+     * Return the .apk package that implements this input method.
+     */
+    public String getPackageName() {
+        return mService.serviceInfo.packageName;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mId);
+        mService.writeToParcel(dest, flags);
+    }
+
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<SpellCheckerInfo> CREATOR
+            = new Parcelable.Creator<SpellCheckerInfo>() {
+        @Override
+        public SpellCheckerInfo createFromParcel(Parcel source) {
+            return new SpellCheckerInfo(source);
+        }
+
+        @Override
+        public SpellCheckerInfo[] newArray(int size) {
+            return new SpellCheckerInfo[size];
+        }
+    };
+
+    /**
+     * Used to make this class parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/view/textservice/SuggestionsInfo.aidl b/core/java/android/view/textservice/SuggestionsInfo.aidl
new file mode 100644
index 0000000..66e20d2
--- /dev/null
+++ b/core/java/android/view/textservice/SuggestionsInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.textservice;
+
+parcelable SuggestionsInfo;
diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java
new file mode 100644
index 0000000..e2df7b8
--- /dev/null
+++ b/core/java/android/view/textservice/SuggestionsInfo.java
@@ -0,0 +1,176 @@
+/*
+ * 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 android.view.textservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains a metadata of suggestions from the text service
+ */
+public final class SuggestionsInfo implements Parcelable {
+    /**
+     * Flag of the attributes of the suggestions that can be obtained by
+     * {@link getSuggestionsAttibutes}: this tells that the requested word was found
+     * in the dictionary in the text service.
+     */
+    public static final int RESULT_ATTR_IN_THE_DICTIONARY = 0x0001;
+    /** Flag of the attributes of the suggestions that can be obtained by
+     * {@link getSuggestionsAttibutes}: this tells that there are one or more suggestions available
+     * for the requested word.  This doesn't necessarily mean that the suggestions are actually in
+     * this SuggestionsInfo.  For instance, the caller could have been asked to limit the maximum
+     * number of suggestions returned.
+     */
+    public static final int RESULT_ATTR_SUGGESTIONS_AVAILABLE = 0x0002;
+    /**
+     * Flag of the attributes of the suggestions that can be obtained by
+     * {@link getSuggestionsAttibutes}: this tells that the text service thinks the requested word
+     * looks a typo.
+     */
+    public static final int RESULT_ATTR_LOOKS_TYPO = 0x0004;
+    private final int mSuggestionsAttributes;
+    private final String[] mSuggestions;
+    private int mCookie;
+    private int mSequence;
+
+    /**
+     * Constructor.
+     * @param suggestionsAttributes from the text service
+     * @param suggestions from the text service
+     */
+    public SuggestionsInfo(int suggestionsAttributes, String[] suggestions) {
+        if (suggestions == null) {
+            throw new NullPointerException();
+        }
+        mSuggestionsAttributes = suggestionsAttributes;
+        mSuggestions = suggestions;
+        mCookie = 0;
+        mSequence = 0;
+    }
+
+    /**
+     * Constructor.
+     * @param suggestionsAttributes from the text service
+     * @param suggestions from the text service
+     * @param cookie the cookie of the input TextInfo
+     * @param sequence the cookie of the input TextInfo
+     */
+    public SuggestionsInfo(
+            int suggestionsAttributes, String[] suggestions, int cookie, int sequence) {
+        if (suggestions == null) {
+            throw new NullPointerException();
+        }
+        mSuggestionsAttributes = suggestionsAttributes;
+        mSuggestions = suggestions;
+        mCookie = cookie;
+        mSequence = sequence;
+    }
+
+    public SuggestionsInfo(Parcel source) {
+        mSuggestionsAttributes = source.readInt();
+        mSuggestions = source.readStringArray();
+        mCookie = source.readInt();
+        mSequence = source.readInt();
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSuggestionsAttributes);
+        dest.writeStringArray(mSuggestions);
+        dest.writeInt(mCookie);
+        dest.writeInt(mSequence);
+    }
+
+    /**
+     * Set the cookie and the sequence of SuggestionsInfo which are set to TextInfo from a client
+     * application
+     * @param cookie the cookie of an input TextInfo
+     * @param sequence the cookie of an input TextInfo
+     */
+    public void setCookieAndSequence(int cookie, int sequence) {
+        mCookie = cookie;
+        mSequence = sequence;
+    }
+
+    /**
+     * @return the cookie which may be set by a client application
+     */
+    public int getCookie() {
+        return mCookie;
+    }
+
+    /**
+     * @return the sequence which may be set by a client application
+     */
+    public int getSequence() {
+        return mSequence;
+    }
+
+    /**
+     * @return the attributes of suggestions. This includes whether the spell checker has the word
+     * in its dictionary or not and whether the spell checker has confident suggestions for the
+     * word or not.
+     */
+    public int getSuggestionsAttributes() {
+        return mSuggestionsAttributes;
+    }
+
+    /**
+     * @return the count of suggestions
+     */
+    public int getSuggestionsCount() {
+        return mSuggestions.length;
+    }
+
+    /**
+     * @param i the id of suggestions
+     * @return the suggestion at the specified id
+     */
+    public String getSuggestionAt(int i) {
+        return mSuggestions[i];
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<SuggestionsInfo> CREATOR
+            = new Parcelable.Creator<SuggestionsInfo>() {
+        @Override
+        public SuggestionsInfo createFromParcel(Parcel source) {
+            return new SuggestionsInfo(source);
+        }
+
+        @Override
+        public SuggestionsInfo[] newArray(int size) {
+            return new SuggestionsInfo[size];
+        }
+    };
+
+    /**
+     * Used to make this class parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/view/textservice/TextInfo.aidl b/core/java/android/view/textservice/TextInfo.aidl
new file mode 100644
index 0000000..d231d76
--- /dev/null
+++ b/core/java/android/view/textservice/TextInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.view.textservice;
+
+parcelable TextInfo;
diff --git a/core/java/android/view/textservice/TextInfo.java b/core/java/android/view/textservice/TextInfo.java
new file mode 100644
index 0000000..b534eb0
--- /dev/null
+++ b/core/java/android/view/textservice/TextInfo.java
@@ -0,0 +1,117 @@
+/*
+ * 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 android.view.textservice;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * This class contains a metadata of the input of TextService
+ */
+public final class TextInfo implements Parcelable {
+    private final String mText;
+    private final int mCookie;
+    private final int mSequence;
+
+    /**
+     * Constructor.
+     * @param text the text which will be input to TextService
+     */
+    public TextInfo(String text) {
+        this(text, 0, 0);
+    }
+
+    /**
+     * Constructor.
+     * @param text the text which will be input to TextService
+     * @param cookie the cookie for this TextInfo
+     * @param sequence the sequence number for this TextInfo
+     */
+    public TextInfo(String text, int cookie, int sequence) {
+        if (TextUtils.isEmpty(text)) {
+            throw new IllegalArgumentException(text);
+        }
+        mText = text;
+        mCookie = cookie;
+        mSequence = sequence;
+    }
+
+    public TextInfo(Parcel source) {
+        mText = source.readString();
+        mCookie = source.readInt();
+        mSequence = source.readInt();
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mText);
+        dest.writeInt(mCookie);
+        dest.writeInt(mSequence);
+    }
+
+    /**
+     * @return the text which is an input of a text service
+     */
+    public String getText() {
+        return mText;
+    }
+
+    /**
+     * @return the cookie of TextInfo
+     */
+    public int getCookie() {
+        return mCookie;
+    }
+
+    /**
+     * @return the sequence of TextInfo
+     */
+    public int getSequence() {
+        return mSequence;
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    public static final Parcelable.Creator<TextInfo> CREATOR
+            = new Parcelable.Creator<TextInfo>() {
+        @Override
+        public TextInfo createFromParcel(Parcel source) {
+            return new TextInfo(source);
+        }
+
+        @Override
+        public TextInfo[] newArray(int size) {
+            return new TextInfo[size];
+        }
+    };
+
+    /**
+     * Used to make this class parcelable.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/view/textservice/TextServicesManager.java b/core/java/android/view/textservice/TextServicesManager.java
new file mode 100644
index 0000000..6fa7e4d
--- /dev/null
+++ b/core/java/android/view/textservice/TextServicesManager.java
@@ -0,0 +1,106 @@
+/*
+ * 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 android.view.textservice;
+
+import com.android.internal.textservice.ITextServicesManager;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.view.textservice.SpellCheckerInfo;
+import android.service.textservice.SpellCheckerSession;
+import android.service.textservice.SpellCheckerSession.SpellCheckerSessionListener;
+
+import java.util.Locale;
+
+/**
+ * System API to the overall text services, which arbitrates interaction between applications
+ * and text services. You can retrieve an instance of this interface with
+ * {@link Context#getSystemService(String) Context.getSystemService()}.
+ *
+ * The user can change the current text services in Settings. And also applications can specify
+ * the target text services.
+ */
+public final class TextServicesManager {
+    private static final String TAG = TextServicesManager.class.getSimpleName();
+
+    private static TextServicesManager sInstance;
+    private static ITextServicesManager sService;
+
+    private TextServicesManager() {
+        if (sService == null) {
+            IBinder b = ServiceManager.getService(Context.TEXT_SERVICES_MANAGER_SERVICE);
+            sService = ITextServicesManager.Stub.asInterface(b);
+        }
+    }
+
+    /**
+     * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
+     * @hide
+     */
+    public static TextServicesManager getInstance() {
+        synchronized (TextServicesManager.class) {
+            if (sInstance != null) {
+                return sInstance;
+            }
+            sInstance = new TextServicesManager();
+        }
+        return sInstance;
+    }
+
+
+    /**
+     * Get the current spell checker service info for the specified locale.
+     * @param locale locale of a spell checker
+     * @return SpellCheckerInfo for the specified locale.
+     */
+    // TODO: Add a method to get enabled spell checkers.
+    public SpellCheckerInfo getCurrentSpellChecker(Locale locale) {
+        if (locale == null) {
+            throw new NullPointerException("locale is null");
+        }
+        try {
+            return sService.getCurrentSpellChecker(locale.toString());
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Get a spell checker session for a specified spell checker
+     * @param info SpellCheckerInfo of the spell checker
+     * @param locale the locale for the spell checker
+     * @param listener a spell checker session lister for getting results from a spell checker.
+     * @return the spell checker session of the spell checker
+     */
+    public SpellCheckerSession newSpellCheckerSession(
+            SpellCheckerInfo info, Locale locale, SpellCheckerSessionListener listener) {
+        if (info == null || locale == null || listener == null) {
+            throw new NullPointerException();
+        }
+        final SpellCheckerSession session = new SpellCheckerSession(sService, listener);
+        try {
+            sService.getSpellCheckerService(
+                    info, locale.toString(), session.getTextServicesSessionListener(),
+                    session.getSpellCheckerSessionListener());
+        } catch (RemoteException e) {
+            return null;
+        }
+        return session;
+    }
+}
diff --git a/core/java/com/android/internal/textservice/ISpellCheckerService.aidl b/core/java/com/android/internal/textservice/ISpellCheckerService.aidl
new file mode 100644
index 0000000..ff00492
--- /dev/null
+++ b/core/java/com/android/internal/textservice/ISpellCheckerService.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.internal.textservice;
+
+import com.android.internal.textservice.ISpellCheckerSession;
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+
+/**
+ * Public interface to the global spell checker.
+ * @hide
+ */
+interface ISpellCheckerService {
+    ISpellCheckerSession getISpellCheckerSession(
+            String locale, ISpellCheckerSessionListener listener);
+}
diff --git a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
new file mode 100644
index 0000000..79e43510c0
--- /dev/null
+++ b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.internal.textservice;
+
+import android.view.textservice.TextInfo;
+
+/**
+ * @hide
+ */
+oneway interface ISpellCheckerSession {
+    void getSuggestionsMultiple(
+            in TextInfo[] textInfos, int suggestionsLimit, boolean multipleWords);
+    void cancel();
+}
diff --git a/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl
new file mode 100644
index 0000000..796b06e
--- /dev/null
+++ b/core/java/com/android/internal/textservice/ISpellCheckerSessionListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.internal.textservice;
+
+import android.view.textservice.SuggestionsInfo;
+
+/**
+ * @hide
+ */
+oneway interface ISpellCheckerSessionListener {
+    void onGetSuggestions(in SuggestionsInfo[] results);
+}
diff --git a/core/java/com/android/internal/textservice/ITextServicesManager.aidl b/core/java/com/android/internal/textservice/ITextServicesManager.aidl
new file mode 100644
index 0000000..ad0c1ff
--- /dev/null
+++ b/core/java/com/android/internal/textservice/ITextServicesManager.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.internal.textservice;
+
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesSessionListener;
+
+import android.content.ComponentName;
+import android.view.textservice.SpellCheckerInfo;
+
+/**
+ * Interface to the text service manager.
+ * @hide
+ */
+interface ITextServicesManager {
+    SpellCheckerInfo getCurrentSpellChecker(String locale);
+    oneway void getSpellCheckerService(in SpellCheckerInfo info, in String locale,
+            in ITextServicesSessionListener tsListener,
+            in ISpellCheckerSessionListener scListener);
+    oneway void finishSpellCheckerService(in ISpellCheckerSessionListener listener);
+}
diff --git a/core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl b/core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl
new file mode 100644
index 0000000..ecb6cd0
--- /dev/null
+++ b/core/java/com/android/internal/textservice/ITextServicesSessionListener.aidl
@@ -0,0 +1,29 @@
+/*
+ * 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.internal.textservice;
+
+import com.android.internal.textservice.ISpellCheckerSession;
+
+import android.view.textservice.SpellCheckerInfo;
+
+/**
+ * Interface to the text service session.
+ * @hide
+ */
+interface ITextServicesSessionListener {
+    oneway void onServiceConnected(in ISpellCheckerSession spellCheckerSession);
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index c67b625..91003d1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1120,6 +1120,13 @@
         android:description="@string/permdesc_bindInputMethod"
         android:protectionLevel="signature" />
 
+    <!-- Must be required by a TextService (e.g. SpellCheckerService)
+         to ensure that only the system can bind to it. -->
+    <permission android:name="android.permission.BIND_TEXT_SERVICE"
+        android:label="@string/permlab_bindTextService"
+        android:description="@string/permdesc_bindTextService"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by a {@link android.service.wallpaper.WallpaperService},
          to ensure that only the system can bind to it. -->
     <permission android:name="android.permission.BIND_WALLPAPER"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 509ee69..6162e51 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -705,6 +705,12 @@
         interface of an input method. Should never be needed for normal applications.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_bindTextService">bind to a text service</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_bindTextService">Allows the holder to bind to the top-level
+        interface of a text service(e.g. SpellCheckerService). Should never be needed for normal applications.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_bindWallpaper">bind to a wallpaper</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_bindWallpaper">Allows the holder to bind to the top-level