TextServicesManagerService.java revision 562ab585f9e413d9696ee250e5ec02f95889a157
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);
84988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                if (sci == null) {
85988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    sci = findAvailSpellCheckerLocked(null, null);
86988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    if (sci == null) return;
87988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    // Set the current spell checker if there is one or more spell checkers
88988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    // available. In this case, "sci" is the first one in the available spell
89988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    // checkers.
90988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    setCurrentSpellChecker(sci);
91988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
92988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final String packageName = sci.getPackageName();
93988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final int change = isPackageDisappearing(packageName);
94988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) {
95988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    // Package disappearing
96988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    setCurrentSpellChecker(findAvailSpellCheckerLocked(null, packageName));
97988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                } else if (isPackageModified(packageName)) {
98988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    // Package modified
99988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    setCurrentSpellChecker(findAvailSpellCheckerLocked(null, packageName));
100988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
101988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
102988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
103988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
104988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
105988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private static void buildSpellCheckerMapLocked(Context context,
106988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            ArrayList<SpellCheckerInfo> list, HashMap<String, SpellCheckerInfo> map) {
107988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        list.clear();
108988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        map.clear();
109988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final PackageManager pm = context.getPackageManager();
110988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        List<ResolveInfo> services = pm.queryIntentServices(
111988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                new Intent(SpellCheckerService.SERVICE_INTERFACE), PackageManager.GET_META_DATA);
112988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final int N = services.size();
113988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        for (int i = 0; i < N; ++i) {
114988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final ResolveInfo ri = services.get(i);
115988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final ServiceInfo si = ri.serviceInfo;
116988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final ComponentName compName = new ComponentName(si.packageName, si.name);
117988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!android.Manifest.permission.BIND_TEXT_SERVICE.equals(si.permission)) {
118988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Slog.w(TAG, "Skipping text service " + compName
119988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        + ": it does not require the permission "
120988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        + android.Manifest.permission.BIND_TEXT_SERVICE);
121988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                continue;
122988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
123988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (DBG) Slog.d(TAG, "Add: " + compName);
124988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final SpellCheckerInfo sci = new SpellCheckerInfo(context, ri);
125988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            list.add(sci);
126988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            map.put(sci.getId(), sci);
127988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
128988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
129988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
130988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // TODO: find an appropriate spell checker for specified locale
131988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private SpellCheckerInfo findAvailSpellCheckerLocked(String locale, String prefPackage) {
132988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final int spellCheckersCount = mSpellCheckerList.size();
133988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (spellCheckersCount == 0) {
134988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Slog.w(TAG, "no available spell checker services found");
135988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return null;
136988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
137988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (prefPackage != null) {
138988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            for (int i = 0; i < spellCheckersCount; ++i) {
139988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final SpellCheckerInfo sci = mSpellCheckerList.get(i);
140988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                if (prefPackage.equals(sci.getPackageName())) {
141988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    return sci;
142988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
143988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
144988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
145988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (spellCheckersCount > 1) {
146988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Slog.w(TAG, "more than one spell checker service found, picking first");
147988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
148988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return mSpellCheckerList.get(0);
149988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
150988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
151988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // TODO: Save SpellCheckerService by supported languages. Currently only one spell
152988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // checker is saved.
153988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    @Override
154988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public SpellCheckerInfo getCurrentSpellChecker(String locale) {
155988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        synchronized (mSpellCheckerMap) {
156988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final String curSpellCheckerId =
157988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    Settings.Secure.getString(mContext.getContentResolver(),
158988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            Settings.Secure.SPELL_CHECKER_SERVICE);
159562ab585f9e413d9696ee250e5ec02f95889a157satok            if (DBG) {
160562ab585f9e413d9696ee250e5ec02f95889a157satok                Slog.w(TAG, "getCurrentSpellChecker: " + curSpellCheckerId);
161562ab585f9e413d9696ee250e5ec02f95889a157satok            }
162988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (TextUtils.isEmpty(curSpellCheckerId)) {
163988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return null;
164988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
165988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return mSpellCheckerMap.get(curSpellCheckerId);
166988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
167988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
168988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
169988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    @Override
170988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void getSpellCheckerService(SpellCheckerInfo info, String locale,
171988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            ITextServicesSessionListener tsListener, ISpellCheckerSessionListener scListener) {
172988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (!mSystemReady) {
173988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return;
174988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
175988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (info == null || tsListener == null) {
176988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            Slog.e(TAG, "getSpellCheckerService: Invalid input.");
177988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return;
178988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
179988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final String sciId = info.getId();
180988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        synchronized(mSpellCheckerMap) {
181988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!mSpellCheckerMap.containsKey(sciId)) {
182988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
183988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
184988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (mSpellCheckerBindGroups.containsKey(sciId)) {
185988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mSpellCheckerBindGroups.get(sciId).addListener(tsListener, locale, scListener);
186988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
187988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
188988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final InternalServiceConnection connection = new InternalServiceConnection(
189988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    sciId, locale, scListener);
190988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final Intent serviceIntent = new Intent(SpellCheckerService.SERVICE_INTERFACE);
191988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            serviceIntent.setComponent(info.getComponent());
192988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
193988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Slog.e(TAG, "Failed to get a spell checker service.");
194988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                return;
195988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
196988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            final SpellCheckerBindGroup group = new SpellCheckerBindGroup(
197988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    connection, tsListener, locale, scListener);
198988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mSpellCheckerBindGroups.put(sciId, group);
199988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
200988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        return;
201988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
202988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
203988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    @Override
204562ab585f9e413d9696ee250e5ec02f95889a157satok    public SpellCheckerInfo[] getEnabledSpellCheckers() {
205562ab585f9e413d9696ee250e5ec02f95889a157satok        return mSpellCheckerList.toArray(new SpellCheckerInfo[mSpellCheckerList.size()]);
206562ab585f9e413d9696ee250e5ec02f95889a157satok    }
207562ab585f9e413d9696ee250e5ec02f95889a157satok
208562ab585f9e413d9696ee250e5ec02f95889a157satok    @Override
209988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    public void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
210988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        synchronized(mSpellCheckerMap) {
211988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            for (SpellCheckerBindGroup group : mSpellCheckerBindGroups.values()) {
212988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                if (group == null) continue;
213988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                group.removeListener(listener);
214988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
215988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
216988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
217988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
218988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private void setCurrentSpellChecker(SpellCheckerInfo sci) {
219562ab585f9e413d9696ee250e5ec02f95889a157satok        if (DBG) {
220562ab585f9e413d9696ee250e5ec02f95889a157satok            Slog.w(TAG, "setCurrentSpellChecker: " + sci.getId());
221562ab585f9e413d9696ee250e5ec02f95889a157satok        }
222988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        if (sci == null || mSpellCheckerMap.containsKey(sci.getId())) return;
223988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        Settings.Secure.putString(mContext.getContentResolver(),
224988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                Settings.Secure.SPELL_CHECKER_SERVICE, sci == null ? "" : sci.getId());
225988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
226988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
227988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // SpellCheckerBindGroup contains active text service session listeners.
228988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // If there are no listeners anymore, the SpellCheckerBindGroup instance will be removed from
229988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    // mSpellCheckerBindGroups
230988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private class SpellCheckerBindGroup {
231988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final InternalServiceConnection mInternalConnection;
232988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        final ArrayList<InternalDeathRecipient> mListeners =
233988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                new ArrayList<InternalDeathRecipient>();
234988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
235988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public SpellCheckerBindGroup(InternalServiceConnection connection,
236988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ITextServicesSessionListener listener, String locale,
237988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ISpellCheckerSessionListener scListener) {
238988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mInternalConnection = connection;
239988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            addListener(listener, locale, scListener);
240988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
241988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
242988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onServiceConnected(ISpellCheckerService spellChecker) {
243988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            synchronized(mSpellCheckerMap) {
244988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                for (InternalDeathRecipient listener : mListeners) {
245988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    try {
246988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        final ISpellCheckerSession session = spellChecker.getISpellCheckerSession(
247988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                                listener.mScLocale, listener.mScListener);
248988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        listener.mTsListener.onServiceConnected(session);
249988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    } catch (RemoteException e) {
250988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    }
251988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
252988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
253988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
254988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
255988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void addListener(ITextServicesSessionListener tsListener, String locale,
256988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ISpellCheckerSessionListener scListener) {
257988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            synchronized(mSpellCheckerMap) {
258988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                try {
259988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    final int size = mListeners.size();
260988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    for (int i = 0; i < size; ++i) {
261988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        if (mListeners.get(i).hasSpellCheckerListener(scListener)) {
262988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            // do not add the lister if the group already contains this.
263988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            return;
264988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        }
265988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    }
266988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    final InternalDeathRecipient recipient = new InternalDeathRecipient(
267988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            this, tsListener, locale, scListener);
268988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    scListener.asBinder().linkToDeath(recipient, 0);
269988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    mListeners.add(new InternalDeathRecipient(
270988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                            this, tsListener, locale, scListener));
271988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                } catch(RemoteException e) {
272988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    // do nothing
273988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
274988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                cleanLocked();
275988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
276988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
277988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
278988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void removeListener(ISpellCheckerSessionListener listener) {
279988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            synchronized(mSpellCheckerMap) {
280988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final int size = mListeners.size();
281988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final ArrayList<InternalDeathRecipient> removeList =
282988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        new ArrayList<InternalDeathRecipient>();
283988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                for (int i = 0; i < size; ++i) {
284988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    final InternalDeathRecipient tempRecipient = mListeners.get(i);
285988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    if(tempRecipient.hasSpellCheckerListener(listener)) {
286988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                        removeList.add(tempRecipient);
287988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    }
288988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
289988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final int removeSize = removeList.size();
290988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                for (int i = 0; i < removeSize; ++i) {
291988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    mListeners.remove(removeList.get(i));
292988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
293988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                cleanLocked();
294988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
295988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
296988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
297988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private void cleanLocked() {
298988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            if (mListeners.isEmpty()) {
299988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mSpellCheckerBindGroups.remove(this);
300988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                // Unbind service when there is no active clients.
301988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                mContext.unbindService(mInternalConnection);
302988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
303988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
304988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
305988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
306988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private class InternalServiceConnection implements ServiceConnection {
307988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final ISpellCheckerSessionListener mListener;
308988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final String mSciId;
309988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final String mLocale;
310988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public InternalServiceConnection(
311988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                String id, String locale, ISpellCheckerSessionListener listener) {
312988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mSciId = id;
313988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mLocale = locale;
314988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mListener = listener;
315988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
316988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
317988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
318988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onServiceConnected(ComponentName name, IBinder service) {
319988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            synchronized(mSpellCheckerMap) {
320988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ISpellCheckerService spellChecker = ISpellCheckerService.Stub.asInterface(service);
321988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                final SpellCheckerBindGroup group = mSpellCheckerBindGroups.get(mSciId);
322988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                if (group != null) {
323988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                    group.onServiceConnected(spellChecker);
324988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                }
325988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            }
326988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
327988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
328988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
329988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void onServiceDisconnected(ComponentName name) {
330988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mSpellCheckerBindGroups.remove(mSciId);
331988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
332988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
333988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
334988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    private class InternalDeathRecipient implements IBinder.DeathRecipient {
335988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public final ITextServicesSessionListener mTsListener;
336988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public final ISpellCheckerSessionListener mScListener;
337988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public final String mScLocale;
338988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        private final SpellCheckerBindGroup mGroup;
339988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public InternalDeathRecipient(SpellCheckerBindGroup group,
340988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ITextServicesSessionListener tsListener, String scLocale,
341988323c57bd25a58f05dfa492d9b9c8ab62c5153satok                ISpellCheckerSessionListener scListener) {
342988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mTsListener = tsListener;
343988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mScListener = scListener;
344988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mScLocale = scLocale;
345988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mGroup = group;
346988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
347988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
348988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public boolean hasSpellCheckerListener(ISpellCheckerSessionListener listener) {
349988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            return mScListener.equals(listener);
350988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
351988323c57bd25a58f05dfa492d9b9c8ab62c5153satok
352988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        @Override
353988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        public void binderDied() {
354988323c57bd25a58f05dfa492d9b9c8ab62c5153satok            mGroup.removeListener(mScListener);
355988323c57bd25a58f05dfa492d9b9c8ab62c5153satok        }
356988323c57bd25a58f05dfa492d9b9c8ab62c5153satok    }
357988323c57bd25a58f05dfa492d9b9c8ab62c5153satok}
358