File: TextServicesManager.java

package info (click to toggle)
android-platform-frameworks-base 1%3A14~beta1-3
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 326,092 kB
  • sloc: java: 2,032,373; xml: 343,016; cpp: 304,181; python: 3,683; ansic: 2,090; sh: 1,871; makefile: 117; sed: 19
file content (387 lines) | stat: -rw-r--r-- 15,226 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package android.view.textservice;

import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
import android.annotation.UserIdInt;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.UserHandle;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
import android.view.textservice.SpellCheckerSession.SpellCheckerSessionParams;

import com.android.internal.textservice.ISpellCheckerSessionListener;
import com.android.internal.textservice.ITextServicesManager;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * System API to the overall text services, which arbitrates interaction between applications
 * and text services.
 *
 * The user can change the current text services in Settings. And also applications can specify
 * the target text services.
 *
 * <h3>Architecture Overview</h3>
 *
 * <p>There are three primary parties involved in the text services
 * framework (TSF) architecture:</p>
 *
 * <ul>
 * <li> The <strong>text services manager</strong> as expressed by this class
 * is the central point of the system that manages interaction between all
 * other parts.  It is expressed as the client-side API here which exists
 * in each application context and communicates with a global system service
 * that manages the interaction across all processes.
 * <li> A <strong>text service</strong> implements a particular
 * interaction model allowing the client application to retrieve information of text.
 * The system binds to the current text service that is in use, causing it to be created and run.
 * <li> Multiple <strong>client applications</strong> arbitrate with the text service
 * manager for connections to text services.
 * </ul>
 *
 * <h3>Text services sessions</h3>
 * <ul>
 * <li>The <strong>spell checker session</strong> is one of the text services.
 * {@link android.view.textservice.SpellCheckerSession}</li>
 * </ul>
 *
 */
@SystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)
public final class TextServicesManager {
    private static final String TAG = TextServicesManager.class.getSimpleName();
    private static final boolean DBG = false;

    /**
     * @deprecated Do not use. Just kept because of {@link UnsupportedAppUsage} in
     * {@link #getInstance()}.
     */
    @Deprecated
    private static TextServicesManager sInstance;

    private final ITextServicesManager mService;

    @UserIdInt
    private final int mUserId;

    @Nullable
    private final InputMethodManager mInputMethodManager;

    private TextServicesManager(@UserIdInt int userId,
            @Nullable InputMethodManager inputMethodManager) throws ServiceNotFoundException {
        mService = ITextServicesManager.Stub.asInterface(
                ServiceManager.getServiceOrThrow(Context.TEXT_SERVICES_MANAGER_SERVICE));
        mUserId = userId;
        mInputMethodManager = inputMethodManager;
    }

    /**
     * The factory method of {@link TextServicesManager}.
     *
     * @param context {@link Context} from which {@link TextServicesManager} should be instantiated.
     * @return {@link TextServicesManager} that is associated with {@link Context#getUserId()}.
     * @throws ServiceNotFoundException When {@link TextServicesManager} is not available.
     * @hide
     */
    @UserHandleAware
    @NonNull
    public static TextServicesManager createInstance(@NonNull Context context)
            throws ServiceNotFoundException {
        return new TextServicesManager(context.getUserId(), context.getSystemService(
                InputMethodManager.class));
    }

    /**
     * @deprecated Do not use. Just kept because of {@link UnsupportedAppUsage} in
     * {@link #getInstance()}.
     * @hide
     */
    @UnsupportedAppUsage
    public static TextServicesManager getInstance() {
        synchronized (TextServicesManager.class) {
            if (sInstance == null) {
                try {
                    sInstance = new TextServicesManager(UserHandle.myUserId(), null);
                } catch (ServiceNotFoundException e) {
                    throw new IllegalStateException(e);
                }
            }
            return sInstance;
        }
    }

    /** @hide */
    @Nullable
    public InputMethodManager getInputMethodManager() {
        return mInputMethodManager;
    }

    /**
     * Returns the language component of a given locale string.
     */
    private static String parseLanguageFromLocaleString(String locale) {
        final int idx = locale.indexOf('_');
        if (idx < 0) {
            return locale;
        } else {
            return locale.substring(0, idx);
        }
    }

    /**
     * Get a spell checker session from the spell checker.
     *
     * <p>{@link SuggestionsInfo#RESULT_ATTR_IN_THE_DICTIONARY},
     * {@link SuggestionsInfo#RESULT_ATTR_LOOKS_LIKE_TYPO}, and
     * {@link SuggestionsInfo#RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS} will be passed to the spell
     * checker as supported attributes.
     *
     * @param locale the locale for the spell checker. If {@code locale} is null and
     * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
     * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
     * the locale specified in Settings will be returned only when it is same as {@code locale}.
     * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
     * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
     * selected.
     * @param listener a spell checker session lister for getting results from the spell checker.
     * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
     * languages in settings will be returned.
     * @return a spell checker session of the spell checker
     */
    @UserHandleAware
    @Nullable
    public SpellCheckerSession newSpellCheckerSession(@Nullable Bundle bundle,
            @Nullable Locale locale,
            @NonNull SpellCheckerSessionListener listener,
            boolean referToSpellCheckerLanguageSettings) {
        // Attributes existed before {@link #newSpellCheckerSession(Locale, boolean, int, Bundle,
        // Executor, SpellCheckerSessionListener)} was introduced.
        int supportedAttributes = SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY
                | SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO
                | SuggestionsInfo.RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS;
        SpellCheckerSessionParams.Builder paramsBuilder = new SpellCheckerSessionParams.Builder()
                .setLocale(locale)
                .setShouldReferToSpellCheckerLanguageSettings(referToSpellCheckerLanguageSettings)
                .setSupportedAttributes(supportedAttributes);
        if (bundle != null) {
            paramsBuilder.setExtras(bundle);
        }
        // Using the implicit looper to preserve the old behavior.
        Executor executor = new HandlerExecutor(new Handler());
        return newSpellCheckerSession(paramsBuilder.build(), executor, listener);
    }

    /**
     * Get a spell checker session from the spell checker.
     *
     * @param params The parameters passed to the spell checker.
     * @param executor The executor on which {@code listener} will be called back.
     * @param listener a spell checker session lister for getting results from the spell checker.
     * @return The spell checker session of the spell checker.
     */
    @UserHandleAware
    @Nullable
    public SpellCheckerSession newSpellCheckerSession(
            @NonNull SpellCheckerSessionParams params,
            @NonNull @CallbackExecutor Executor executor,
            @NonNull SpellCheckerSessionListener listener) {
        Objects.requireNonNull(executor);
        Objects.requireNonNull(listener);
        Locale locale = params.getLocale();
        if (!params.shouldReferToSpellCheckerLanguageSettings() && locale == null) {
            throw new IllegalArgumentException("Locale should not be null if you don't refer"
                    + " settings.");
        }

        if (params.shouldReferToSpellCheckerLanguageSettings() && !isSpellCheckerEnabled()) {
            return null;
        }

        final SpellCheckerInfo sci;
        try {
            sci = mService.getCurrentSpellChecker(mUserId, null);
        } catch (RemoteException e) {
            return null;
        }
        if (sci == null) {
            return null;
        }
        SpellCheckerSubtype subtypeInUse = null;
        if (params.shouldReferToSpellCheckerLanguageSettings()) {
            subtypeInUse = getCurrentSpellCheckerSubtype(true);
            if (subtypeInUse == null) {
                return null;
            }
            if (locale != null) {
                final String subtypeLocale = subtypeInUse.getLocale();
                final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
                if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
                    return null;
                }
            }
        } else {
            final String localeStr = locale.toString();
            for (int i = 0; i < sci.getSubtypeCount(); ++i) {
                final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
                final String tempSubtypeLocale = subtype.getLocale();
                final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
                if (tempSubtypeLocale.equals(localeStr)) {
                    subtypeInUse = subtype;
                    break;
                } else if (tempSubtypeLanguage.length() >= 2 &&
                        locale.getLanguage().equals(tempSubtypeLanguage)) {
                    subtypeInUse = subtype;
                }
            }
        }
        if (subtypeInUse == null) {
            return null;
        }
        final SpellCheckerSession session = new SpellCheckerSession(sci, this, listener, executor);
        try {
            mService.getSpellCheckerService(mUserId, sci.getId(), subtypeInUse.getLocale(),
                    session.getTextServicesSessionListener(),
                    session.getSpellCheckerSessionListener(),
                    params.getExtras(), params.getSupportedAttributes());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        return session;
    }

    /**
     * Deprecated. Use {@link #getEnabledSpellCheckerInfos()} instead.
     * @hide
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553,
            publicAlternatives = "Use {@link #getEnabledSpellCheckerInfos()} instead.")
    @UserHandleAware
    public SpellCheckerInfo[] getEnabledSpellCheckers() {
        try {
            final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers(mUserId);
            if (DBG) {
                Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
            }
            return retval;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Retrieve the list of currently enabled spell checkers.
     *
     * @return The list of currently enabled spell checkers.
     */
    @UserHandleAware
    @NonNull
    public List<SpellCheckerInfo> getEnabledSpellCheckerInfos() {
        final SpellCheckerInfo[] enabledSpellCheckers = getEnabledSpellCheckers();
        return enabledSpellCheckers != null
                ? Arrays.asList(enabledSpellCheckers) : Collections.emptyList();
    }

    /**
     * Retrieve the currently active spell checker, or null if there is none.
     *
     * @return The current active spell checker info.
     */
    @UserHandleAware
    @Nullable
    public SpellCheckerInfo getCurrentSpellCheckerInfo() {
        try {
            // Passing null as a locale for ICS
            return mService.getCurrentSpellChecker(mUserId, null);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Deprecated. Use {@link #getCurrentSpellCheckerInfo()} instead.
     * @hide
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R,
            publicAlternatives = "Use {@link #getCurrentSpellCheckerInfo()} instead.")
    @UserHandleAware
    @Nullable
    public SpellCheckerInfo getCurrentSpellChecker() {
        return getCurrentSpellCheckerInfo();
    }

    /**
     * Retrieve the selected subtype of the selected spell checker, or null if there is none.
     *
     * @param allowImplicitlySelectedSubtype {@code true} to return the default language matching
     * system locale if there's no subtype selected explicitly, otherwise, returns null.
     * @return The meta information of the selected subtype of the selected spell checker.
     *
     * @hide
     */
    @UnsupportedAppUsage
    @UserHandleAware
    @Nullable
    public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
            boolean allowImplicitlySelectedSubtype) {
        try {
            return mService.getCurrentSpellCheckerSubtype(mUserId, allowImplicitlySelectedSubtype);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Return whether the spell checker is enabled or not.
     *
     * @return {@code true} if spell checker is enabled, {@code false} otherwise.
     */
    @UserHandleAware
    public boolean isSpellCheckerEnabled() {
        try {
            return mService.isSpellCheckerEnabled(mUserId);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    @UserHandleAware
    void finishSpellCheckerService(ISpellCheckerSessionListener listener) {
        try {
            mService.finishSpellCheckerService(mUserId, listener);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}