/* * 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.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. * *
There are three primary parties involved in the text services * framework (TSF) architecture:
* *{@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
*/
@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.
*/
@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.")
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.
*/
@NonNull
public List