1988323c57bd25a58f05dfa492d9b9c8ab62c5153satok/*
2988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * Copyright (C) 2011 The Android Open Source Project
3988323c57bd25a58f05dfa492d9b9c8ab62c5153satok *
4988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * use this file except in compliance with the License. You may obtain a copy of
6988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * the License at
7988323c57bd25a58f05dfa492d9b9c8ab62c5153satok *
8988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * http://www.apache.org/licenses/LICENSE-2.0
9988323c57bd25a58f05dfa492d9b9c8ab62c5153satok *
10988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * Unless required by applicable law or agreed to in writing, software
11988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * License for the specific language governing permissions and limitations under
14988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * the License.
15988323c57bd25a58f05dfa492d9b9c8ab62c5153satok */
16988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
17988323c57bd25a58f05dfa492d9b9c8ab62c5153satokpackage android.service.textservice;
18988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
19988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ISpellCheckerService;
20988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ISpellCheckerSession;
21988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ISpellCheckerSessionListener;
22988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
23988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.app.Service;
24988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.content.Intent;
255357806980269d846a15c845a6fcc0384fb18860satokimport android.os.Bundle;
26988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.os.IBinder;
2733b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackbornimport android.os.Process;
28988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.os.RemoteException;
29c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satokimport android.text.TextUtils;
30c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satokimport android.text.method.WordIterator;
316be6d7548fb7c29a4d46dc985318ab2adf69f95fsatokimport android.util.Log;
32d404fe110558bd2e1960b428db6a2ee8bfd040cdsatokimport android.view.textservice.SentenceSuggestionsInfo;
33988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.view.textservice.SuggestionsInfo;
34988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.view.textservice.TextInfo;
35988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
36988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport java.lang.ref.WeakReference;
37c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satokimport java.text.BreakIterator;
38c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satokimport java.util.ArrayList;
39c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satokimport java.util.Locale;
40988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
41988323c57bd25a58f05dfa492d9b9c8ab62c5153satok/**
42988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * SpellCheckerService provides an abstract base class for a spell checker.
43988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * This class combines a service to the system with the spell checker service interface that
44988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * spell checker must implement.
4544b75030931d9c65c9e495a86d11d71da59b4429satok *
4644b75030931d9c65c9e495a86d11d71da59b4429satok * <p>In addition to the normal Service lifecycle methods, this class
4744b75030931d9c65c9e495a86d11d71da59b4429satok * introduces a new specific callback that subclasses should override
4844b75030931d9c65c9e495a86d11d71da59b4429satok * {@link #createSession()} to provide a spell checker session that is corresponding
4944b75030931d9c65c9e495a86d11d71da59b4429satok * to requested language and so on. The spell checker session returned by this method
5044b75030931d9c65c9e495a86d11d71da59b4429satok * should extend {@link SpellCheckerService.Session}.
5144b75030931d9c65c9e495a86d11d71da59b4429satok * </p>
5244b75030931d9c65c9e495a86d11d71da59b4429satok *
5344b75030931d9c65c9e495a86d11d71da59b4429satok * <h3>Returning spell check results</h3>
5444b75030931d9c65c9e495a86d11d71da59b4429satok *
5544b75030931d9c65c9e495a86d11d71da59b4429satok * <p>{@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
5644b75030931d9c65c9e495a86d11d71da59b4429satok * should return spell check results.
5744b75030931d9c65c9e495a86d11d71da59b4429satok * It receives {@link android.view.textservice.TextInfo} and returns
5844b75030931d9c65c9e495a86d11d71da59b4429satok * {@link android.view.textservice.SuggestionsInfo} for the input.
5944b75030931d9c65c9e495a86d11d71da59b4429satok * You may want to override
6044b75030931d9c65c9e495a86d11d71da59b4429satok * {@link SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)} for
6144b75030931d9c65c9e495a86d11d71da59b4429satok * better performance and quality.
6244b75030931d9c65c9e495a86d11d71da59b4429satok * </p>
6344b75030931d9c65c9e495a86d11d71da59b4429satok *
6444b75030931d9c65c9e495a86d11d71da59b4429satok * <p>Please note that {@link SpellCheckerService.Session#getLocale()} does not return a valid
6544b75030931d9c65c9e495a86d11d71da59b4429satok * locale before {@link SpellCheckerService.Session#onCreate()} </p>
6644b75030931d9c65c9e495a86d11d71da59b4429satok *
67988323c57bd25a58f05dfa492d9b9c8ab62c5153satok */
68988323c57bd25a58f05dfa492d9b9c8ab62c5153satokpublic abstract class SpellCheckerService extends Service {
69988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static final String TAG = SpellCheckerService.class.getSimpleName();
706be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok    private static final boolean DBG = false;
71142d7575b52d03d46246e3b142e22ebc32d45a84satok    public static final String SERVICE_INTERFACE =
72142d7575b52d03d46246e3b142e22ebc32d45a84satok            "android.service.textservice.SpellCheckerService";
73988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
74988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final SpellCheckerServiceBinder mBinder = new SpellCheckerServiceBinder(this);
75988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
76988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
77988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
785357806980269d846a15c845a6fcc0384fb18860satok     * Implement to return the implementation of the internal spell checker
795357806980269d846a15c845a6fcc0384fb18860satok     * service interface. Subclasses should not override.
80988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
815357806980269d846a15c845a6fcc0384fb18860satok    @Override
825357806980269d846a15c845a6fcc0384fb18860satok    public final IBinder onBind(final Intent intent) {
835357806980269d846a15c845a6fcc0384fb18860satok        if (DBG) {
845357806980269d846a15c845a6fcc0384fb18860satok            Log.w(TAG, "onBind");
85988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
865357806980269d846a15c845a6fcc0384fb18860satok        return mBinder;
87988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
88988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
89988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
905357806980269d846a15c845a6fcc0384fb18860satok     * Factory method to create a spell checker session impl
915357806980269d846a15c845a6fcc0384fb18860satok     * @return SpellCheckerSessionImpl which should be overridden by a concrete implementation.
92988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
935357806980269d846a15c845a6fcc0384fb18860satok    public abstract Session createSession();
94988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
95988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    /**
965357806980269d846a15c845a6fcc0384fb18860satok     * This abstract class should be overridden by a concrete implementation of a spell checker.
97988323c57bd25a58f05dfa492d9b9c8ab62c5153satok     */
98117999d1f44ec3423369385495ae207898b7b73esatok    public static abstract class Session {
995357806980269d846a15c845a6fcc0384fb18860satok        private InternalISpellCheckerSession mInternalSession;
100c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        private volatile SentenceLevelAdapter mSentenceLevelAdapter;
1015357806980269d846a15c845a6fcc0384fb18860satok
1025357806980269d846a15c845a6fcc0384fb18860satok        /**
1035357806980269d846a15c845a6fcc0384fb18860satok         * @hide
1045357806980269d846a15c845a6fcc0384fb18860satok         */
1055357806980269d846a15c845a6fcc0384fb18860satok        public final void setInternalISpellCheckerSession(InternalISpellCheckerSession session) {
1065357806980269d846a15c845a6fcc0384fb18860satok            mInternalSession = session;
1075357806980269d846a15c845a6fcc0384fb18860satok        }
1085357806980269d846a15c845a6fcc0384fb18860satok
1095357806980269d846a15c845a6fcc0384fb18860satok        /**
1105357806980269d846a15c845a6fcc0384fb18860satok         * This is called after the class is initialized, at which point it knows it can call
1115357806980269d846a15c845a6fcc0384fb18860satok         * getLocale() etc...
1125357806980269d846a15c845a6fcc0384fb18860satok         */
1135357806980269d846a15c845a6fcc0384fb18860satok        public abstract void onCreate();
1145357806980269d846a15c845a6fcc0384fb18860satok
1155357806980269d846a15c845a6fcc0384fb18860satok        /**
1165357806980269d846a15c845a6fcc0384fb18860satok         * Get suggestions for specified text in TextInfo.
1175357806980269d846a15c845a6fcc0384fb18860satok         * This function will run on the incoming IPC thread.
1185357806980269d846a15c845a6fcc0384fb18860satok         * So, this is not called on the main thread,
1195357806980269d846a15c845a6fcc0384fb18860satok         * but will be called in series on another thread.
1205357806980269d846a15c845a6fcc0384fb18860satok         * @param textInfo the text metadata
1216183cd64a98a69ea247813c9ba0a07326c4bc1aesatok         * @param suggestionsLimit the maximum number of suggestions to be returned
12244b75030931d9c65c9e495a86d11d71da59b4429satok         * @return SuggestionsInfo which contains suggestions for textInfo
1235357806980269d846a15c845a6fcc0384fb18860satok         */
1245357806980269d846a15c845a6fcc0384fb18860satok        public abstract SuggestionsInfo onGetSuggestions(TextInfo textInfo, int suggestionsLimit);
1255357806980269d846a15c845a6fcc0384fb18860satok
1265357806980269d846a15c845a6fcc0384fb18860satok        /**
1275357806980269d846a15c845a6fcc0384fb18860satok         * A batch process of onGetSuggestions.
1285357806980269d846a15c845a6fcc0384fb18860satok         * This function will run on the incoming IPC thread.
1295357806980269d846a15c845a6fcc0384fb18860satok         * So, this is not called on the main thread,
1305357806980269d846a15c845a6fcc0384fb18860satok         * but will be called in series on another thread.
1315357806980269d846a15c845a6fcc0384fb18860satok         * @param textInfos an array of the text metadata
1326183cd64a98a69ea247813c9ba0a07326c4bc1aesatok         * @param suggestionsLimit the maximum number of suggestions to be returned
1335357806980269d846a15c845a6fcc0384fb18860satok         * @param sequentialWords true if textInfos can be treated as sequential words.
1346183cd64a98a69ea247813c9ba0a07326c4bc1aesatok         * @return an array of {@link SentenceSuggestionsInfo} returned by
1356183cd64a98a69ea247813c9ba0a07326c4bc1aesatok         * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
1365357806980269d846a15c845a6fcc0384fb18860satok         */
1375357806980269d846a15c845a6fcc0384fb18860satok        public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos,
1385357806980269d846a15c845a6fcc0384fb18860satok                int suggestionsLimit, boolean sequentialWords) {
1395357806980269d846a15c845a6fcc0384fb18860satok            final int length = textInfos.length;
1405357806980269d846a15c845a6fcc0384fb18860satok            final SuggestionsInfo[] retval = new SuggestionsInfo[length];
1415357806980269d846a15c845a6fcc0384fb18860satok            for (int i = 0; i < length; ++i) {
1425357806980269d846a15c845a6fcc0384fb18860satok                retval[i] = onGetSuggestions(textInfos[i], suggestionsLimit);
1435357806980269d846a15c845a6fcc0384fb18860satok                retval[i].setCookieAndSequence(
1445357806980269d846a15c845a6fcc0384fb18860satok                        textInfos[i].getCookie(), textInfos[i].getSequence());
1455357806980269d846a15c845a6fcc0384fb18860satok            }
1465357806980269d846a15c845a6fcc0384fb18860satok            return retval;
1475357806980269d846a15c845a6fcc0384fb18860satok        }
1485357806980269d846a15c845a6fcc0384fb18860satok
1495357806980269d846a15c845a6fcc0384fb18860satok        /**
150431ea84e1fde20139b748a4818c44e85a715e155satok         * Get sentence suggestions for specified texts in an array of TextInfo.
151c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok         * The default implementation splits the input text to words and returns
152c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok         * {@link SentenceSuggestionsInfo} which contains suggestions for each word.
153431ea84e1fde20139b748a4818c44e85a715e155satok         * This function will run on the incoming IPC thread.
154431ea84e1fde20139b748a4818c44e85a715e155satok         * So, this is not called on the main thread,
155431ea84e1fde20139b748a4818c44e85a715e155satok         * but will be called in series on another thread.
1560dc1f648a09b46c45190ba1ce7daecf7fada4347satok         * When you override this method, make sure that suggestionsLimit is applied to suggestions
1570dc1f648a09b46c45190ba1ce7daecf7fada4347satok         * that share the same start position and length.
1586183cd64a98a69ea247813c9ba0a07326c4bc1aesatok         * @param textInfos an array of the text metadata
1596183cd64a98a69ea247813c9ba0a07326c4bc1aesatok         * @param suggestionsLimit the maximum number of suggestions to be returned
1606183cd64a98a69ea247813c9ba0a07326c4bc1aesatok         * @return an array of {@link SentenceSuggestionsInfo} returned by
1616183cd64a98a69ea247813c9ba0a07326c4bc1aesatok         * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)}
1620dc1f648a09b46c45190ba1ce7daecf7fada4347satok         */
163d404fe110558bd2e1960b428db6a2ee8bfd040cdsatok        public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos,
1640dc1f648a09b46c45190ba1ce7daecf7fada4347satok                int suggestionsLimit) {
165c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            if (textInfos == null || textInfos.length == 0) {
166c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
167c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
168c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            if (DBG) {
169c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                Log.d(TAG, "onGetSentenceSuggestionsMultiple: + " + textInfos.length + ", "
170c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        + suggestionsLimit);
171c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
172c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            if (mSentenceLevelAdapter == null) {
173c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                synchronized(this) {
174c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    if (mSentenceLevelAdapter == null) {
175c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        final String localeStr = getLocale();
176c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        if (!TextUtils.isEmpty(localeStr)) {
177c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                            mSentenceLevelAdapter = new SentenceLevelAdapter(new Locale(localeStr));
178c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        }
179c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    }
180c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                }
181c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
182c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            if (mSentenceLevelAdapter == null) {
183c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS;
184c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
185c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int infosSize = textInfos.length;
186c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[infosSize];
187c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            for (int i = 0; i < infosSize; ++i) {
188c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                final SentenceLevelAdapter.SentenceTextInfoParams textInfoParams =
189c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        mSentenceLevelAdapter.getSplitWords(textInfos[i]);
190c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                final ArrayList<SentenceLevelAdapter.SentenceWordItem> mItems =
191c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        textInfoParams.mItems;
192c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                final int itemsSize = mItems.size();
193c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                final TextInfo[] splitTextInfos = new TextInfo[itemsSize];
194c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                for (int j = 0; j < itemsSize; ++j) {
195c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    splitTextInfos[j] = mItems.get(j).mTextInfo;
196c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                }
197c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                retval[i] = SentenceLevelAdapter.reconstructSuggestions(
198c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        textInfoParams, onGetSuggestionsMultiple(
199c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                                splitTextInfos, suggestionsLimit, true));
2000dc1f648a09b46c45190ba1ce7daecf7fada4347satok            }
2010dc1f648a09b46c45190ba1ce7daecf7fada4347satok            return retval;
2020dc1f648a09b46c45190ba1ce7daecf7fada4347satok        }
2030dc1f648a09b46c45190ba1ce7daecf7fada4347satok
2040dc1f648a09b46c45190ba1ce7daecf7fada4347satok        /**
2055357806980269d846a15c845a6fcc0384fb18860satok         * Request to abort all tasks executed in SpellChecker.
2065357806980269d846a15c845a6fcc0384fb18860satok         * This function will run on the incoming IPC thread.
2075357806980269d846a15c845a6fcc0384fb18860satok         * So, this is not called on the main thread,
2085357806980269d846a15c845a6fcc0384fb18860satok         * but will be called in series on another thread.
2095357806980269d846a15c845a6fcc0384fb18860satok         */
2105357806980269d846a15c845a6fcc0384fb18860satok        public void onCancel() {}
2115357806980269d846a15c845a6fcc0384fb18860satok
2125357806980269d846a15c845a6fcc0384fb18860satok        /**
21374061ff90453c79ddbde468f630a41425da07710satok         * Request to close this session.
21474061ff90453c79ddbde468f630a41425da07710satok         * This function will run on the incoming IPC thread.
21574061ff90453c79ddbde468f630a41425da07710satok         * So, this is not called on the main thread,
21674061ff90453c79ddbde468f630a41425da07710satok         * but will be called in series on another thread.
21774061ff90453c79ddbde468f630a41425da07710satok         */
21874061ff90453c79ddbde468f630a41425da07710satok        public void onClose() {}
21974061ff90453c79ddbde468f630a41425da07710satok
22074061ff90453c79ddbde468f630a41425da07710satok        /**
2215357806980269d846a15c845a6fcc0384fb18860satok         * @return Locale for this session
2225357806980269d846a15c845a6fcc0384fb18860satok         */
2235357806980269d846a15c845a6fcc0384fb18860satok        public String getLocale() {
2245357806980269d846a15c845a6fcc0384fb18860satok            return mInternalSession.getLocale();
2255357806980269d846a15c845a6fcc0384fb18860satok        }
2265357806980269d846a15c845a6fcc0384fb18860satok
2275357806980269d846a15c845a6fcc0384fb18860satok        /**
2285357806980269d846a15c845a6fcc0384fb18860satok         * @return Bundle for this session
2295357806980269d846a15c845a6fcc0384fb18860satok         */
2305357806980269d846a15c845a6fcc0384fb18860satok        public Bundle getBundle() {
2315357806980269d846a15c845a6fcc0384fb18860satok            return mInternalSession.getBundle();
2326be6d7548fb7c29a4d46dc985318ab2adf69f95fsatok        }
233988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
234988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
2355357806980269d846a15c845a6fcc0384fb18860satok    // Preventing from exposing ISpellCheckerSession.aidl, create an internal class.
2365357806980269d846a15c845a6fcc0384fb18860satok    private static class InternalISpellCheckerSession extends ISpellCheckerSession.Stub {
23774061ff90453c79ddbde468f630a41425da07710satok        private ISpellCheckerSessionListener mListener;
2385357806980269d846a15c845a6fcc0384fb18860satok        private final Session mSession;
2395357806980269d846a15c845a6fcc0384fb18860satok        private final String mLocale;
2405357806980269d846a15c845a6fcc0384fb18860satok        private final Bundle mBundle;
241988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
2425357806980269d846a15c845a6fcc0384fb18860satok        public InternalISpellCheckerSession(String locale, ISpellCheckerSessionListener listener,
2435357806980269d846a15c845a6fcc0384fb18860satok                Bundle bundle, Session session) {
244988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mListener = listener;
2455357806980269d846a15c845a6fcc0384fb18860satok            mSession = session;
2465357806980269d846a15c845a6fcc0384fb18860satok            mLocale = locale;
2475357806980269d846a15c845a6fcc0384fb18860satok            mBundle = bundle;
2485357806980269d846a15c845a6fcc0384fb18860satok            session.setInternalISpellCheckerSession(this);
249988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
250988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
251988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
2525357806980269d846a15c845a6fcc0384fb18860satok        public void onGetSuggestionsMultiple(
253988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
25433b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            int pri = Process.getThreadPriority(Process.myTid());
255988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            try {
25633b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
257988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mListener.onGetSuggestions(
2585357806980269d846a15c845a6fcc0384fb18860satok                        mSession.onGetSuggestionsMultiple(
2595357806980269d846a15c845a6fcc0384fb18860satok                                textInfos, suggestionsLimit, sequentialWords));
260988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            } catch (RemoteException e) {
26133b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            } finally {
26233b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn                Process.setThreadPriority(pri);
263988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
264988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
265988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
266988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
267d404fe110558bd2e1960b428db6a2ee8bfd040cdsatok        public void onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
2680dc1f648a09b46c45190ba1ce7daecf7fada4347satok            try {
269d404fe110558bd2e1960b428db6a2ee8bfd040cdsatok                mListener.onGetSentenceSuggestions(
270d404fe110558bd2e1960b428db6a2ee8bfd040cdsatok                        mSession.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit));
2710dc1f648a09b46c45190ba1ce7daecf7fada4347satok            } catch (RemoteException e) {
2720dc1f648a09b46c45190ba1ce7daecf7fada4347satok            }
2730dc1f648a09b46c45190ba1ce7daecf7fada4347satok        }
2740dc1f648a09b46c45190ba1ce7daecf7fada4347satok
2750dc1f648a09b46c45190ba1ce7daecf7fada4347satok        @Override
2765357806980269d846a15c845a6fcc0384fb18860satok        public void onCancel() {
27733b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            int pri = Process.getThreadPriority(Process.myTid());
27833b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            try {
27933b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
28033b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn                mSession.onCancel();
28133b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            } finally {
28233b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn                Process.setThreadPriority(pri);
28333b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            }
2845357806980269d846a15c845a6fcc0384fb18860satok        }
2855357806980269d846a15c845a6fcc0384fb18860satok
28674061ff90453c79ddbde468f630a41425da07710satok        @Override
28774061ff90453c79ddbde468f630a41425da07710satok        public void onClose() {
28833b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            int pri = Process.getThreadPriority(Process.myTid());
28933b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            try {
29033b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
29133b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn                mSession.onClose();
29233b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            } finally {
29333b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn                Process.setThreadPriority(pri);
29433b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn                mListener = null;
29533b8ee509f36a0168c8ce5a9091b57ab936f4c13Dianne Hackborn            }
29674061ff90453c79ddbde468f630a41425da07710satok        }
29774061ff90453c79ddbde468f630a41425da07710satok
2985357806980269d846a15c845a6fcc0384fb18860satok        public String getLocale() {
2995357806980269d846a15c845a6fcc0384fb18860satok            return mLocale;
3005357806980269d846a15c845a6fcc0384fb18860satok        }
3015357806980269d846a15c845a6fcc0384fb18860satok
3025357806980269d846a15c845a6fcc0384fb18860satok        public Bundle getBundle() {
3035357806980269d846a15c845a6fcc0384fb18860satok            return mBundle;
304988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
305988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
306988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
307988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static class SpellCheckerServiceBinder extends ISpellCheckerService.Stub {
308988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final WeakReference<SpellCheckerService> mInternalServiceRef;
309988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
310988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public SpellCheckerServiceBinder(SpellCheckerService service) {
311988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mInternalServiceRef = new WeakReference<SpellCheckerService>(service);
312988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
313988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
314988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
315988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public ISpellCheckerSession getISpellCheckerSession(
3165357806980269d846a15c845a6fcc0384fb18860satok                String locale, ISpellCheckerSessionListener listener, Bundle bundle) {
317988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final SpellCheckerService service = mInternalServiceRef.get();
318988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (service == null) return null;
3195357806980269d846a15c845a6fcc0384fb18860satok            final Session session = service.createSession();
3205357806980269d846a15c845a6fcc0384fb18860satok            final InternalISpellCheckerSession internalSession =
3215357806980269d846a15c845a6fcc0384fb18860satok                    new InternalISpellCheckerSession(locale, listener, bundle, session);
3225357806980269d846a15c845a6fcc0384fb18860satok            session.onCreate();
3235357806980269d846a15c845a6fcc0384fb18860satok            return internalSession;
324988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
325988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
326c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok
327c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok    /**
328c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok     * Adapter class to accommodate word level spell checking APIs to sentence level spell checking
329c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok     * APIs used in
330c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok     * {@link SpellCheckerService.Session#onGetSuggestionsMultiple(TextInfo[], int, boolean)}
331c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok     */
332c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok    private static class SentenceLevelAdapter {
333c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS =
334c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                new SentenceSuggestionsInfo[] {};
335c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null);
336c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        /**
337c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok         * Container for split TextInfo parameters
338c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok         */
339c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        public static class SentenceWordItem {
340c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            public final TextInfo mTextInfo;
341c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            public final int mStart;
342c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            public final int mLength;
343c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            public SentenceWordItem(TextInfo ti, int start, int end) {
344c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                mTextInfo = ti;
345c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                mStart = start;
346c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                mLength = end - start;
347c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
348c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        }
349c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok
350c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        /**
351c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok         * Container for originally queried TextInfo and parameters
352c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok         */
353c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        public static class SentenceTextInfoParams {
354c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final TextInfo mOriginalTextInfo;
355c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final ArrayList<SentenceWordItem> mItems;
356c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int mSize;
357c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) {
358c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                mOriginalTextInfo = ti;
359c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                mItems = items;
360c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                mSize = items.size();
361c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
362c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        }
363c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok
364c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        private final WordIterator mWordIterator;
365c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        public SentenceLevelAdapter(Locale locale) {
366c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            mWordIterator = new WordIterator(locale);
367c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        }
368c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok
369c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        private SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) {
370c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final WordIterator wordIterator = mWordIterator;
371c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final CharSequence originalText = originalTextInfo.getText();
372c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int cookie = originalTextInfo.getCookie();
373c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int start = 0;
374c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int end = originalText.length();
375c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final ArrayList<SentenceWordItem> wordItems = new ArrayList<SentenceWordItem>();
376c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            wordIterator.setCharSequence(originalText, 0, originalText.length());
377c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            int wordEnd = wordIterator.following(start);
378c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            int wordStart = wordIterator.getBeginning(wordEnd);
379c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            if (DBG) {
380c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                Log.d(TAG, "iterator: break: ---- 1st word start = " + wordStart + ", end = "
381c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        + wordEnd + "\n" + originalText);
382c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
383c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            while (wordStart <= end && wordEnd != BreakIterator.DONE
384c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    && wordStart != BreakIterator.DONE) {
385c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                if (wordEnd >= start && wordEnd > wordStart) {
386fccd37fb7ef6995c29ced0d2956e91dd84d7586fYohei Yukawa                    final CharSequence query = originalText.subSequence(wordStart, wordEnd);
387fccd37fb7ef6995c29ced0d2956e91dd84d7586fYohei Yukawa                    final TextInfo ti = new TextInfo(query, 0, query.length(), cookie,
388fccd37fb7ef6995c29ced0d2956e91dd84d7586fYohei Yukawa                            query.hashCode());
389c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd));
390c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    if (DBG) {
391c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        Log.d(TAG, "Adapter: word (" + (wordItems.size() - 1) + ") " + query);
392c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    }
393c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                }
394c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                wordEnd = wordIterator.following(wordEnd);
395c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                if (wordEnd == BreakIterator.DONE) {
396c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    break;
397c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                }
398c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                wordStart = wordIterator.getBeginning(wordEnd);
399c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
400c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            return new SentenceTextInfoParams(originalTextInfo, wordItems);
401c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        }
402c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok
403c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        public static SentenceSuggestionsInfo reconstructSuggestions(
404c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) {
405c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            if (results == null || results.length == 0) {
406c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                return null;
407c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
408c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            if (DBG) {
409c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                Log.w(TAG, "Adapter: onGetSuggestions: got " + results.length);
410c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
411c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            if (originalTextInfoParams == null) {
412c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                if (DBG) {
413c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    Log.w(TAG, "Adapter: originalTextInfoParams is null.");
414c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                }
415c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                return null;
416c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
417c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie();
418c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int originalSequence =
419c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    originalTextInfoParams.mOriginalTextInfo.getSequence();
420c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok
421c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int querySize = originalTextInfoParams.mSize;
422c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int[] offsets = new int[querySize];
423c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final int[] lengths = new int[querySize];
424c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize];
425c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            for (int i = 0; i < querySize; ++i) {
426c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                final SentenceWordItem item = originalTextInfoParams.mItems.get(i);
427c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                SuggestionsInfo result = null;
428c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                for (int j = 0; j < results.length; ++j) {
429c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    final SuggestionsInfo cur = results[j];
430c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) {
431c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        result = cur;
432c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        result.setCookieAndSequence(originalCookie, originalSequence);
433c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                        break;
434c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    }
435c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                }
436c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                offsets[i] = item.mStart;
437c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                lengths[i] = item.mLength;
438c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO;
439c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                if (DBG) {
440c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    final int size = reconstructedSuggestions[i].getSuggestionsCount();
441c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                    Log.w(TAG, "reconstructedSuggestions(" + i + ")" + size + ", first = "
442c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                            + (size > 0 ? reconstructedSuggestions[i].getSuggestionAt(0)
443c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                                    : "<none>") + ", offset = " + offsets[i] + ", length = "
444c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                            + lengths[i]);
445c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok                }
446c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            }
447c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok            return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths);
448c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok        }
449c7ee1b9369ffd7c21a70738056a82dc4238e7fc1satok    }
450988323c57bd25a58f05dfa492d9b9c8ab62c5153satok}
451