TextServicesManagerService.java revision da317ef68603dc7649f98bda495267973825e7fa
1988323c57bd25a58f05dfa492d9b9c8ab62c5153satok/*
2988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * Copyright (C) 2011 The Android Open Source Project
3988323c57bd25a58f05dfa492d9b9c8ab62c5153satok *
4988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * Licensed under the Apache License, Version 2.0 (the "License");
5988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * you may not use this file except in compliance with the License.
6988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * You may obtain a copy of 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,
12988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * See the License for the specific language governing permissions and
14988323c57bd25a58f05dfa492d9b9c8ab62c5153satok * limitations under the License.
15988323c57bd25a58f05dfa492d9b9c8ab62c5153satok */
16988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
17988323c57bd25a58f05dfa492d9b9c8ab62c5153satokpackage com.android.server;
18988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
19988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.content.PackageMonitor;
20988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ISpellCheckerService;
21988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ISpellCheckerSession;
22988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ISpellCheckerSessionListener;
23988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ITextServicesManager;
24988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport com.android.internal.textservice.ITextServicesSessionListener;
25988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
26988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.content.ComponentName;
27988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.content.Context;
28988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.content.Intent;
29988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.content.ServiceConnection;
30988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.content.pm.PackageManager;
31988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.content.pm.ResolveInfo;
32988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.content.pm.ServiceInfo;
33988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.os.IBinder;
34988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.os.RemoteException;
35988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.os.SystemClock;
36988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.provider.Settings;
37988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.text.TextUtils;
38988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.service.textservice.SpellCheckerService;
39988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.util.Log;
40988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.util.Slog;
41988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport android.view.textservice.SpellCheckerInfo;
42988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
43988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport java.util.ArrayList;
44988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport java.util.HashMap;
45988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport java.util.HashSet;
46988323c57bd25a58f05dfa492d9b9c8ab62c5153satokimport java.util.List;
47988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
48988323c57bd25a58f05dfa492d9b9c8ab62c5153satokpublic class TextServicesManagerService extends ITextServicesManager.Stub {
49988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static final String TAG = TextServicesManagerService.class.getSimpleName();
50988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static final boolean DBG = false;
51988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
52988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final Context mContext;
53988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private boolean mSystemReady;
54988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final TextServicesMonitor mMonitor;
55988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final HashMap<String, SpellCheckerInfo> mSpellCheckerMap =
56988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            new HashMap<String, SpellCheckerInfo>();
57988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final ArrayList<SpellCheckerInfo> mSpellCheckerList = new ArrayList<SpellCheckerInfo>();
58988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private final HashMap<String, SpellCheckerBindGroup> mSpellCheckerBindGroups =
59988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            new HashMap<String, SpellCheckerBindGroup>();
60988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
61988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void systemReady() {
62988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (!mSystemReady) {
63988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mSystemReady = true;
64988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
65988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
66988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
67988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public TextServicesManagerService(Context context) {
68988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mSystemReady = false;
69988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mContext = context;
70988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mMonitor = new TextServicesMonitor();
71988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        mMonitor.register(context, true);
72988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        synchronized (mSpellCheckerMap) {
73988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            buildSpellCheckerMapLocked(context, mSpellCheckerList, mSpellCheckerMap);
74988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
75988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
76988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
77988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private class TextServicesMonitor extends PackageMonitor {
78988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
79988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onSomePackagesChanged() {
80988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            synchronized (mSpellCheckerMap) {
81988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                buildSpellCheckerMapLocked(mContext, mSpellCheckerList, mSpellCheckerMap);
82988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                // TODO: Update for each locale
83988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                SpellCheckerInfo sci = getCurrentSpellChecker(null);
84da317ef68603dc7649f98bda495267973825e7fasatok                if (sci == null) return;
85988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final String packageName = sci.getPackageName();
86988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final int change = isPackageDisappearing(packageName);
87988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) {
88988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    // Package disappearing
89988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    setCurrentSpellChecker(findAvailSpellCheckerLocked(null, packageName));
90988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                } else if (isPackageModified(packageName)) {
91988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    // Package modified
92988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    setCurrentSpellChecker(findAvailSpellCheckerLocked(null, packageName));
93988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
94988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
95988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
96988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
97988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
98988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static void buildSpellCheckerMapLocked(Context context,
99988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map) {
100988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        list.clear();
101988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        map.clear();
102988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final PackageManager pm = context.getPackageManager();
103988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        List<ResolveInfo> services = pm.queryIntentServices(
104988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
105988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final int N = services.size();
106988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        for (int i = 0; i < N; ++i) {
107988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final ResolveInfo ri = services.get(i);
108988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final ServiceInfo si = ri.serviceInfo;
109988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final ComponentName compName = new ComponentName(si.packageName, si.name);
110988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
111988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Slog.w(TAG, "Skipping text service " + compName
112988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        + ": it does not require the permission "
113988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        + android.Manifest.permission.BIND_TEXT_SERVICE);
114988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                continue;
115988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
116988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (DBG) Slog.d(TAG, "Add: " + compName);
117988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri);
118988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            list.add(sci);
119988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            map.put(sci.getId(), sci);
120988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
121da317ef68603dc7649f98bda495267973825e7fasatok        if (DBG) {
122da317ef68603dc7649f98bda495267973825e7fasatok            Slog.d(TAG, "buildSpellCheckerMapLocked: " + list.size() + "," + map.size());
123da317ef68603dc7649f98bda495267973825e7fasatok        }
124988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
125988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
126988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // TODO: find an appropriate spell checker for specified locale
127988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private SpellCheckerInfo findAvailSpellCheckerLocked(String locale, String prefPackage) {
128988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final int spellCheckersCount = mSpellCheckerList.size();
129988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (spellCheckersCount == 0) {
130988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Slog.w(TAG, "no available spell checker services found");
131988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return null;
132988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
133988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (prefPackage != null) {
134988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            for (int i = 0; i < spellCheckersCount; ++i) {
135988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final SpellCheckerInfo sci = mSpellCheckerList.get(i);
136988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                if (prefPackage.equals(sci.getPackageName())) {
137da317ef68603dc7649f98bda495267973825e7fasatok                    if (DBG) {
138da317ef68603dc7649f98bda495267973825e7fasatok                        Slog.d(TAG, "findAvailSpellCheckerLocked: " + sci.getPackageName());
139da317ef68603dc7649f98bda495267973825e7fasatok                    }
140988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    return sci;
141988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
142988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
143988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
144988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (spellCheckersCount > 1) {
145988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Slog.w(TAG, "more than one spell checker service found, picking first");
146988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
147988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return mSpellCheckerList.get(0);
148988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
149988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
150988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // TODO: Save SpellCheckerService by supported languages. Currently only one spell
151988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // checker is saved.
152988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    @Override
153988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public SpellCheckerInfo getCurrentSpellChecker(String locale) {
154988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        synchronized (mSpellCheckerMap) {
155da317ef68603dc7649f98bda495267973825e7fasatok            String curSpellCheckerId =
156988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    Settings.Secure.getString(mContext.getContentResolver(),
157988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            Settings.Secure.SPELL_CHECKER_SERVICE);
158562ab585f9e413d9696ee250e5ec02f95889a157satok            if (DBG) {
159562ab585f9e413d9696ee250e5ec02f95889a157satok                Slog.w(TAG, "getCurrentSpellChecker: " + curSpellCheckerId);
160562ab585f9e413d9696ee250e5ec02f95889a157satok            }
161988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (TextUtils.isEmpty(curSpellCheckerId)) {
162da317ef68603dc7649f98bda495267973825e7fasatok                final SpellCheckerInfo sci = findAvailSpellCheckerLocked(null, null);
163da317ef68603dc7649f98bda495267973825e7fasatok                if (sci == null) return null;
164da317ef68603dc7649f98bda495267973825e7fasatok                // Set the current spell checker if there is one or more spell checkers
165da317ef68603dc7649f98bda495267973825e7fasatok                // available. In this case, "sci" is the first one in the available spell
166da317ef68603dc7649f98bda495267973825e7fasatok                // checkers.
167da317ef68603dc7649f98bda495267973825e7fasatok                setCurrentSpellChecker(sci);
168da317ef68603dc7649f98bda495267973825e7fasatok                return sci;
169988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
170988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return mSpellCheckerMap.get(curSpellCheckerId);
171988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
172988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
173988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
174988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    @Override
175988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void getSpellCheckerService(SpellCheckerInfo info, String locale,
176988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener) {
177988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (!mSystemReady) {
178988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return;
179988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
180988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (info == null || tsListener == null) {
181988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Slog.e(TAG, "getSpellCheckerService: Invalid input.");
182988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return;
183988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
184988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final String sciId = info.getId();
185988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        synchronized(mSpellCheckerMap) {
186988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!mSpellCheckerMap.containsKey(sciId)) {
187988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
188988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
189988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (mSpellCheckerBindGroups.containsKey(sciId)) {
190988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mSpellCheckerBindGroups.get(sciId).addListener(tsListener, locale, scListener);
191988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
192988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
193988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final InternalServiceConnection connection = new InternalServiceConnection(
194988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    sciId, locale, scListener);
195988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
196988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            serviceIntent.setComponent(info.getComponent());
197988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
198988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Slog.e(TAG, "Failed to get a spell checker service.");
199988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
200988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
201988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final SpellCheckerBindGroup group = new SpellCheckerBindGroup(
202988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    connection, tsListener, locale, scListener);
203988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mSpellCheckerBindGroups.put(sciId, group);
204988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
205988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return;
206988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
207988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
208988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    @Override
209562ab585f9e413d9696ee250e5ec02f95889a157satok    public SpellCheckerInfo[] getEnabledSpellCheckers() {
210da317ef68603dc7649f98bda495267973825e7fasatok        if (DBG) {
211da317ef68603dc7649f98bda495267973825e7fasatok            Slog.d(TAG, "getEnabledSpellCheckers: " + mSpellCheckerList.size());
212da317ef68603dc7649f98bda495267973825e7fasatok            for (int i = 0; i < mSpellCheckerList.size(); ++i) {
213da317ef68603dc7649f98bda495267973825e7fasatok                Slog.d(TAG, "EnabledSpellCheckers: " + mSpellCheckerList.get(i).getPackageName());
214da317ef68603dc7649f98bda495267973825e7fasatok            }
215da317ef68603dc7649f98bda495267973825e7fasatok        }
216562ab585f9e413d9696ee250e5ec02f95889a157satok        return mSpellCheckerList.toArray(new SpellCheckerInfo[mSpellCheckerList.size()]);
217562ab585f9e413d9696ee250e5ec02f95889a157satok    }
218562ab585f9e413d9696ee250e5ec02f95889a157satok
219562ab585f9e413d9696ee250e5ec02f95889a157satok    @Override
220988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
221da317ef68603dc7649f98bda495267973825e7fasatok        if (DBG) {
222da317ef68603dc7649f98bda495267973825e7fasatok            Slog.d(TAG, "FinishSpellCheckerService");
223da317ef68603dc7649f98bda495267973825e7fasatok        }
224988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        synchronized(mSpellCheckerMap) {
225988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) {
226988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                if (group == null) continue;
227988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                group.removeListener(listener);
228988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
229988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
230988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
231988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
232988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private void setCurrentSpellChecker(SpellCheckerInfo sci) {
233562ab585f9e413d9696ee250e5ec02f95889a157satok        if (DBG) {
234562ab585f9e413d9696ee250e5ec02f95889a157satok            Slog.w(TAG, "setCurrentSpellChecker: " + sci.getId());
235562ab585f9e413d9696ee250e5ec02f95889a157satok        }
236988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (sci == null || mSpellCheckerMap.containsKey(sci.getId())) return;
237988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        Settings.Secure.putString(mContext.getContentResolver(),
238988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Settings.Secure.SPELL_CHECKER_SERVICE, sci == null ? "" : sci.getId());
239988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
240988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
241988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // SpellCheckerBindGroup contains active text service session listeners.
242988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
243988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // mSpellCheckerBindGroups
244988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private class SpellCheckerBindGroup {
245988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final InternalServiceConnection mInternalConnection;
246988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final ArrayList<InternalDeathRecipient> mListeners =
247988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                new ArrayList<InternalDeathRecipient>();
248988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
249988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public SpellCheckerBindGroup(InternalServiceConnection connection,
250988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ITextServicesSessionListener listener, String locale,
251988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ISpellCheckerSessionListener scListener) {
252988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mInternalConnection = connection;
253988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            addListener(listener, locale, scListener);
254988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
255988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
256988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onServiceConnected(ISpellCheckerService spellChecker) {
257da317ef68603dc7649f98bda495267973825e7fasatok            if (DBG) {
258da317ef68603dc7649f98bda495267973825e7fasatok                Slog.d(TAG, "onServiceConnected");
259da317ef68603dc7649f98bda495267973825e7fasatok            }
260988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            synchronized(mSpellCheckerMap) {
261988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                for (InternalDeathRecipient listener : mListeners) {
262988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    try {
263988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        final ISpellCheckerSession session = spellChecker.getISpellCheckerSession(
264988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                                listener.mScLocale, listener.mScListener);
265988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        listener.mTsListener.onServiceConnected(session);
266988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    } catch (RemoteException e) {
267988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    }
268988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
269988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
270988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
271988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
272988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void addListener(ITextServicesSessionListener tsListener, String locale,
273988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ISpellCheckerSessionListener scListener) {
274da317ef68603dc7649f98bda495267973825e7fasatok            if (DBG) {
275da317ef68603dc7649f98bda495267973825e7fasatok                Slog.d(TAG, "addListener: " + locale);
276da317ef68603dc7649f98bda495267973825e7fasatok            }
277988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            synchronized(mSpellCheckerMap) {
278988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                try {
279988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    final int size = mListeners.size();
280988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    for (int i = 0; i < size; ++i) {
281988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        if (mListeners.get(i).hasSpellCheckerListener(scListener)) {
282988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            // do not add the lister if the group already contains this.
283988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            return;
284988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        }
285988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    }
286988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    final InternalDeathRecipient recipient = new InternalDeathRecipient(
287988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            this, tsListener, locale, scListener);
288988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    scListener.asBinder().linkToDeath(recipient, 0);
289988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    mListeners.add(new InternalDeathRecipient(
290988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            this, tsListener, locale, scListener));
291988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                } catch(RemoteException e) {
292988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    // do nothing
293988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
294988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                cleanLocked();
295988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
296988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
297988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
298988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void removeListener(ISpellCheckerSessionListener listener) {
299da317ef68603dc7649f98bda495267973825e7fasatok            if (DBG) {
300da317ef68603dc7649f98bda495267973825e7fasatok                Slog.d(TAG, "remove listener");
301da317ef68603dc7649f98bda495267973825e7fasatok            }
302988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            synchronized(mSpellCheckerMap) {
303988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final int size = mListeners.size();
304988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final ArrayList<InternalDeathRecipient> removeList =
305988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        new ArrayList<InternalDeathRecipient>();
306988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                for (int i = 0; i < size; ++i) {
307988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    final InternalDeathRecipient tempRecipient = mListeners.get(i);
308988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    if(tempRecipient.hasSpellCheckerListener(listener)) {
309988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        removeList.add(tempRecipient);
310988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    }
311988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
312988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final int removeSize = removeList.size();
313988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                for (int i = 0; i < removeSize; ++i) {
314988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    mListeners.remove(removeList.get(i));
315988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
316988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                cleanLocked();
317988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
318988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
319988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
320988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void cleanLocked() {
321da317ef68603dc7649f98bda495267973825e7fasatok            if (DBG) {
322da317ef68603dc7649f98bda495267973825e7fasatok                Slog.d(TAG, "cleanLocked");
323da317ef68603dc7649f98bda495267973825e7fasatok            }
324988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (mListeners.isEmpty()) {
325988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mSpellCheckerBindGroups.remove(this);
326988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                // Unbind service when there is no active clients.
327988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mContext.unbindService(mInternalConnection);
328988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
329988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
330988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
331988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
332988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private class InternalServiceConnection implements ServiceConnection {
333988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final ISpellCheckerSessionListener mListener;
334988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final String mSciId;
335988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final String mLocale;
336988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public InternalServiceConnection(
337988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                String id, String locale, ISpellCheckerSessionListener listener) {
338988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mSciId = id;
339988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mLocale = locale;
340988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mListener = listener;
341988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
342988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
343988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
344988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onServiceConnected(ComponentName name, IBinder service) {
345988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            synchronized(mSpellCheckerMap) {
346988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ISpellCheckerService spellChecker = ISpellCheckerService.Stub.asInterface(service);
347988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
348988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                if (group != null) {
349988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    group.onServiceConnected(spellChecker);
350988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
351988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
352988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
353988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
354988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
355988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onServiceDisconnected(ComponentName name) {
356988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mSpellCheckerBindGroups.remove(mSciId);
357988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
358988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
359988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
360988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private class InternalDeathRecipient implements IBinder.DeathRecipient {
361988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public final ITextServicesSessionListener mTsListener;
362988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public final ISpellCheckerSessionListener mScListener;
363988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public final String mScLocale;
364988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final SpellCheckerBindGroup mGroup;
365988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public InternalDeathRecipient(SpellCheckerBindGroup group,
366988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ITextServicesSessionListener tsListener, String scLocale,
367988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ISpellCheckerSessionListener scListener) {
368988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mTsListener = tsListener;
369988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mScListener = scListener;
370988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mScLocale = scLocale;
371988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mGroup = group;
372988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
373988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
374988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) {
375988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return mScListener.equals(listener);
376988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
377988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
378988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
379988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void binderDied() {
380988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mGroup.removeListener(mScListener);
381988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
382988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
383988323c57bd25a58f05dfa492d9b9c8ab62c5153satok}
384