Add RunInLocale class to guard locale switching

Bug: 6128216
Change-Id: I8d9c75c773c3de886183b291ada7a3836295839b
main
Tadashi G. Takaoka 2012-04-03 14:28:56 +09:00
parent 78e333594b
commit 16c6f35570
7 changed files with 163 additions and 125 deletions

View File

@ -31,7 +31,7 @@ import com.android.inputmethod.compat.InputTypeCompatUtils;
import com.android.inputmethod.keyboard.KeyboardSet.Params.ElementParams; import com.android.inputmethod.keyboard.KeyboardSet.Params.ElementParams;
import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinIME;
import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.LocaleUtils; import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.StringUtils;
import com.android.inputmethod.latin.XmlParseUtils; import com.android.inputmethod.latin.XmlParseUtils;
@ -66,6 +66,7 @@ public class KeyboardSet {
public static class KeyboardSetException extends RuntimeException { public static class KeyboardSetException extends RuntimeException {
public final KeyboardId mKeyboardId; public final KeyboardId mKeyboardId;
public KeyboardSetException(Throwable cause, KeyboardId keyboardId) { public KeyboardSetException(Throwable cause, KeyboardId keyboardId) {
super(cause); super(cause);
mKeyboardId = keyboardId; mKeyboardId = keyboardId;
@ -161,26 +162,29 @@ public class KeyboardSet {
} }
} }
private Keyboard getKeyboard(Context context, ElementParams elementParams, KeyboardId id) { private Keyboard getKeyboard(Context context, ElementParams elementParams,
final Resources res = context.getResources(); final KeyboardId id) {
final SoftReference<Keyboard> ref = sKeyboardCache.get(id); final SoftReference<Keyboard> ref = sKeyboardCache.get(id);
Keyboard keyboard = (ref == null) ? null : ref.get(); Keyboard keyboard = (ref == null) ? null : ref.get();
if (keyboard == null) { if (keyboard == null) {
final Locale savedLocale = LocaleUtils.setSystemLocale(res, id.mLocale);
try {
final Keyboard.Builder<Keyboard.Params> builder = final Keyboard.Builder<Keyboard.Params> builder =
new Keyboard.Builder<Keyboard.Params>(context, new Keyboard.Params()); new Keyboard.Builder<Keyboard.Params>(mContext, new Keyboard.Params());
if (id.isAlphabetKeyboard()) { if (id.isAlphabetKeyboard()) {
builder.setAutoGenerate(sKeysCache); builder.setAutoGenerate(sKeysCache);
} }
builder.load(elementParams.mKeyboardXmlId, id); final int keyboardXmlId = elementParams.mKeyboardXmlId;
final RunInLocale<Void> job = new RunInLocale<Void>() {
@Override
protected Void job(Resources res) {
builder.load(keyboardXmlId, id);
return null;
}
};
job.runInLocale(context.getResources(), id.mLocale);
builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled); builder.setTouchPositionCorrectionEnabled(mParams.mTouchPositionCorrectionEnabled);
builder.setProximityCharsCorrectionEnabled( builder.setProximityCharsCorrectionEnabled(
elementParams.mProximityCharsCorrectionEnabled); elementParams.mProximityCharsCorrectionEnabled);
keyboard = builder.build(); keyboard = builder.build();
} finally {
LocaleUtils.setSystemLocale(res, savedLocale);
}
sKeyboardCache.put(id, new SoftReference<Keyboard>(keyboard)); sKeyboardCache.put(id, new SoftReference<Keyboard>(keyboard));
if (DEBUG_CACHE) { if (DEBUG_CACHE) {
@ -271,16 +275,20 @@ public class KeyboardSet {
if (mParams.mLocale == null) if (mParams.mLocale == null)
throw new RuntimeException("KeyboardSet subtype is not specified"); throw new RuntimeException("KeyboardSet subtype is not specified");
final Locale savedLocale = LocaleUtils.setSystemLocale(mResources, mParams.mLocale); final RunInLocale<Void> job = new RunInLocale<Void>() {
@Override
protected Void job(Resources res) {
try { try {
parseKeyboardSet(mResources, R.xml.keyboard_set); parseKeyboardSet(res, R.xml.keyboard_set);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e.getMessage() + " in " throw new RuntimeException(e.getMessage() + " in "
+ mResources.getResourceName(R.xml.keyboard_set) + res.getResourceName(R.xml.keyboard_set)
+ " of locale " + mParams.mLocale); + " of locale " + mParams.mLocale);
} finally {
LocaleUtils.setSystemLocale(mResources, savedLocale);
} }
return null;
}
};
job.runInLocale(mResources, mParams.mLocale);
return new KeyboardSet(mContext, mParams); return new KeyboardSet(mContext, mParams);
} }

View File

@ -23,6 +23,8 @@ import android.content.res.AssetFileDescriptor;
import android.content.res.Resources; import android.content.res.Resources;
import android.util.Log; import android.util.Log;
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
@ -154,11 +156,13 @@ class BinaryDictionaryGetter {
*/ */
private static AssetFileAddress loadFallbackResource(final Context context, private static AssetFileAddress loadFallbackResource(final Context context,
final int fallbackResId, final Locale locale) { final int fallbackResId, final Locale locale) {
final Resources res = context.getResources(); final RunInLocale<AssetFileDescriptor> job = new RunInLocale<AssetFileDescriptor>() {
final Locale savedLocale = LocaleUtils.setSystemLocale(res, locale); @Override
final AssetFileDescriptor afd = res.openRawResourceFd(fallbackResId); protected AssetFileDescriptor job(Resources res) {
LocaleUtils.setSystemLocale(res, savedLocale); return res.openRawResourceFd(fallbackResId);
}
};
final AssetFileDescriptor afd = job.runInLocale(context.getResources(), locale);
if (afd == null) { if (afd == null) {
Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId=" Log.e(TAG, "Found the resource but cannot read it. Is it compressed? resId="
+ fallbackResId); + fallbackResId);

View File

@ -21,6 +21,8 @@ import android.content.res.AssetFileDescriptor;
import android.content.res.Resources; import android.content.res.Resources;
import android.util.Log; import android.util.Log;
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
@ -30,7 +32,6 @@ import java.util.Locale;
* Factory for dictionary instances. * Factory for dictionary instances.
*/ */
public class DictionaryFactory { public class DictionaryFactory {
private static String TAG = DictionaryFactory.class.getSimpleName(); private static String TAG = DictionaryFactory.class.getSimpleName();
/** /**
@ -98,14 +99,13 @@ public class DictionaryFactory {
final int resId, final Locale locale) { final int resId, final Locale locale) {
AssetFileDescriptor afd = null; AssetFileDescriptor afd = null;
try { try {
final Resources res = context.getResources(); final RunInLocale<AssetFileDescriptor> job = new RunInLocale<AssetFileDescriptor>() {
if (null != locale) { @Override
final Locale savedLocale = LocaleUtils.setSystemLocale(res, locale); protected AssetFileDescriptor job(Resources res) {
afd = res.openRawResourceFd(resId); return res.openRawResourceFd(resId);
LocaleUtils.setSystemLocale(res, savedLocale);
} else {
afd = res.openRawResourceFd(resId);
} }
};
afd = job.runInLocale(context.getResources(), locale);
if (afd == null) { if (afd == null) {
Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
return null; return null;
@ -161,9 +161,9 @@ public class DictionaryFactory {
* @return whether a (non-placeholder) dictionary is available or not. * @return whether a (non-placeholder) dictionary is available or not.
*/ */
public static boolean isDictionaryAvailable(Context context, Locale locale) { public static boolean isDictionaryAvailable(Context context, Locale locale) {
final Resources res = context.getResources(); final RunInLocale<Boolean> job = new RunInLocale<Boolean>() {
final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale); @Override
protected Boolean job(Resources res) {
final int resourceId = getMainDictionaryResourceId(res); final int resourceId = getMainDictionaryResourceId(res);
final AssetFileDescriptor afd = res.openRawResourceFd(resourceId); final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
final boolean hasDictionary = isFullDictionary(afd); final boolean hasDictionary = isFullDictionary(afd);
@ -172,16 +172,17 @@ public class DictionaryFactory {
} catch (java.io.IOException e) { } catch (java.io.IOException e) {
/* Um, what can we do here exactly? */ /* Um, what can we do here exactly? */
} }
LocaleUtils.setSystemLocale(res, saveLocale);
return hasDictionary; return hasDictionary;
} }
};
return job.runInLocale(context.getResources(), locale);
}
// TODO: Do not use the size of the dictionary as an unique dictionary ID. // TODO: Do not use the size of the dictionary as an unique dictionary ID.
public static Long getDictionaryId(final Context context, final Locale locale) { public static Long getDictionaryId(final Context context, final Locale locale) {
final Resources res = context.getResources(); final RunInLocale<Long> job = new RunInLocale<Long>() {
final Locale saveLocale = LocaleUtils.setSystemLocale(res, locale); @Override
protected Long job(Resources res) {
final int resourceId = getMainDictionaryResourceId(res); final int resourceId = getMainDictionaryResourceId(res);
final AssetFileDescriptor afd = res.openRawResourceFd(resourceId); final AssetFileDescriptor afd = res.openRawResourceFd(resourceId);
final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH) final Long size = (afd != null && afd.getLength() > PLACEHOLDER_LENGTH)
@ -191,10 +192,11 @@ public class DictionaryFactory {
if (null != afd) afd.close(); if (null != afd) afd.close();
} catch (java.io.IOException e) { } catch (java.io.IOException e) {
} }
LocaleUtils.setSystemLocale(res, saveLocale);
return size; return size;
} }
};
return job.runInLocale(context.getResources(), locale);
}
// TODO: Find the Right Way to find out whether the resource is a placeholder or not. // TODO: Find the Right Way to find out whether the resource is a placeholder or not.
// Suggestion : strip the locale, open the placeholder file and store its offset. // Suggestion : strip the locale, open the placeholder file and store its offset.

View File

@ -64,6 +64,7 @@ import com.android.inputmethod.keyboard.KeyboardId;
import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.KeyboardView;
import com.android.inputmethod.keyboard.LatinKeyboardView; import com.android.inputmethod.keyboard.LatinKeyboardView;
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.define.ProductionFlag;
import com.android.inputmethod.latin.suggestions.SuggestionsView; import com.android.inputmethod.latin.suggestions.SuggestionsView;
@ -478,7 +479,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
// Has to be package-visible for unit tests // Has to be package-visible for unit tests
/* package */ void loadSettings() { /* package */ void loadSettings() {
if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this); if (null == mPrefs) mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mSettingsValues = new SettingsValues(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr()); final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
@Override
protected SettingsValues job(Resources res) {
return new SettingsValues(mPrefs, LatinIME.this);
}
};
mSettingsValues = job.runInLocale(mResources, mSubtypeSwitcher.getInputLocale());
mFeedbackManager = new AudioAndHapticFeedbackManager(this, mSettingsValues); mFeedbackManager = new AudioAndHapticFeedbackManager(this, mSettingsValues);
resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary());
} }
@ -487,8 +494,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); final String localeStr = mSubtypeSwitcher.getInputLocaleStr();
final Locale keyboardLocale = mSubtypeSwitcher.getInputLocale(); final Locale keyboardLocale = mSubtypeSwitcher.getInputLocale();
final Resources res = mResources; final Context context = this;
final Locale savedLocale = LocaleUtils.setSystemLocale(res, keyboardLocale); final RunInLocale<Void> job = new RunInLocale<Void>() {
@Override
protected Void job(Resources res) {
final ContactsDictionary oldContactsDictionary; final ContactsDictionary oldContactsDictionary;
if (mSuggest != null) { if (mSuggest != null) {
oldContactsDictionary = mSuggest.getContactsDictionary(); oldContactsDictionary = mSuggest.getContactsDictionary();
@ -498,22 +507,24 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
} }
int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(res); int mainDicResId = DictionaryFactory.getMainDictionaryResourceId(res);
mSuggest = new Suggest(this, mainDicResId, keyboardLocale); mSuggest = new Suggest(context, mainDicResId, keyboardLocale);
if (mSettingsValues.mAutoCorrectEnabled) { if (mSettingsValues.mAutoCorrectEnabled) {
mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold); mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold);
} }
mUserDictionary = new UserDictionary(this, localeStr); mUserDictionary = new UserDictionary(context, localeStr);
mSuggest.setUserDictionary(mUserDictionary); mSuggest.setUserDictionary(mUserDictionary);
mIsUserDictionaryAvailable = mUserDictionary.isEnabled(); mIsUserDictionaryAvailable = mUserDictionary.isEnabled();
resetContactsDictionary(oldContactsDictionary); resetContactsDictionary(oldContactsDictionary);
mUserHistoryDictionary mUserHistoryDictionary
= new UserHistoryDictionary(this, localeStr, Suggest.DIC_USER_HISTORY); = new UserHistoryDictionary(context, localeStr, Suggest.DIC_USER_HISTORY);
mSuggest.setUserHistoryDictionary(mUserHistoryDictionary); mSuggest.setUserHistoryDictionary(mUserHistoryDictionary);
return null;
LocaleUtils.setSystemLocale(res, savedLocale); }
};
job.runInLocale(mResources, keyboardLocale);
} }
/** /**

View File

@ -161,21 +161,36 @@ public class LocaleUtils {
return LOCALE_MATCH <= level; return LOCALE_MATCH <= level;
} }
static final Object sLockForRunInLocale = new Object();
public abstract static class RunInLocale<T> {
protected abstract T job(Resources res);
/** /**
* Sets the system locale for this process. * Execute {@link #job(Resources)} method in specified system locale exclusively.
* *
* @param res the resources to use. Pass current resources. * @param res the resources to use. Pass current resources.
* @param newLocale the locale to change to. * @param newLocale the locale to change to
* @return the old locale. * @return the value returned from {@link #job(Resources)}.
*/ */
public static synchronized Locale setSystemLocale(final Resources res, final Locale newLocale) { public T runInLocale(final Resources res, final Locale newLocale) {
synchronized (sLockForRunInLocale) {
final Configuration conf = res.getConfiguration(); final Configuration conf = res.getConfiguration();
final Locale oldLocale = conf.locale; final Locale oldLocale = conf.locale;
try {
if (newLocale != null && !newLocale.equals(oldLocale)) { if (newLocale != null && !newLocale.equals(oldLocale)) {
conf.locale = newLocale; conf.locale = newLocale;
res.updateConfiguration(conf, res.getDisplayMetrics()); res.updateConfiguration(conf, res.getDisplayMetrics());
} }
return oldLocale; return job(res);
} finally {
if (newLocale != null && !newLocale.equals(oldLocale)) {
conf.locale = oldLocale;
res.updateConfiguration(conf, res.getDisplayMetrics());
}
}
}
}
} }
private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>(); private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();

View File

@ -25,12 +25,14 @@ import android.view.inputmethod.EditorInfo;
import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.compat.InputTypeCompatUtils;
import com.android.inputmethod.keyboard.internal.KeySpecParser; import com.android.inputmethod.keyboard.internal.KeySpecParser;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.VibratorUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale;
/**
* When you call the constructor of this class, you may want to change the current system locale by
* using {@link LocaleUtils.RunInLocale}.
*/
public class SettingsValues { public class SettingsValues {
private static final String TAG = SettingsValues.class.getSimpleName(); private static final String TAG = SettingsValues.class.getSimpleName();
@ -78,16 +80,8 @@ public class SettingsValues {
private final boolean mVoiceKeyEnabled; private final boolean mVoiceKeyEnabled;
private final boolean mVoiceKeyOnMain; private final boolean mVoiceKeyOnMain;
public SettingsValues(final SharedPreferences prefs, final Context context, public SettingsValues(final SharedPreferences prefs, final Context context) {
final String localeStr) {
final Resources res = context.getResources(); final Resources res = context.getResources();
final Locale savedLocale;
if (null != localeStr) {
final Locale keyboardLocale = LocaleUtils.constructLocaleFromString(localeStr);
savedLocale = LocaleUtils.setSystemLocale(res, keyboardLocale);
} else {
savedLocale = null;
}
// Get the resources // Get the resources
mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions); mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions);
@ -152,8 +146,6 @@ public class SettingsValues {
mAutoCorrectionThresholdRawValue); mAutoCorrectionThresholdRawValue);
mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff); mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff);
mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain); mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain);
LocaleUtils.setSystemLocale(res, savedLocale);
} }
// Helper functions to create member values. // Helper functions to create member values.

View File

@ -22,6 +22,8 @@ import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.android.inputmethod.latin.LocaleUtils.RunInLocale;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
@ -36,10 +38,14 @@ public class WhitelistDictionary extends ExpandableDictionary {
// TODO: Conform to the async load contact of ExpandableDictionary // TODO: Conform to the async load contact of ExpandableDictionary
public WhitelistDictionary(final Context context, final Locale locale) { public WhitelistDictionary(final Context context, final Locale locale) {
super(context, Suggest.DIC_WHITELIST); super(context, Suggest.DIC_WHITELIST);
final Resources res = context.getResources(); final RunInLocale<Void> job = new RunInLocale<Void>() {
final Locale previousLocale = LocaleUtils.setSystemLocale(res, locale); @Override
protected Void job(Resources res) {
initWordlist(res.getStringArray(R.array.wordlist_whitelist)); initWordlist(res.getStringArray(R.array.wordlist_whitelist));
LocaleUtils.setSystemLocale(res, previousLocale); return null;
}
};
job.runInLocale(context.getResources(), locale);
} }
private void initWordlist(String[] wordlist) { private void initWordlist(String[] wordlist) {