Make LocaleUtils.constructLocaleFromString as @Nonnull

Change-Id: I82d574c67b25239510f3ecc8882efe46e40677eb
main
Tadashi G. Takaoka 2014-11-17 18:07:35 +09:00
parent a94733cbca
commit ebe5b42f71
14 changed files with 144 additions and 100 deletions

View File

@ -104,7 +104,8 @@ public final class LocaleUtils {
* @param testedLocale the locale to test.
* @return a constant that measures how well the tested locale matches the reference locale.
*/
public static int getMatchLevel(final String referenceLocale, final String testedLocale) {
public static int getMatchLevel(@Nullable final String referenceLocale,
@Nullable final String testedLocale) {
if (StringUtils.isEmpty(referenceLocale)) {
return StringUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH;
}
@ -167,11 +168,8 @@ public final class LocaleUtils {
* @param localeString a string specification of a locale, in a format of "ll_cc_variant" where
* "ll" is a language code, "cc" is a country code.
*/
@Nullable
public static Locale constructLocaleFromString(@Nullable final String localeString) {
if (localeString == null) {
return null;
}
@Nonnull
public static Locale constructLocaleFromString(@Nonnull final String localeString) {
synchronized (sLocaleCache) {
if (sLocaleCache.containsKey(localeString)) {
return sLocaleCache.get(localeString);

View File

@ -118,11 +118,7 @@ public final class SuggestionSpanUtils {
if (TextUtils.isEmpty(localeString)) {
continue;
}
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
if (locale == null) {
continue;
}
return locale;
return LocaleUtils.constructLocaleFromString(localeString);
}
return null;
}

View File

@ -22,6 +22,7 @@ import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import android.widget.Toast;
import com.android.inputmethod.latin.R;
@ -33,6 +34,8 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
/**
* Service that handles background tasks for the dictionary provider.
*
@ -51,6 +54,8 @@ import java.util.concurrent.TimeUnit;
* to access, and mark the current state as such.
*/
public final class DictionaryService extends Service {
private static final String TAG = DictionaryService.class.getSimpleName();
/**
* The package name, to use in the intent actions.
*/
@ -156,9 +161,14 @@ public final class DictionaryService extends Service {
final int startId) {
final DictionaryService self = this;
if (SHOW_DOWNLOAD_TOAST_INTENT_ACTION.equals(intent.getAction())) {
// This is a UI action, it can't be run in another thread
showStartDownloadingToast(this, LocaleUtils.constructLocaleFromString(
intent.getStringExtra(LOCALE_INTENT_ARGUMENT)));
final String localeString = intent.getStringExtra(LOCALE_INTENT_ARGUMENT);
if (localeString == null) {
Log.e(TAG, "Received " + intent.getAction() + " without locale; skipped");
} else {
// This is a UI action, it can't be run in another thread
showStartDownloadingToast(
this, LocaleUtils.constructLocaleFromString(localeString));
}
} else {
// If it's a command that does not require UI, arrange for the work to be done on a
// separate thread, so that we can return right away. The executor will spawn a thread
@ -245,7 +255,8 @@ public final class DictionaryService extends Service {
/**
* Shows a toast informing the user that an automatic dictionary download is starting.
*/
private static void showStartDownloadingToast(final Context context, final Locale locale) {
private static void showStartDownloadingToast(final Context context,
@Nonnull final Locale locale) {
final String toastText = String.format(
context.getString(R.string.toast_downloading_suggestions),
locale.getDisplayName());

View File

@ -28,7 +28,7 @@ import com.android.inputmethod.annotations.ExternallyReferenced;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.common.LocaleUtils;
import java.util.Locale;
import javax.annotation.Nullable;
/**
* This implements the dialog for asking the user whether it's okay to download dictionaries over
@ -54,11 +54,11 @@ public final class DownloadOverMeteredDialog extends Activity {
setTexts(localeString, size);
}
private void setTexts(final String localeString, final long size) {
private void setTexts(@Nullable final String localeString, final long size) {
final String promptFormat = getString(R.string.should_download_over_metered_prompt);
final String allowButtonFormat = getString(R.string.download_over_metered);
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
final String language = (null == locale ? "" : locale.getDisplayLanguage());
final String language = (null == localeString) ? ""
: LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
final TextView prompt = (TextView)findViewById(R.id.download_over_metered_prompt);
prompt.setText(Html.fromHtml(String.format(promptFormat, language)));
final Button allowButton = (Button)findViewById(R.id.allow_button);

View File

@ -57,7 +57,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;
@ -880,8 +879,8 @@ public final class UpdateHandler {
// None of those are expected to happen, but just in case...
if (null == notificationIntent || null == notificationManager) return;
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
final String language = (null == locale ? "" : locale.getDisplayLanguage());
final String language = (null == localeString) ? ""
: LocaleUtils.constructLocaleFromString(localeString).getDisplayLanguage();
final String titleFormat = context.getString(R.string.dict_available_notification_title);
final String notificationTitle = String.format(titleFormat, language);
final Notification.Builder builder = new Notification.Builder(context)

View File

@ -257,7 +257,7 @@ public class DictionaryFacilitator {
return mMostProbableDictionaryGroup;
}
public void switchMostProbableLanguage(final Locale locale) {
public void switchMostProbableLanguage(@Nullable final Locale locale) {
if (null == locale) {
// In many cases, there is no locale to a committed word. For example, a typed word
// that does not auto-correct has no locale. In this case we simply do not change

View File

@ -101,25 +101,23 @@ public final class DictionaryFactory {
}
final String wordlistId =
DictionaryInfoUtils.getWordListIdFromFileName(new File(f.mFilename).getName());
if (null != wordlistId) {
// TODO: this is a reasonable last resort, but it is suboptimal.
// The following will remove the entry for this dictionary with the dictionary
// provider. When the metadata is downloaded again, we will try downloading it
// again.
// However, in the practice that will mean the user will find themselves without
// the new dictionary. That's fine for languages where it's included in the APK,
// but for other languages it will leave the user without a dictionary at all until
// the next update, which may be a few days away.
// Ideally, we would trigger a new download right away, and use increasing retry
// delays for this particular id/version combination.
// Then again, this is expected to only ever happen in case of human mistake. If
// the wrong file is on the server, the following is still doing the right thing.
// If it's a file left over from the last version however, it's not great.
BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider(
providerClient,
context.getString(R.string.dictionary_pack_client_id),
wordlistId);
}
// TODO: this is a reasonable last resort, but it is suboptimal.
// The following will remove the entry for this dictionary with the dictionary
// provider. When the metadata is downloaded again, we will try downloading it
// again.
// However, in the practice that will mean the user will find themselves without
// the new dictionary. That's fine for languages where it's included in the APK,
// but for other languages it will leave the user without a dictionary at all until
// the next update, which may be a few days away.
// Ideally, we would trigger a new download right away, and use increasing retry
// delays for this particular id/version combination.
// Then again, this is expected to only ever happen in case of human mistake. If
// the wrong file is on the server, the following is still doing the right thing.
// If it's a file left over from the last version however, it's not great.
BinaryDictionaryFileDumper.reportBrokenFileToDictionaryProvider(
providerClient,
context.getString(R.string.dictionary_pack_client_id),
wordlistId);
}
}

View File

@ -101,12 +101,12 @@ public class ExternalDictionaryGetterForDebug {
final File file = new File(dirPath, fileName.toString());
final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file);
final StringBuilder message = new StringBuilder();
final String locale = header.getLocaleString();
for (String key : header.mDictionaryOptions.mAttributes.keySet()) {
final String localeString = header.mLocaleString;
for (final String key : header.mDictionaryOptions.mAttributes.keySet()) {
message.append(key + " = " + header.mDictionaryOptions.mAttributes.get(key));
message.append("\n");
}
final String languageName = LocaleUtils.constructLocaleFromString(locale)
final String languageName = LocaleUtils.constructLocaleFromString(localeString)
.getDisplayName(Locale.getDefault());
final String title = String.format(
context.getString(R.string.read_external_dictionary_confirm_install_message),
@ -146,11 +146,12 @@ public class ExternalDictionaryGetterForDebug {
BufferedOutputStream outputStream = null;
File tempFile = null;
try {
final String locale = header.getLocaleString();
final String localeString = header.mLocaleString;
// Create the id for a main dictionary for this locale
final String id = BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY
+ BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale;
final String finalFileName = DictionaryInfoUtils.getCacheFileName(id, locale, context);
+ BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + localeString;
final String finalFileName = DictionaryInfoUtils.getCacheFileName(
id, localeString, context);
final String tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
tempFile = new File(tempFileName);
tempFile.delete();

View File

@ -19,13 +19,24 @@ package com.android.inputmethod.latin.makedict;
import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Class representing dictionary header.
*/
public final class DictionaryHeader {
public final int mBodyOffset;
@Nonnull
public final DictionaryOptions mDictionaryOptions;
@Nonnull
public final FormatOptions mFormatOptions;
@Nonnull
public final String mLocaleString;
@Nonnull
public final String mVersionString;
@Nonnull
public final String mIdString;
// Note that these are corresponding definitions in native code in latinime::HeaderPolicy
// and latinime::HeaderReadWriteUtils.
@ -46,39 +57,32 @@ public final class DictionaryHeader {
public static final String ATTRIBUTE_VALUE_TRUE = "1";
public static final String CODE_POINT_TABLE_KEY = "codePointTable";
public DictionaryHeader(final int headerSize, final DictionaryOptions dictionaryOptions,
final FormatOptions formatOptions) throws UnsupportedFormatException {
public DictionaryHeader(final int headerSize,
@Nonnull final DictionaryOptions dictionaryOptions,
@Nonnull final FormatOptions formatOptions) throws UnsupportedFormatException {
mDictionaryOptions = dictionaryOptions;
mFormatOptions = formatOptions;
mBodyOffset = formatOptions.mVersion < FormatSpec.VERSION4 ? headerSize : 0;
if (null == getLocaleString()) {
final String localeString = dictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_KEY);
if (null == localeString) {
throw new UnsupportedFormatException("Cannot create a FileHeader without a locale");
}
if (null == getVersion()) {
final String versionString = dictionaryOptions.mAttributes.get(DICTIONARY_VERSION_KEY);
if (null == versionString) {
throw new UnsupportedFormatException(
"Cannot create a FileHeader without a version");
}
if (null == getId()) {
final String idString = dictionaryOptions.mAttributes.get(DICTIONARY_ID_KEY);
if (null == idString) {
throw new UnsupportedFormatException("Cannot create a FileHeader without an ID");
}
}
// Helper method to get the locale as a String
public String getLocaleString() {
return mDictionaryOptions.mAttributes.get(DICTIONARY_LOCALE_KEY);
}
// Helper method to get the version String
public String getVersion() {
return mDictionaryOptions.mAttributes.get(DICTIONARY_VERSION_KEY);
}
// Helper method to get the dictionary ID as a String
public String getId() {
return mDictionaryOptions.mAttributes.get(DICTIONARY_ID_KEY);
mLocaleString = localeString;
mVersionString = versionString;
mIdString = idString;
}
// Helper method to get the description
@Nullable
public String getDescription() {
// TODO: Right now each dictionary file comes with a description in its own language.
// It will display as is no matter the device's locale. It should be internationalized.

View File

@ -115,7 +115,8 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session {
@Override
public void onCreate() {
final String localeString = getLocale();
mLocale = LocaleUtils.constructLocaleFromString(localeString);
mLocale = (null == localeString) ? null
: LocaleUtils.constructLocaleFromString(localeString);
mScript = ScriptUtils.getScriptFromSpellCheckerLocale(mLocale);
}

View File

@ -34,6 +34,8 @@ import java.util.ArrayList;
import java.util.Locale;
import java.util.TreeSet;
import javax.annotation.Nullable;
// Caveat: This class is basically taken from
// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryAddWordContents.java
// in order to deal with some devices that have issues with the user dictionary handling
@ -218,8 +220,8 @@ public class UserDictionaryAddWordContents {
public static class LocaleRenderer {
private final String mLocaleString;
private final String mDescription;
// LocaleString may NOT be null.
public LocaleRenderer(final Context context, final String localeString) {
public LocaleRenderer(final Context context, @Nullable final String localeString) {
mLocaleString = localeString;
if (null == localeString) {
mDescription = context.getString(R.string.user_dict_settings_more_languages);

View File

@ -37,6 +37,8 @@ import java.util.List;
import java.util.Locale;
import java.util.TreeSet;
import javax.annotation.Nullable;
// Caveat: This class is basically taken from
// packages/apps/Settings/src/com/android/settings/inputmethod/UserDictionaryList.java
// in order to deal with some devices that have issues with the user dictionary handling
@ -131,21 +133,23 @@ public class UserDictionaryList extends PreferenceFragment {
/**
* Create a single User Dictionary Preference object, with its parameters set.
* @param locale The locale for which this user dictionary is for.
* @param localeString The locale for which this user dictionary is for.
* @return The corresponding preference.
*/
protected Preference createUserDictionaryPreference(final String locale) {
protected Preference createUserDictionaryPreference(@Nullable final String localeString) {
final Preference newPref = new Preference(getActivity());
final Intent intent = new Intent(USER_DICTIONARY_SETTINGS_INTENT_ACTION);
if (null == locale) {
if (null == localeString) {
newPref.setTitle(Locale.getDefault().getDisplayName());
} else {
if ("".equals(locale))
if (localeString.isEmpty()) {
newPref.setTitle(getString(R.string.user_dict_settings_all_languages));
else
newPref.setTitle(LocaleUtils.constructLocaleFromString(locale).getDisplayName());
intent.putExtra("locale", locale);
newPref.getExtras().putString("locale", locale);
} else {
newPref.setTitle(
LocaleUtils.constructLocaleFromString(localeString).getDisplayName());
}
intent.putExtra("locale", localeString);
newPref.getExtras().putString("locale", localeString);
}
newPref.setIntent(intent);
newPref.setFragment(UserDictionarySettings.class.getName());

View File

@ -40,6 +40,9 @@ import java.util.Iterator;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* This class encapsulates the logic for the Latin-IME side of dictionary information management.
*/
@ -59,19 +62,26 @@ public class DictionaryInfoUtils {
private static final String DATE_COLUMN = "date";
private static final String FILESIZE_COLUMN = "filesize";
private static final String VERSION_COLUMN = "version";
@Nonnull
public final String mId;
@Nonnull
public final Locale mLocale;
@Nullable
public final String mDescription;
@Nonnull
public final AssetFileAddress mFileAddress;
public final int mVersion;
public DictionaryInfo(final String id, final Locale locale, final String description,
final AssetFileAddress fileAddress, final int version) {
public DictionaryInfo(@Nonnull final String id, @Nonnull final Locale locale,
@Nullable final String description, @Nonnull final AssetFileAddress fileAddress,
final int version) {
mId = id;
mLocale = locale;
mDescription = description;
mFileAddress = fileAddress;
mVersion = version;
}
public ContentValues toContentValues() {
final ContentValues values = new ContentValues();
values.put(WORDLISTID_COLUMN, mId);
@ -144,7 +154,8 @@ public class DictionaryInfoUtils {
/**
* Reverse escaping done by replaceFileNameDangerousCharacters.
*/
public static String getWordListIdFromFileName(final String fname) {
@Nonnull
public static String getWordListIdFromFileName(@Nonnull final String fname) {
final StringBuilder sb = new StringBuilder();
final int fnameLength = fname.length();
for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
@ -176,12 +187,15 @@ public class DictionaryInfoUtils {
* {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}.
* @return The category as a string or null if it can't be found in the file name.
*/
public static String getCategoryFromFileName(final String fileName) {
@Nullable
public static String getCategoryFromFileName(@Nonnull final String fileName) {
final String id = getWordListIdFromFileName(fileName);
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
// An id is supposed to be in format category:locale, so splitting on the separator
// should yield a 2-elements array
if (2 != idArray.length) return null;
if (2 != idArray.length) {
return null;
}
return idArray[0];
}
@ -225,7 +239,9 @@ public class DictionaryInfoUtils {
final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR);
// An id is supposed to be in format category:locale, so splitting on the separator
// should yield a 2-elements array
if (2 != idArray.length) return false;
if (2 != idArray.length) {
return false;
}
return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
}
@ -266,7 +282,9 @@ public class DictionaryInfoUtils {
*/
public static int getMainDictionaryResourceId(final Resources res, final Locale locale) {
int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
if (0 != resourceId) return resourceId;
if (0 != resourceId) {
return resourceId;
}
return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME);
}
@ -335,10 +353,10 @@ public class DictionaryInfoUtils {
if (header == null) {
return null;
}
final String id = header.getId();
final Locale locale = LocaleUtils.constructLocaleFromString(header.getLocaleString());
final String id = header.mIdString;
final Locale locale = LocaleUtils.constructLocaleFromString(header.mLocaleString);
final String description = header.getDescription();
final String version = header.getVersion();
final String version = header.mVersionString;
return new DictionaryInfo(id, locale, description, fileAddress, Integer.parseInt(version));
}
@ -366,10 +384,13 @@ public class DictionaryInfoUtils {
if (null != directoryList) {
for (final File directory : directoryList) {
final String localeString = getWordListIdFromFileName(directory.getName());
File[] dicts = BinaryDictionaryGetter.getCachedWordLists(localeString, context);
final File[] dicts = BinaryDictionaryGetter.getCachedWordLists(
localeString, context);
for (final File dict : dicts) {
final String wordListId = getWordListIdFromFileName(dict.getName());
if (!DictionaryInfoUtils.isMainWordListId(wordListId)) continue;
if (!DictionaryInfoUtils.isMainWordListId(wordListId)) {
continue;
}
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict);
final DictionaryInfo dictionaryInfo =
@ -377,7 +398,9 @@ public class DictionaryInfoUtils {
// Protect against cases of a less-specific dictionary being found, like an
// en dictionary being used for an en_US locale. In this case, the en dictionary
// should be used for en_US but discounted for listing purposes.
if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) continue;
if (dictionaryInfo == null || !dictionaryInfo.mLocale.equals(locale)) {
continue;
}
addOrUpdateDictInfo(dictList, dictionaryInfo);
}
}
@ -391,14 +414,18 @@ public class DictionaryInfoUtils {
final int resourceId =
DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
context.getResources(), locale);
if (0 == resourceId) continue;
if (0 == resourceId) {
continue;
}
final AssetFileAddress fileAddress =
BinaryDictionaryGetter.loadFallbackResource(context, resourceId);
final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress);
// Protect against cases of a less-specific dictionary being found, like an
// en dictionary being used for an en_US locale. In this case, the en dictionary
// should be used for en_US but discounted for listing purposes.
if (!dictionaryInfo.mLocale.equals(locale)) continue;
if (!dictionaryInfo.mLocale.equals(locale)) {
continue;
}
addOrUpdateDictInfo(dictList, dictionaryInfo);
}
@ -408,7 +435,9 @@ public class DictionaryInfoUtils {
@UsedForTesting
public static boolean looksValidForDictionaryInsertion(final CharSequence text,
final SpacingAndPunctuations spacingAndPunctuations) {
if (TextUtils.isEmpty(text)) return false;
if (TextUtils.isEmpty(text)) {
return false;
}
final int length = text.length();
if (length > Constants.DICTIONARY_MAX_WORD_LENGTH) {
return false;
@ -424,7 +453,9 @@ public class DictionaryInfoUtils {
digitCount += charCount;
continue;
}
if (!spacingAndPunctuations.isWordCodePoint(codePoint)) return false;
if (!spacingAndPunctuations.isWordCodePoint(codePoint)) {
return false;
}
}
// We reject strings entirely comprised of digits to avoid using PIN codes or credit
// card numbers. It would come in handy for word prediction though; a good example is

View File

@ -199,8 +199,7 @@ public final class SubtypeLocaleUtils {
if (sExceptionalLocaleDisplayedInRootLocale.containsKey(localeString)) {
languageString = localeString;
} else {
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
languageString = locale.getLanguage();
languageString = LocaleUtils.constructLocaleFromString(localeString).getLanguage();
}
return getSubtypeLocaleDisplayNameInternal(languageString, displayLocale);
}
@ -232,8 +231,8 @@ public final class SubtypeLocaleUtils {
};
displayName = getExceptionalName.runInLocale(sResources, displayLocale);
} else {
final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
displayName = locale.getDisplayName(displayLocale);
displayName = LocaleUtils.constructLocaleFromString(localeString)
.getDisplayName(displayLocale);
}
return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale);
}