diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
10 files changed, 360 insertions, 503 deletions
diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java index 28cec56e6..458d9ee14 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java @@ -22,27 +22,8 @@ import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOAR import android.view.inputmethod.InputMethodSubtype; -import java.util.HashMap; public class AdditionalSubtype { - public static final String QWERTY = "qwerty"; - public static final String QWERTZ = "qwertz"; - public static final String AZERTY = "azerty"; - public static final String[] PREDEFINED_KEYBOARD_LAYOUT_SET = { - QWERTY, - QWERTZ, - AZERTY - }; - - // Keyboard layout to subtype name resource id map. - private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = - new HashMap<String, Integer>(); - - static { - sKeyboardLayoutToNameIdsMap.put(QWERTY, R.string.subtype_generic_qwerty); - sKeyboardLayoutToNameIdsMap.put(QWERTZ, R.string.subtype_generic_qwertz); - sKeyboardLayoutToNameIdsMap.put(AZERTY, R.string.subtype_generic_azerty); - } private AdditionalSubtype() { // This utility class is not publicly instantiable. @@ -60,7 +41,8 @@ public class AdditionalSubtype { final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName; final String filteredExtraValue = StringUtils.appendToCsvIfNotExists( IS_ADDITIONAL_SUBTYPE, extraValue); - Integer nameId = sKeyboardLayoutToNameIdsMap.get(keyboardLayoutSetName); + Integer nameId = SubtypeLocale.getSubtypeNameIdFromKeyboardLayoutName( + keyboardLayoutSetName); if (nameId == null) nameId = R.string.subtype_generic; return new InputMethodSubtype(nameId, R.drawable.ic_subtype_keyboard, localeString, KEYBOARD_MODE, diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java index 7a22c9742..613c20304 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java @@ -109,18 +109,15 @@ public class AdditionalSubtypeSettings extends PreferenceFragment { } static class KeyboardLayoutSetItem extends Pair<String, String> { - public KeyboardLayoutSetItem(String keyboardLayoutSetName) { - super(keyboardLayoutSetName, getDisplayName(keyboardLayoutSetName)); + public KeyboardLayoutSetItem(InputMethodSubtype subtype) { + super(SubtypeLocale.getKeyboardLayoutSetName(subtype), + SubtypeLocale.getKeyboardLayoutSetDisplayName(subtype)); } @Override public String toString() { return second; } - - private static String getDisplayName(String keyboardLayoutSetName) { - return keyboardLayoutSetName.toUpperCase(); - } } static class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> { @@ -129,8 +126,11 @@ public class AdditionalSubtypeSettings extends PreferenceFragment { setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // TODO: Should filter out already existing combinations of locale and layout. - for (final String layout : AdditionalSubtype.PREDEFINED_KEYBOARD_LAYOUT_SET) { - add(new KeyboardLayoutSetItem(layout)); + for (final String layout : SubtypeLocale.getPredefinedKeyboardLayoutSet()) { + // This is a dummy subtype with NO_LANGUAGE, only for display. + final InputMethodSubtype subtype = AdditionalSubtype.createAdditionalSubtype( + SubtypeLocale.NO_LANGUAGE, layout, null); + add(new KeyboardLayoutSetItem(subtype)); } } } @@ -196,8 +196,7 @@ public class AdditionalSubtypeSettings extends PreferenceFragment { .setNegativeButton(R.string.remove, this); final SubtypeLocaleItem localeItem = SubtypeLocaleAdapter.createItem( context, mSubtype.getLocale()); - final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem( - SubtypeLocale.getKeyboardLayoutSetName(mSubtype)); + final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype); setSpinnerPosition(mSubtypeLocaleSpinner, localeItem); setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem); } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index e4d081b56..a4670daf2 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -49,7 +49,10 @@ public class BinaryDictionaryFileDumper { */ private static final int FILE_READ_BUFFER_SIZE = 1024; // TODO: make the following data common with the native code - private static final byte[] MAGIC_NUMBER = new byte[] { 0x78, (byte)0xB1 }; + private static final byte[] MAGIC_NUMBER_VERSION_1 = + new byte[] { (byte)0x78, (byte)0xB1, (byte)0x00, (byte)0x00 }; + private static final byte[] MAGIC_NUMBER_VERSION_2 = + new byte[] { (byte)0x9B, (byte)0xC1, (byte)0x3A, (byte)0xFE }; private static final String DICTIONARY_PROJECTION[] = { "id" }; @@ -268,15 +271,18 @@ public class BinaryDictionaryFileDumper { private static void checkMagicAndCopyFileTo(final BufferedInputStream input, final FileOutputStream output) throws FileNotFoundException, IOException { // Check the magic number - final byte[] magicNumberBuffer = new byte[MAGIC_NUMBER.length]; - final int readMagicNumberSize = input.read(magicNumberBuffer, 0, MAGIC_NUMBER.length); - if (readMagicNumberSize < MAGIC_NUMBER.length) { + final int length = MAGIC_NUMBER_VERSION_2.length; + final byte[] magicNumberBuffer = new byte[length]; + final int readMagicNumberSize = input.read(magicNumberBuffer, 0, length); + if (readMagicNumberSize < length) { throw new IOException("Less bytes to read than the magic number length"); } - if (!Arrays.equals(MAGIC_NUMBER, magicNumberBuffer)) { - throw new IOException("Wrong magic number for downloaded file"); + if (!Arrays.equals(MAGIC_NUMBER_VERSION_2, magicNumberBuffer)) { + if (!Arrays.equals(MAGIC_NUMBER_VERSION_1, magicNumberBuffer)) { + throw new IOException("Wrong magic number for downloaded file"); + } } - output.write(MAGIC_NUMBER); + output.write(magicNumberBuffer); // Actually copy the file final byte[] buffer = new byte[FILE_READ_BUFFER_SIZE]; diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 072dec9d1..5acd62904 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -24,6 +24,7 @@ import android.util.Log; import java.io.File; import java.util.ArrayList; +import java.util.HashMap; import java.util.Locale; /** @@ -46,6 +47,10 @@ class BinaryDictionaryGetter { */ private static final String COMMON_PREFERENCES_NAME = "LatinImeDictPrefs"; + // Name of the category for the main dictionary + private static final String MAIN_DICTIONARY_CATEGORY = "main"; + public static final String ID_CATEGORY_SEPARATOR = ":"; + // Prevents this from being instantiated private BinaryDictionaryGetter() {} @@ -206,7 +211,40 @@ class BinaryDictionaryGetter { } /** - * Returns the list of cached files for a specific locale. + * Returns the category for a given file name. + * + * This parses the file name, extracts the category, and returns it. See + * {@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. + */ + private static String getCategoryFromFileName(final String fileName) { + final String id = getWordListIdFromFileName(fileName); + final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); + if (2 != idArray.length) return null; + return idArray[0]; + } + + /** + * Utility class for the {@link #getCachedWordLists} method + */ + private static class FileAndMatchLevel { + final File mFile; + final int mMatchLevel; + public FileAndMatchLevel(final File file, final int matchLevel) { + mFile = file; + mMatchLevel = matchLevel; + } + } + + /** + * Returns the list of cached files for a specific locale, one for each category. + * + * This will return exactly one file for each word list category that matches + * the passed locale. If several files match the locale for any given category, + * this returns the file with the closest match to the locale. For example, if + * the passed word list is en_US, and for a category we have an en and an en_US + * word list available, we'll return only the en_US one. + * Thus, the list will contain as many files as there are categories. * * @param locale the locale to find the dictionary files for, as a string. * @param context the context on which to open the files upon. @@ -216,21 +254,32 @@ class BinaryDictionaryGetter { final Context context) { final File[] directoryList = getCachedDirectoryList(context); if (null == directoryList) return EMPTY_FILE_ARRAY; - final ArrayList<File> cacheFiles = new ArrayList<File>(); + final HashMap<String, FileAndMatchLevel> cacheFiles = + new HashMap<String, FileAndMatchLevel>(); for (File directory : directoryList) { if (!directory.isDirectory()) continue; final String dirLocale = getWordListIdFromFileName(directory.getName()); - if (LocaleUtils.isMatch(LocaleUtils.getMatchLevel(dirLocale, locale))) { + final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale); + if (LocaleUtils.isMatch(matchLevel)) { final File[] wordLists = directory.listFiles(); if (null != wordLists) { for (File wordList : wordLists) { - cacheFiles.add(wordList); + final String category = getCategoryFromFileName(wordList.getName()); + final FileAndMatchLevel currentBestMatch = cacheFiles.get(category); + if (null == currentBestMatch || currentBestMatch.mMatchLevel < matchLevel) { + cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel)); + } } } } } if (cacheFiles.isEmpty()) return EMPTY_FILE_ARRAY; - return cacheFiles.toArray(EMPTY_FILE_ARRAY); + final File[] result = new File[cacheFiles.size()]; + int index = 0; + for (final FileAndMatchLevel entry : cacheFiles.values()) { + result[index++] = entry.mFile; + } + return result; } /** @@ -245,7 +294,13 @@ class BinaryDictionaryGetter { // This works because we don't include by default different dictionaries for // different countries. This actually needs to return the id that we would // like to use for word lists included in resources, and the following is okay. - return locale.getLanguage().toString(); + return MAIN_DICTIONARY_CATEGORY + ID_CATEGORY_SEPARATOR + locale.getLanguage().toString(); + } + + private static boolean isMainWordListId(final String id) { + final String[] idArray = id.split(ID_CATEGORY_SEPARATOR); + if (2 != idArray.length) return false; + return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); } /** @@ -270,9 +325,7 @@ class BinaryDictionaryGetter { BinaryDictionaryFileDumper.cacheWordListsFromContentProvider(locale, context, hasDefaultWordList); final File[] cachedWordLists = getCachedWordLists(locale.toString(), context); - final String mainDictId = getMainDictId(locale); - final DictPackSettings dictPackSettings = new DictPackSettings(context); boolean foundMainDict = false; @@ -280,7 +333,7 @@ class BinaryDictionaryGetter { // cachedWordLists may not be null, see doc for getCachedDictionaryList for (final File f : cachedWordLists) { final String wordListId = getWordListIdFromFileName(f.getName()); - if (wordListId.equals(mainDictId)) { + if (isMainWordListId(wordListId)) { foundMainDict = true; } if (!dictPackSettings.isWordListActive(wordListId)) continue; diff --git a/java/src/com/android/inputmethod/latin/EditingUtils.java b/java/src/com/android/inputmethod/latin/EditingUtils.java index b3f613bae..7d673175e 100644 --- a/java/src/com/android/inputmethod/latin/EditingUtils.java +++ b/java/src/com/android/inputmethod/latin/EditingUtils.java @@ -16,7 +16,6 @@ package com.android.inputmethod.latin; -import android.text.TextUtils; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; @@ -37,30 +36,6 @@ public class EditingUtils { // Unintentional empty constructor for singleton. } - /** - * Append newText to the text field represented by connection. - * The new text becomes selected. - */ - public static void appendText(InputConnection connection, String newText) { - if (connection == null) { - return; - } - - // Commit the composing text - connection.finishComposingText(); - - // Add a space if the field already has text. - String text = newText; - CharSequence charBeforeCursor = connection.getTextBeforeCursor(1, 0); - if (charBeforeCursor != null - && !charBeforeCursor.equals(" ") - && (charBeforeCursor.length() > 0)) { - text = " " + text; - } - - connection.setComposingText(text, 1); - } - private static int getCursorPosition(InputConnection connection) { if (null == connection) return INVALID_CURSOR_POSITION; ExtractedText extracted = connection.getExtractedText( @@ -146,7 +121,6 @@ public class EditingUtils { private static final Pattern spaceRegex = Pattern.compile("\\s+"); - public static CharSequence getPreviousWord(InputConnection connection, String sentenceSeperators) { //TODO: Should fix this. This could be slow! @@ -205,77 +179,4 @@ public class EditingUtils { return w[w.length - 1]; } - - public static class SelectedWord { - public final int mStart; - public final int mEnd; - public final CharSequence mWord; - - public SelectedWord(int start, int end, CharSequence word) { - mStart = start; - mEnd = end; - mWord = word; - } - } - - /** - * Takes a character sequence with a single character and checks if the character occurs - * in a list of word separators or is empty. - * @param singleChar A CharSequence with null, zero or one character - * @param wordSeparators A String containing the word separators - * @return true if the character is at a word boundary, false otherwise - */ - private static boolean isWordBoundary(CharSequence singleChar, String wordSeparators) { - return TextUtils.isEmpty(singleChar) || wordSeparators.contains(singleChar); - } - - /** - * Checks if the cursor is inside a word or the current selection is a whole word. - * @param ic the InputConnection for accessing the text field - * @param selStart the start position of the selection within the text field - * @param selEnd the end position of the selection within the text field. This could be - * the same as selStart, if there's no selection. - * @param wordSeparators the word separator characters for the current language - * @return an object containing the text and coordinates of the selected/touching word, - * null if the selection/cursor is not marking a whole word. - */ - public static SelectedWord getWordAtCursorOrSelection(final InputConnection ic, - int selStart, int selEnd, String wordSeparators) { - if (selStart == selEnd) { - // There is just a cursor, so get the word at the cursor - // getWordRangeAtCursor returns null if the connection is null - final EditingUtils.Range range = getWordRangeAtCursor(ic, wordSeparators); - if (range != null && !TextUtils.isEmpty(range.mWord)) { - return new SelectedWord(selStart - range.mCharsBefore, selEnd + range.mCharsAfter, - range.mWord); - } - } else { - if (null == ic) return null; - // Is the previous character empty or a word separator? If not, return null. - final CharSequence charsBefore = ic.getTextBeforeCursor(1, 0); - if (!isWordBoundary(charsBefore, wordSeparators)) { - return null; - } - - // Is the next character empty or a word separator? If not, return null. - final CharSequence charsAfter = ic.getTextAfterCursor(1, 0); - if (!isWordBoundary(charsAfter, wordSeparators)) { - return null; - } - - // Extract the selection alone - final CharSequence touching = ic.getSelectedText(0); - if (TextUtils.isEmpty(touching)) return null; - // Is any part of the selection a separator? If so, return null. - final int length = touching.length(); - for (int i = 0; i < length; i++) { - if (wordSeparators.contains(touching.subSequence(i, i + 1))) { - return null; - } - } - // Prepare the selected word - return new SelectedWord(selStart, selEnd, touching); - } - return null; - } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 31c832c31..e1978fca1 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -154,6 +154,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private final SubtypeSwitcher mSubtypeSwitcher; private boolean mShouldSwitchToLastSubtype = true; + private boolean mIsMainDictionaryAvailable; private UserDictionary mUserDictionary; private UserHistoryDictionary mUserHistoryDictionary; private boolean mIsUserDictionaryAvailable; @@ -449,14 +450,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return new SettingsValues(mPrefs, LatinIME.this); } }; - mSettingsValues = job.runInLocale(mResources, mSubtypeSwitcher.getInputLocale()); + mSettingsValues = job.runInLocale(mResources, mSubtypeSwitcher.getCurrentSubtypeLocale()); mFeedbackManager = new AudioAndHapticFeedbackManager(this, mSettingsValues); resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); } private void initSuggest() { - final String localeStr = mSubtypeSwitcher.getInputLocaleStr(); - final Locale keyboardLocale = mSubtypeSwitcher.getInputLocale(); + final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); + final String localeStr = subtypeLocale.toString(); final Dictionary oldContactsDictionary; if (mSuggest != null) { @@ -465,11 +466,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { oldContactsDictionary = null; } - mSuggest = new Suggest(this, keyboardLocale); + mSuggest = new Suggest(this, subtypeLocale); if (mSettingsValues.mAutoCorrectEnabled) { mSuggest.setAutoCorrectionThreshold(mSettingsValues.mAutoCorrectionThreshold); } + mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale); + mUserDictionary = new UserDictionary(this, localeStr); mSuggest.setUserDictionary(mUserDictionary); mIsUserDictionaryAvailable = mUserDictionary.isEnabled(); @@ -511,7 +514,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { if (USE_BINARY_CONTACTS_DICTIONARY) { dictionaryToUse = new ContactsBinaryDictionary(this, Suggest.DIC_CONTACTS, - mSubtypeSwitcher.getInputLocale()); + mSubtypeSwitcher.getCurrentSubtypeLocale()); } else { dictionaryToUse = new ContactsDictionary(this, Suggest.DIC_CONTACTS); } @@ -523,7 +526,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } /* package private */ void resetSuggestMainDict() { - mSuggest.resetMainDict(this, mSubtypeSwitcher.getInputLocale()); + final Locale subtypeLocale = mSubtypeSwitcher.getCurrentSubtypeLocale(); + mSuggest.resetMainDict(this, subtypeLocale); + mIsMainDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(this, subtypeLocale); } @Override @@ -603,7 +608,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onCurrentInputMethodSubtypeChanged(InputMethodSubtype subtype) { - SubtypeSwitcher.getInstance().updateSubtype(subtype); + mSubtypeSwitcher.updateSubtype(subtype); } private void onStartInputInternal(EditorInfo editorInfo, boolean restarting) { @@ -629,7 +634,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen editorInfo.inputType, editorInfo.imeOptions)); } if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.latinIME_onStartInputViewInternal(editorInfo); + ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, mPrefs); } if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) { Log.w(TAG, "Deprecated private IME option specified: " @@ -1843,6 +1848,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mExpectingUpdateSelection = true; commitChosenWord(suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR); + // Don't allow cancellation of manual pick + mLastComposedWord.deactivate(); // Add the word to the user history dictionary addToUserHistoryDictionary(suggestion); mSpaceState = SPACE_STATE_PHANTOM; @@ -1894,7 +1901,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mSettingsValues.mEnableSuggestionSpanInsertion) { final SuggestedWords suggestedWords = mSuggestionsView.getSuggestions(); ic.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( - this, bestWord, suggestedWords, mSubtypeSwitcher.isDictionaryAvailable()), + this, bestWord, suggestedWords, mIsMainDictionaryAvailable), 1); if (ProductionFlag.IS_EXPERIMENTAL) { ResearchLogger.latinIME_commitText(bestWord); @@ -1972,7 +1979,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } final String secondWord; if (mWordComposer.isAutoCapitalized() && !mWordComposer.isMostlyCaps()) { - secondWord = suggestion.toString().toLowerCase(mSubtypeSwitcher.getInputLocale()); + secondWord = suggestion.toString().toLowerCase( + mSubtypeSwitcher.getCurrentSubtypeLocale()); } else { secondWord = suggestion.toString(); } @@ -2300,8 +2308,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void showOptionDialogInternal(AlertDialog dialog) { - final IBinder windowToken = KeyboardSwitcher.getInstance().getKeyboardView() - .getWindowToken(); + final IBinder windowToken = mKeyboardSwitcher.getKeyboardView().getWindowToken(); if (windowToken == null) return; dialog.setCancelable(true); diff --git a/java/src/com/android/inputmethod/latin/ResearchLogger.java b/java/src/com/android/inputmethod/latin/ResearchLogger.java index 7072dda23..566af7061 100644 --- a/java/src/com/android/inputmethod/latin/ResearchLogger.java +++ b/java/src/com/android/inputmethod/latin/ResearchLogger.java @@ -18,10 +18,12 @@ package com.android.inputmethod.latin; import android.content.SharedPreferences; import android.inputmethodservice.InputMethodService; +import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Log; import android.view.MotionEvent; @@ -31,7 +33,6 @@ import android.view.inputmethod.EditorInfo; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.internal.AlphabetShiftState; import com.android.inputmethod.keyboard.internal.KeyboardState; import com.android.inputmethod.latin.define.ProductionFlag; @@ -45,6 +46,7 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; +import java.util.Map; /** * Logs the use of the LatinIME keyboard. @@ -68,7 +70,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang * Isolates management of files. This variable should never be null, but can be changed * to support testing. */ - private LogFileManager mLogFileManager; + /* package */ LogFileManager mLogFileManager; /** * Manages the file(s) that stores the logs. @@ -93,63 +95,53 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mIms = ims; } - public synchronized boolean createLogFile() { - try { - return createLogFile(DEFAULT_FILENAME); - } catch (IOException e) { - e.printStackTrace(); - Log.w(TAG, e); - return false; - } + public synchronized void createLogFile() throws IOException { + createLogFile(DEFAULT_FILENAME); } - public synchronized boolean createLogFile(final SharedPreferences prefs) { - try { - final String filename = - prefs.getString(RESEARCH_LOG_FILENAME_KEY, DEFAULT_FILENAME); - return createLogFile(filename); - } catch (IOException e) { - Log.w(TAG, e); - e.printStackTrace(); - } - return false; + public synchronized void createLogFile(final SharedPreferences prefs) + throws IOException { + final String filename = + prefs.getString(RESEARCH_LOG_FILENAME_KEY, DEFAULT_FILENAME); + createLogFile(filename); } - public synchronized boolean createLogFile(final String filename) + public synchronized void createLogFile(final String filename) throws IOException { if (mIms == null) { - Log.w(TAG, "InputMethodService is not configured. Logging is off."); - return false; + final String msg = "InputMethodService is not configured. Logging is off."; + Log.w(TAG, msg); + throw new IOException(msg); } final File filesDir = mIms.getFilesDir(); if (filesDir == null || !filesDir.exists()) { - Log.w(TAG, "Storage directory does not exist. Logging is off."); - return false; + final String msg = "Storage directory does not exist. Logging is off."; + Log.w(TAG, msg); + throw new IOException(msg); } close(); final File file = new File(filesDir, filename); mFile = file; - file.setReadable(false, false); boolean append = true; if (file.exists() && file.lastModified() + LOGFILE_PURGE_INTERVAL < System.currentTimeMillis()) { append = false; } mPrintWriter = new PrintWriter(new BufferedWriter(new FileWriter(file, append)), true); - return true; } public synchronized boolean append(final String s) { - final PrintWriter printWriter = mPrintWriter; - if (printWriter == null) { + PrintWriter printWriter = mPrintWriter; + if (printWriter == null || !mFile.exists()) { if (DEBUG) { Log.w(TAG, "PrintWriter is null... attempting to create default log file"); } - if (!createLogFile()) { - if (DEBUG) { - Log.w(TAG, "Failed to create log file. Not logging."); - return false; - } + try { + createLogFile(); + printWriter = mPrintWriter; + } catch (IOException e) { + Log.w(TAG, "Failed to create log file. Not logging."); + return false; } } printWriter.print(s); @@ -161,9 +153,15 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (mPrintWriter != null) { mPrintWriter.close(); mPrintWriter = null; + if (DEBUG) { + Log.d(TAG, "logfile closed"); + } } if (mFile != null) { mFile.delete(); + if (DEBUG) { + Log.d(TAG, "logfile deleted"); + } mFile = null; } } @@ -173,6 +171,9 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang mPrintWriter.close(); mPrintWriter = null; mFile = null; + if (DEBUG) { + Log.d(TAG, "logfile closed"); + } } } @@ -240,12 +241,16 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang sInstance.initInternal(ims, prefs); } - public void initInternal(final InputMethodService ims, final SharedPreferences prefs) { + /* package */ void initInternal(final InputMethodService ims, final SharedPreferences prefs) { mIms = ims; final LogFileManager logFileManager = mLogFileManager; if (logFileManager != null) { logFileManager.init(ims); - logFileManager.createLogFile(prefs); + try { + logFileManager.createLogFile(prefs); + } catch (IOException e) { + e.printStackTrace(); + } } if (prefs != null) { sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); @@ -254,19 +259,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } /** - * Change to a different logFileManager. - * - * @throws IllegalArgumentException if logFileManager is null - */ - void setLogFileManager(final LogFileManager manager) { - if (manager == null) { - throw new IllegalArgumentException("warning: trying to set null logFileManager"); - } else { - mLogFileManager = manager; - } - } - - /** * Represents a category of logging events that share the same subfield structure. */ private static enum LogGroup { @@ -334,26 +326,11 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang public static class UnsLogGroup { private static final boolean DEFAULT_ENABLED = true; - private static final boolean ALPHABETSHIFTSTATE_SETSHIFTED_ENABLED = DEFAULT_ENABLED; - private static final boolean ALPHABETSHIFTSTATE_SETSHIFTLOCKED_ENABLED = DEFAULT_ENABLED; - private static final boolean ALPHABETSHIFTSTATE_SETAUTOMATICSHIFTED_ENABLED - = DEFAULT_ENABLED; private static final boolean KEYBOARDSTATE_ONCANCELINPUT_ENABLED = DEFAULT_ENABLED; private static final boolean KEYBOARDSTATE_ONCODEINPUT_ENABLED = DEFAULT_ENABLED; private static final boolean KEYBOARDSTATE_ONLONGPRESSTIMEOUT_ENABLED = DEFAULT_ENABLED; private static final boolean KEYBOARDSTATE_ONPRESSKEY_ENABLED = DEFAULT_ENABLED; private static final boolean KEYBOARDSTATE_ONRELEASEKEY_ENABLED = DEFAULT_ENABLED; - private static final boolean KEYBOARDSTATE_ONRESTOREKEYBOARDSTATE_ENABLED = DEFAULT_ENABLED; - private static final boolean KEYBOARDSTATE_ONSAVEKEYBOARDSTATE_ENABLED = DEFAULT_ENABLED; - private static final boolean KEYBOARDSTATE_ONUPDATESHIFTSTATE_ENABLED = DEFAULT_ENABLED; - private static final boolean KEYBOARDSTATE_SETALPHABETKEYBOARD_ENABLED = DEFAULT_ENABLED; - private static final boolean KEYBOARDSTATE_SETSHIFTED_ENABLED = DEFAULT_ENABLED; - private static final boolean KEYBOARDSTATE_SETSHIFTLOCKED_ENABLED = DEFAULT_ENABLED; - private static final boolean KEYBOARDSTATE_SETSYMBOLSKEYBOARD_ENABLED = DEFAULT_ENABLED; - private static final boolean KEYBOARDSTATE_SETSYMBOLSSHIFTEDKEYBOARD_ENABLED - = DEFAULT_ENABLED; - private static final boolean KEYBOARDSTATE_TOGGLEALPHABETANDSYMBOLS_ENABLED - = DEFAULT_ENABLED; private static final boolean LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_COMMITTEXT_ENABLED = DEFAULT_ENABLED; private static final boolean LATINIME_DELETESURROUNDINGTEXT_ENABLED = DEFAULT_ENABLED; @@ -377,6 +354,7 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang private static final boolean LATINKEYBOARDVIEW_ONLONGPRESS_ENABLED = DEFAULT_ENABLED; private static final boolean LATINKEYBOARDVIEW_ONPROCESSMOTIONEVENT_ENABLED = DEFAULT_ENABLED; + private static final boolean LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED = DEFAULT_ENABLED; private static final boolean POINTERTRACKER_CALLLISTENERONCANCELINPUT_ENABLED = DEFAULT_ENABLED; private static final boolean POINTERTRACKER_CALLLISTENERONCODEINPUT_ENABLED @@ -413,12 +391,21 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang if (DEBUG) { Log.d(TAG, "Write: " + '[' + logGroup.mLogString + ']' + log); } - if (mLogFileManager.append(builder.toString())) { + final String s = builder.toString(); + if (mLogFileManager.append(s)) { // success } else { if (DEBUG) { Log.w(TAG, "Unable to write to log."); } + // perhaps logfile was deleted. try to recreate and relog. + try { + mLogFileManager.createLogFile(PreferenceManager + .getDefaultSharedPreferences(mIms)); + mLogFileManager.append(s); + } catch (IOException e) { + e.printStackTrace(); + } } } }); @@ -448,32 +435,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang sIsLogging = prefs.getBoolean(PREF_USABILITY_STUDY_MODE, false); } - public static void alphabetShiftState_setShifted(final boolean newShiftState, - final int oldState, final AlphabetShiftState alphabetShiftState) { - if (UnsLogGroup.ALPHABETSHIFTSTATE_SETSHIFTED_ENABLED) { - final String s = "setShifted(" + newShiftState + "): " + oldState - + " > " + alphabetShiftState; - logUnstructured("AlphabetShiftState_setShifted", s); - } - } - - public static void alphabetShiftState_setShiftLocked(final boolean newShiftLockState, - final int oldState, final AlphabetShiftState alphabetShiftState) { - if (UnsLogGroup.ALPHABETSHIFTSTATE_SETSHIFTLOCKED_ENABLED) { - final String s = "setShiftLocked(" + newShiftLockState + "): " - + oldState + " > " + alphabetShiftState; - logUnstructured("AlphabetShiftState_setShiftLocked", s); - } - } - - public static void alphabetShiftState_setAutomaticShifted(final int oldState, - final AlphabetShiftState alphabetShiftState) { - if (UnsLogGroup.ALPHABETSHIFTSTATE_SETAUTOMATICSHIFTED_ENABLED) { - final String s = "setAutomaticShifted: " + oldState + " > " + alphabetShiftState; - logUnstructured("AlphabetShiftState_setAutomaticShifted", s); - } - } - public static void keyboardState_onCancelInput(final boolean isSinglePointer, final KeyboardState keyboardState) { if (UnsLogGroup.KEYBOARDSTATE_ONCANCELINPUT_ENABLED) { @@ -520,76 +481,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } - public static void keyboardState_onRestoreKeyboardState(final KeyboardState keyboardState, - final String savedKeyboardState) { - if (UnsLogGroup.KEYBOARDSTATE_ONRESTOREKEYBOARDSTATE_ENABLED) { - final String s = "onRestoreKeyboardState: saved=" + savedKeyboardState + " " - + keyboardState; - logUnstructured("KeyboardState_onRestoreKeyboardState", s); - } - } - - public static void keyboardState_onSaveKeyboardState(final KeyboardState keyboardState, - final String savedKeyboardState) { - if (UnsLogGroup.KEYBOARDSTATE_ONSAVEKEYBOARDSTATE_ENABLED) { - final String s = "onSaveKeyboardState: saved=" + savedKeyboardState + " " - + keyboardState; - logUnstructured("KeyboardState_onSaveKeyboardState", s); - } - } - - public static void keyboardState_onUpdateShiftState(final KeyboardState keyboardState, - final boolean autoCaps) { - if (UnsLogGroup.KEYBOARDSTATE_ONUPDATESHIFTSTATE_ENABLED) { - final String s = "onUpdateShiftState: autoCaps=" + autoCaps + " " + keyboardState; - logUnstructured("KeyboardState_onUpdateShiftState", s); - } - } - - public static void keyboardState_setAlphabetKeyboard() { - if (UnsLogGroup.KEYBOARDSTATE_SETALPHABETKEYBOARD_ENABLED) { - final String s = "setAlphabetKeyboard"; - logUnstructured("KeyboardState_setAlphabetKeyboard", s); - } - } - - public static void keyboardState_setShifted(final KeyboardState keyboardState, - final String shiftMode) { - if (UnsLogGroup.KEYBOARDSTATE_SETSHIFTED_ENABLED) { - final String s = "setShifted: shiftMode=" + shiftMode + " " + keyboardState; - logUnstructured("KeyboardState_setShifted", s); - } - } - - public static void keyboardState_setShiftLocked(final KeyboardState keyboardState, - final boolean shiftLocked) { - if (UnsLogGroup.KEYBOARDSTATE_SETSHIFTLOCKED_ENABLED) { - final String s = "setShiftLocked: shiftLocked=" + shiftLocked + " " + keyboardState; - logUnstructured("KeyboardState_setShiftLocked", s); - } - } - - public static void keyboardState_setSymbolsKeyboard() { - if (UnsLogGroup.KEYBOARDSTATE_SETSYMBOLSKEYBOARD_ENABLED) { - final String s = "setSymbolsKeyboard"; - logUnstructured("KeyboardState_setSymbolsKeyboard", s); - } - } - - public static void keyboardState_setSymbolsShiftedKeyboard() { - if (UnsLogGroup.KEYBOARDSTATE_SETSYMBOLSSHIFTEDKEYBOARD_ENABLED) { - final String s = "setSymbolsShiftedKeyboard"; - logUnstructured("KeyboardState_setSymbolsShiftedKeyboard", s); - } - } - - public static void keyboardState_toggleAlphabetAndSymbols(final KeyboardState keyboardState) { - if (UnsLogGroup.KEYBOARDSTATE_TOGGLEALPHABETANDSYMBOLS_ENABLED) { - final String s = "toggleAlphabetAndSymbols: " + keyboardState; - logUnstructured("KeyboardState_toggleAlphabetAndSymbols", s); - } - } - public static void latinIME_commitCurrentAutoCorrection(final String typedWord, final String autoCorrection) { if (UnsLogGroup.LATINIME_COMMITCURRENTAUTOCORRECTION_ENABLED) { @@ -637,14 +528,22 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } - public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo) { + public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo, + final SharedPreferences prefs) { if (UnsLogGroup.LATINIME_ONSTARTINPUTVIEWINTERNAL_ENABLED) { final StringBuilder builder = new StringBuilder(); builder.append("onStartInputView: editorInfo:"); - builder.append("inputType="); - builder.append(editorInfo.inputType); - builder.append("imeOptions="); - builder.append(editorInfo.imeOptions); + builder.append("\tinputType="); + builder.append(Integer.toHexString(editorInfo.inputType)); + builder.append("\timeOptions="); + builder.append(Integer.toHexString(editorInfo.imeOptions)); + builder.append("\tdisplay="); builder.append(Build.DISPLAY); + builder.append("\tmodel="); builder.append(Build.MODEL); + for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) { + builder.append("\t" + entry.getKey()); + Object value = entry.getValue(); + builder.append("=" + ((value == null) ? "<null>" : value.toString())); + } logUnstructured("LatinIME_onStartInputViewInternal", builder.toString()); } } @@ -745,6 +644,42 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang } } + public static void latinKeyboardView_setKeyboard(final Keyboard keyboard) { + if (UnsLogGroup.LATINKEYBOARDVIEW_SETKEYBOARD_ENABLED) { + StringBuilder builder = new StringBuilder(); + builder.append("id="); + builder.append(keyboard.mId); + builder.append("\tw="); + builder.append(keyboard.mOccupiedWidth); + builder.append("\th="); + builder.append(keyboard.mOccupiedHeight); + builder.append("\tkeys=["); + boolean first = true; + for (Key key : keyboard.mKeys) { + if (first) { + first = false; + } else { + builder.append(","); + } + builder.append("{code:"); + builder.append(key.mCode); + builder.append(",altCode:"); + builder.append(key.mAltCode); + builder.append(",x:"); + builder.append(key.mX); + builder.append(",y:"); + builder.append(key.mY); + builder.append(",w:"); + builder.append(key.mWidth); + builder.append(",h:"); + builder.append(key.mHeight); + builder.append("}"); + } + builder.append("]"); + logUnstructured("LatinKeyboardView_setKeyboard", builder.toString()); + } + } + public static void latinIME_revertCommit(final String originallyTypedWord) { if (UnsLogGroup.LATINIME_REVERTCOMMIT_ENABLED) { logUnstructured("LatinIME_revertCommit", originallyTypedWord); diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java index fc6193287..33ad23a60 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java +++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java @@ -27,10 +27,23 @@ import java.util.Locale; public class SubtypeLocale { private static final String TAG = SubtypeLocale.class.getSimpleName(); + // This class must be located in the same package as LatinIME.java. + private static final String RESOURCE_PACKAGE_NAME = + DictionaryFactory.class.getPackage().getName(); // Special language code to represent "no language". public static final String NO_LANGUAGE = "zz"; + public static final String QWERTY = "qwerty"; + + private static String[] sPredefinedKeyboardLayoutSet; + // Keyboard layout to its display name map. + private static final HashMap<String, String> sKeyboardKayoutToDisplayNameMap = + new HashMap<String, String>(); + // Keyboard layout to subtype name resource id map. + private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = + new HashMap<String, Integer>(); + private static final String SUBTYPE_RESOURCE_GENERIC_NAME_PREFIX = "string/subtype_generic_"; // Exceptional locales to display name map. private static final HashMap<String, String> sExceptionalDisplayNamesMap = new HashMap<String, String>(); @@ -41,13 +54,36 @@ public class SubtypeLocale { public static void init(Context context) { final Resources res = context.getResources(); - final String[] locales = res.getStringArray(R.array.subtype_locale_exception_keys); - final String[] displayNames = res.getStringArray(R.array.subtype_locale_exception_values); - for (int i = 0; i < locales.length; i++) { - sExceptionalDisplayNamesMap.put(locales[i], displayNames[i]); + + final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts); + sPredefinedKeyboardLayoutSet = predefinedLayoutSet; + final String[] layoutDisplayNames = res.getStringArray( + R.array.predefined_layout_display_names); + for (int i = 0; i < predefinedLayoutSet.length; i++) { + final String layoutName = predefinedLayoutSet[i]; + sKeyboardKayoutToDisplayNameMap.put(layoutName, layoutDisplayNames[i]); + final String resourceName = SUBTYPE_RESOURCE_GENERIC_NAME_PREFIX + layoutName; + final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); + sKeyboardLayoutToNameIdsMap.put(layoutName, resId); + } + + final String[] exceptionalLocales = res.getStringArray( + R.array.subtype_locale_exception_keys); + final String[] exceptionalDisplayNames = res.getStringArray( + R.array.subtype_locale_exception_values); + for (int i = 0; i < exceptionalLocales.length; i++) { + sExceptionalDisplayNamesMap.put(exceptionalLocales[i], exceptionalDisplayNames[i]); } } + public static String[] getPredefinedKeyboardLayoutSet() { + return sPredefinedKeyboardLayoutSet; + } + + public static int getSubtypeNameIdFromKeyboardLayoutName(String keyboardLayoutName) { + return sKeyboardLayoutToNameIdsMap.get(keyboardLayoutName); + } + // Get InputMethodSubtype's display name in its locale. // isAdditionalSubtype (T=true, F=false) // locale layout | Short Middle Full @@ -115,7 +151,8 @@ public class SubtypeLocale { } public static String getKeyboardLayoutSetDisplayName(InputMethodSubtype subtype) { - return getKeyboardLayoutSetName(subtype).toUpperCase(); + final String layoutName = getKeyboardLayoutSetName(subtype); + return sKeyboardKayoutToDisplayNameMap.get(layoutName); } public static String getKeyboardLayoutSetName(InputMethodSubtype subtype) { @@ -125,7 +162,7 @@ public class SubtypeLocale { if (keyboardLayoutSet == null) { android.util.Log.w(TAG, "KeyboardLayoutSet not found, use QWERTY: " + "locale=" + subtype.getLocale() + " extraValue=" + subtype.getExtraValue()); - return AdditionalSubtype.QWERTY; + return QWERTY; } return keyboardLayoutSet; } diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index 1a9f373a1..804287309 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -16,19 +16,16 @@ package com.android.inputmethod.latin; -import static com.android.inputmethod.latin.Constants.Subtype.KEYBOARD_MODE; import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.REQ_NETWORK_CONNECTIVITY; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; import android.os.IBinder; -import android.text.TextUtils; import android.util.Log; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; @@ -36,7 +33,6 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.keyboard.KeyboardSwitcher; -import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -45,37 +41,42 @@ public class SubtypeSwitcher { private static boolean DBG = LatinImeLogger.sDBG; private static final String TAG = SubtypeSwitcher.class.getSimpleName(); - private static final char LOCALE_SEPARATOR = '_'; - private final TextUtils.SimpleStringSplitter mLocaleSplitter = - new TextUtils.SimpleStringSplitter(LOCALE_SEPARATOR); - private static final SubtypeSwitcher sInstance = new SubtypeSwitcher(); private /* final */ LatinIME mService; private /* final */ InputMethodManager mImm; private /* final */ Resources mResources; private /* final */ ConnectivityManager mConnectivityManager; - private final ArrayList<InputMethodSubtype> mEnabledKeyboardSubtypesOfCurrentInputMethod = - new ArrayList<InputMethodSubtype>(); - private final ArrayList<String> mEnabledLanguagesOfCurrentInputMethod = new ArrayList<String>(); /*-----------------------------------------------------------*/ // Variants which should be changed only by reload functions. - private boolean mNeedsToDisplayLanguage; - private boolean mIsDictionaryAvailable; - private boolean mIsSystemLanguageSameAsInputLanguage; + private NeedsToDisplayLanguage mNeedsToDisplayLanguage = new NeedsToDisplayLanguage(); private InputMethodInfo mShortcutInputMethodInfo; private InputMethodSubtype mShortcutSubtype; - private List<InputMethodSubtype> mAllEnabledSubtypesOfCurrentInputMethod; private InputMethodSubtype mNoLanguageSubtype; // Note: This variable is always non-null after {@link #initialize(LatinIME)}. private InputMethodSubtype mCurrentSubtype; - private Locale mSystemLocale; - private Locale mInputLocale; - private String mInputLocaleStr; + private Locale mCurrentSystemLocale; /*-----------------------------------------------------------*/ private boolean mIsNetworkConnected; + static class NeedsToDisplayLanguage { + private int mEnabledSubtypeCount; + private boolean mIsSystemLanguageSameAsInputLanguage; + + public boolean getValue() { + return mEnabledSubtypeCount >= 2 || !mIsSystemLanguageSameAsInputLanguage; + } + + public void updateEnabledSubtypeCount(int count) { + mEnabledSubtypeCount = count; + } + + public void updateIsSystemLanguageSameAsInputLanguage(boolean isSame) { + mIsSystemLanguageSameAsInputLanguage = isSame; + } + } + public static SubtypeSwitcher getInstance() { return sInstance; } @@ -96,15 +97,10 @@ public class SubtypeSwitcher { mImm = ImfUtils.getInputMethodManager(service); mConnectivityManager = (ConnectivityManager) service.getSystemService( Context.CONNECTIVITY_SERVICE); - mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); - mEnabledLanguagesOfCurrentInputMethod.clear(); - mSystemLocale = null; - mInputLocale = null; - mInputLocaleStr = null; + mCurrentSystemLocale = mResources.getConfiguration().locale; mCurrentSubtype = mImm.getCurrentInputMethodSubtype(); - mAllEnabledSubtypesOfCurrentInputMethod = null; mNoLanguageSubtype = ImfUtils.findSubtypeByLocaleAndKeyboardLayoutSet( - service, SubtypeLocale.NO_LANGUAGE, AdditionalSubtype.QWERTY); + service, SubtypeLocale.NO_LANGUAGE, SubtypeLocale.QWERTY); final NetworkInfo info = mConnectivityManager.getActiveNetworkInfo(); mIsNetworkConnected = (info != null && info.isConnected()); @@ -113,7 +109,7 @@ public class SubtypeSwitcher { // Update all parameters stored in SubtypeSwitcher. // Only configuration changed event is allowed to call this because this is heavy. private void updateAllParameters() { - mSystemLocale = mResources.getConfiguration().locale; + mCurrentSystemLocale = mResources.getConfiguration().locale; updateSubtype(mImm.getCurrentInputMethodSubtype()); updateParametersOnStartInputView(); } @@ -127,31 +123,20 @@ public class SubtypeSwitcher { // Reload enabledSubtypes from the framework. private void updateEnabledSubtypes() { - final String currentMode = mCurrentSubtype.getMode(); + final InputMethodSubtype currentSubtype = mCurrentSubtype; boolean foundCurrentSubtypeBecameDisabled = true; - mAllEnabledSubtypesOfCurrentInputMethod = mImm.getEnabledInputMethodSubtypeList( - null, true); - mEnabledLanguagesOfCurrentInputMethod.clear(); - mEnabledKeyboardSubtypesOfCurrentInputMethod.clear(); - for (InputMethodSubtype ims : mAllEnabledSubtypesOfCurrentInputMethod) { - final String locale = ims.getLocale(); - final String mode = ims.getMode(); - mLocaleSplitter.setString(locale); - if (mLocaleSplitter.hasNext()) { - mEnabledLanguagesOfCurrentInputMethod.add(mLocaleSplitter.next()); - } - if (locale.equals(mInputLocaleStr) && mode.equals(currentMode)) { + final List<InputMethodSubtype> enabledSubtypesOfThisIme = + mImm.getEnabledInputMethodSubtypeList(null, true); + for (InputMethodSubtype ims : enabledSubtypesOfThisIme) { + if (ims.equals(currentSubtype)) { foundCurrentSubtypeBecameDisabled = false; } - if (KEYBOARD_MODE.equals(ims.getMode())) { - mEnabledKeyboardSubtypesOfCurrentInputMethod.add(ims); - } } - mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1 - && mIsSystemLanguageSameAsInputLanguage); + mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); if (foundCurrentSubtypeBecameDisabled) { if (DBG) { - Log.w(TAG, "Current subtype: " + mInputLocaleStr + ", " + currentMode); + Log.w(TAG, "Last subtype: " + + currentSubtype.getLocale() + "/" + currentSubtype.getExtraValue()); Log.w(TAG, "Last subtype was disabled. Update to the current one."); } updateSubtype(mImm.getCurrentInputMethodSubtype()); @@ -192,70 +177,20 @@ public class SubtypeSwitcher { // Update the current subtype. LatinIME.onCurrentInputMethodSubtypeChanged calls this function. public void updateSubtype(InputMethodSubtype newSubtype) { - final String newLocale = newSubtype.getLocale(); - final String newMode = newSubtype.getMode(); - final String oldMode = mCurrentSubtype.getMode(); if (DBG) { - Log.w(TAG, "Update subtype to:" + newLocale + "," + newMode - + ", from: " + mInputLocaleStr + ", " + oldMode); + Log.w(TAG, "onCurrentInputMethodSubtypeChanged: to: " + + newSubtype.getLocale() + "/" + newSubtype.getExtraValue() + ", from: " + + mCurrentSubtype.getLocale() + "/" + mCurrentSubtype.getExtraValue()); } - boolean languageChanged = false; - if (!newLocale.equals(mInputLocaleStr)) { - if (mInputLocaleStr != null) { - languageChanged = true; - } - updateInputLocale(newLocale); - } - boolean modeChanged = false; - if (!newMode.equals(oldMode)) { - if (oldMode != null) { - modeChanged = true; - } - } - mCurrentSubtype = newSubtype; + if (newSubtype.equals(mCurrentSubtype)) return; - if (KEYBOARD_MODE.equals(mCurrentSubtype.getMode())) { - if (modeChanged || languageChanged) { - updateShortcutIME(); - mService.onRefreshKeyboard(); - } - } else { - final String packageName = mService.getPackageName(); - int version = -1; - try { - version = mService.getPackageManager().getPackageInfo( - packageName, 0).versionCode; - } catch (NameNotFoundException e) { - } - Log.w(TAG, "Unknown subtype mode: " + newMode + "," + version + ", " + packageName - + ". IME is already changed to other IME."); - Log.w(TAG, "Subtype mode:" + newSubtype.getMode()); - Log.w(TAG, "Subtype locale:" + newSubtype.getLocale()); - Log.w(TAG, "Subtype extra value:" + newSubtype.getExtraValue()); - Log.w(TAG, "Subtype is auxiliary:" + newSubtype.isAuxiliary()); - } - } + final Locale newLocale = SubtypeLocale.getSubtypeLocale(newSubtype); + mNeedsToDisplayLanguage.updateIsSystemLanguageSameAsInputLanguage( + mCurrentSystemLocale.equals(newLocale)); - // Update the current input locale from Locale string. - private void updateInputLocale(String inputLocaleStr) { - // example: inputLocaleStr = "en_US" "en" "" - // "en_US" --> language: en & country: US - // "en" --> language: en - // "" --> the system locale - if (!TextUtils.isEmpty(inputLocaleStr)) { - mInputLocale = LocaleUtils.constructLocaleFromString(inputLocaleStr); - mInputLocaleStr = inputLocaleStr; - } else { - mInputLocale = mSystemLocale; - String country = mSystemLocale.getCountry(); - mInputLocaleStr = mSystemLocale.getLanguage() - + (TextUtils.isEmpty(country) ? "" : "_" + mSystemLocale.getLanguage()); - } - mIsSystemLanguageSameAsInputLanguage = getSystemLocale().getLanguage().equalsIgnoreCase( - getInputLocale().getLanguage()); - mNeedsToDisplayLanguage = !(getEnabledKeyboardLocaleCount() <= 1 - && mIsSystemLanguageSameAsInputLanguage); - mIsDictionaryAvailable = DictionaryFactory.isDictionaryAvailable(mService, mInputLocale); + mCurrentSubtype = newSubtype; + updateShortcutIME(); + mService.onRefreshKeyboard(); } //////////////////////////// @@ -323,62 +258,31 @@ public class SubtypeSwitcher { } ////////////////////////////////// - // Language Switching functions // + // Subtype Switching functions // ////////////////////////////////// - public int getEnabledKeyboardLocaleCount() { - return mEnabledKeyboardSubtypesOfCurrentInputMethod.size(); - } - public boolean needsToDisplayLanguage(Locale keyboardLocale) { if (keyboardLocale.toString().equals(SubtypeLocale.NO_LANGUAGE)) { return true; } - if (!keyboardLocale.equals(mInputLocale)) { + if (!keyboardLocale.equals(getCurrentSubtypeLocale())) { return false; } - return mNeedsToDisplayLanguage; - } - - public Locale getInputLocale() { - return mInputLocale; - } - - public String getInputLocaleStr() { - return mInputLocaleStr; - } - - public String[] getEnabledLanguages() { - int enabledLanguageCount = mEnabledLanguagesOfCurrentInputMethod.size(); - // Workaround for explicitly specifying the voice language - if (enabledLanguageCount == 1) { - mEnabledLanguagesOfCurrentInputMethod.add(mEnabledLanguagesOfCurrentInputMethod - .get(0)); - ++enabledLanguageCount; - } - return mEnabledLanguagesOfCurrentInputMethod.toArray(new String[enabledLanguageCount]); + return mNeedsToDisplayLanguage.getValue(); } - public Locale getSystemLocale() { - return mSystemLocale; - } - - public boolean isSystemLanguageSameAsInputLanguage() { - return mIsSystemLanguageSameAsInputLanguage; + public Locale getCurrentSubtypeLocale() { + return SubtypeLocale.getSubtypeLocale(mCurrentSubtype); } public void onConfigurationChanged(Configuration conf) { final Locale systemLocale = conf.locale; // If system configuration was changed, update all parameters. - if (!TextUtils.equals(systemLocale.toString(), mSystemLocale.toString())) { + if (!systemLocale.equals(mCurrentSystemLocale)) { updateAllParameters(); } } - public boolean isDictionaryAvailable() { - return mIsDictionaryAvailable; - } - public InputMethodSubtype getCurrentSubtype() { return mCurrentSubtype; } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java index d22332116..97df98e34 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java @@ -21,6 +21,7 @@ import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions import com.android.inputmethod.latin.makedict.FusionDictionary.Node; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; @@ -272,6 +273,29 @@ public class BinaryDictInputOutput { } /** + * Writes a string with our character format to a ByteArrayOutputStream. + * + * This will also write the terminator byte. + * + * @param buffer the ByteArrayOutputStream to write to. + * @param word the string to write. + */ + private static void writeString(ByteArrayOutputStream buffer, final String word) { + final int length = word.length(); + for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { + final int codePoint = word.codePointAt(i); + if (1 == getCharSize(codePoint)) { + buffer.write((byte) codePoint); + } else { + buffer.write((byte) (0xFF & (codePoint >> 16))); + buffer.write((byte) (0xFF & (codePoint >> 8))); + buffer.write((byte) (0xFF & codePoint)); + } + } + buffer.write(GROUP_CHARACTERS_TERMINATOR); + } + + /** * Reads a string from a RandomAccessFile. This is the converse of the above method. */ private static String readString(final RandomAccessFile source) throws IOException { @@ -894,15 +918,11 @@ public class BinaryDictInputOutput { final FusionDictionary dict, final int version) throws IOException, UnsupportedFormatException { - // Addresses are limited to 3 bytes, so we'll just make a 16MB buffer. Since addresses - // can be relative to each node, the structure itself is not limited to 16MB at all, but - // I doubt this will ever be shot. If it is, deciding the order of the nodes becomes - // a quite complicated problem, because though the dictionary itself does not have a - // size limit, each node must still be within 16MB of all its children and parents. - // As long as this is ensured, the dictionary file may grow to any size. - // Anyway, to make a dictionary bigger than 16MB just increase the size of this buffer. - final byte[] buffer = new byte[1 << 24]; - int index = 0; + // Addresses are limited to 3 bytes, but since addresses can be relative to each node, the + // structure itself is not limited to 16MB. However, if it is over 16MB deciding the order + // of the nodes becomes a quite complicated problem, because though the dictionary itself + // does not have a size limit, each node must still be within 16MB of all its children and + // parents. As long as this is ensured, the dictionary file may grow to any size. if (version < MINIMUM_SUPPORTED_VERSION || version > MAXIMUM_SUPPORTED_VERSION) { throw new UnsupportedFormatException("Requested file format version " + version @@ -910,47 +930,54 @@ public class BinaryDictInputOutput { + MINIMUM_SUPPORTED_VERSION + " through " + MAXIMUM_SUPPORTED_VERSION); } + ByteArrayOutputStream headerBuffer = new ByteArrayOutputStream(256); + // The magic number in big-endian order. if (version >= FIRST_VERSION_WITH_HEADER_SIZE) { // Magic number for version 2+. - buffer[index++] = (byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 24)); - buffer[index++] = (byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 16)); - buffer[index++] = (byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 8)); - buffer[index++] = (byte) (0xFF & VERSION_2_MAGIC_NUMBER); + headerBuffer.write((byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 24))); + headerBuffer.write((byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 16))); + headerBuffer.write((byte) (0xFF & (VERSION_2_MAGIC_NUMBER >> 8))); + headerBuffer.write((byte) (0xFF & VERSION_2_MAGIC_NUMBER)); // Dictionary version. - buffer[index++] = (byte) (0xFF & (version >> 8)); - buffer[index++] = (byte) (0xFF & version); + headerBuffer.write((byte) (0xFF & (version >> 8))); + headerBuffer.write((byte) (0xFF & version)); } else { // Magic number for version 1. - buffer[index++] = (byte) (0xFF & (VERSION_1_MAGIC_NUMBER >> 8)); - buffer[index++] = (byte) (0xFF & VERSION_1_MAGIC_NUMBER); + headerBuffer.write((byte) (0xFF & (VERSION_1_MAGIC_NUMBER >> 8))); + headerBuffer.write((byte) (0xFF & VERSION_1_MAGIC_NUMBER)); // Dictionary version. - buffer[index++] = (byte) (0xFF & version); + headerBuffer.write((byte) (0xFF & version)); } // Options flags final int options = makeOptionsValue(dict.mOptions); - buffer[index++] = (byte) (0xFF & (options >> 8)); - buffer[index++] = (byte) (0xFF & options); + headerBuffer.write((byte) (0xFF & (options >> 8))); + headerBuffer.write((byte) (0xFF & options)); if (version >= FIRST_VERSION_WITH_HEADER_SIZE) { - final int headerSizeOffset = index; - index += 4; // Size of the header size - + final int headerSizeOffset = headerBuffer.size(); + // Placeholder to be written later with header size. + for (int i = 0; i < 4; ++i) { + headerBuffer.write(0); + } // Write out the options. for (final String key : dict.mOptions.mAttributes.keySet()) { final String value = dict.mOptions.mAttributes.get(key); - index += CharEncoding.writeString(buffer, index, key); - index += CharEncoding.writeString(buffer, index, value); + CharEncoding.writeString(headerBuffer, key); + CharEncoding.writeString(headerBuffer, value); } - + final int size = headerBuffer.size(); + final byte[] bytes = headerBuffer.toByteArray(); // Write out the header size. - buffer[headerSizeOffset] = (byte) (0xFF & (index >> 24)); - buffer[headerSizeOffset + 1] = (byte) (0xFF & (index >> 16)); - buffer[headerSizeOffset + 2] = (byte) (0xFF & (index >> 8)); - buffer[headerSizeOffset + 3] = (byte) (0xFF & (index >> 0)); + bytes[headerSizeOffset] = (byte) (0xFF & (size >> 24)); + bytes[headerSizeOffset + 1] = (byte) (0xFF & (size >> 16)); + bytes[headerSizeOffset + 2] = (byte) (0xFF & (size >> 8)); + bytes[headerSizeOffset + 3] = (byte) (0xFF & (size >> 0)); + destination.write(bytes); + } else { + headerBuffer.writeTo(destination); } - destination.write(buffer, 0, index); - index = 0; + headerBuffer.close(); // Leave the choice of the optimal node order to the flattenTree function. MakedictLog.i("Flattening the tree..."); @@ -961,6 +988,12 @@ public class BinaryDictInputOutput { MakedictLog.i("Checking array..."); checkFlatNodeArray(flatNodes); + // Create a buffer that matches the final dictionary size. + final Node lastNode = flatNodes.get(flatNodes.size() - 1); + final int bufferSize =(lastNode.mCachedAddress + lastNode.mCachedSize); + final byte[] buffer = new byte[bufferSize]; + int index = 0; + MakedictLog.i("Writing file..."); int dataEndOffset = 0; for (Node n : flatNodes) { |