diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
20 files changed, 693 insertions, 94 deletions
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index ad3163347..ab2a12fd0 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -16,7 +16,6 @@ package com.android.inputmethod.latin; -import android.content.Context; import android.text.TextUtils; import android.util.SparseArray; diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index d9d664fb5..0d0ce5756 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -74,6 +74,8 @@ public final class BinaryDictionaryFileDumper { // The path fragment to append after the client ID for dictionary info requests. private static final String QUERY_PATH_DICT_INFO = "dict"; + // The path fragment to append after the client ID for dictionary datafile requests. + private static final String QUERY_PATH_DATAFILE = "datafile"; // The path fragment to append after the client ID for updating the metadata URI. private static final String QUERY_PATH_METADATA = "metadata"; private static final String INSERT_METADATA_CLIENT_ID_COLUMN = "clientid"; @@ -156,7 +158,7 @@ public final class BinaryDictionaryFileDumper { c.close(); return Collections.<WordListInfo>emptyList(); } - final List<WordListInfo> list = CollectionUtils.newArrayList(); + final ArrayList<WordListInfo> list = CollectionUtils.newArrayList(); do { final String wordListId = c.getString(0); final String wordListLocale = c.getString(1); @@ -186,13 +188,18 @@ public final class BinaryDictionaryFileDumper { /** * Helper method to encapsulate exception handling. */ - private static AssetFileDescriptor openAssetFileDescriptor(final ContentResolver resolver, - final Uri uri) { + private static AssetFileDescriptor openAssetFileDescriptor( + final ContentProviderClient providerClient, final Uri uri) { try { - return resolver.openAssetFileDescriptor(uri, "r"); + return providerClient.openAssetFile(uri, "r"); } catch (FileNotFoundException e) { - // I don't want to log the word list URI here for security concerns - Log.e(TAG, "Could not find a word list from the dictionary provider."); + // I don't want to log the word list URI here for security concerns. The exception + // contains the name of the file, so let's not pass it to Log.e here. + Log.e(TAG, "Could not find a word list from the dictionary provider." + /* intentionally don't pass the exception (see comment above) */); + return null; + } catch (RemoteException e) { + Log.e(TAG, "Can't communicate with the dictionary pack", e); return null; } } @@ -202,9 +209,8 @@ public final class BinaryDictionaryFileDumper { * to the cache file name designated by its id and locale, overwriting it if already present * and creating it (and its containing directory) if necessary. */ - private static AssetFileAddress cacheWordList(final String id, final String locale, - final ContentResolver resolver, final Context context) { - + private static AssetFileAddress cacheWordList(final String wordlistId, final String locale, + final ContentProviderClient providerClient, final Context context) { final int COMPRESSED_CRYPTED_COMPRESSED = 0; final int CRYPTED_COMPRESSED = 1; final int COMPRESSED_CRYPTED = 2; @@ -214,11 +220,20 @@ public final class BinaryDictionaryFileDumper { final int MODE_MIN = COMPRESSED_CRYPTED_COMPRESSED; final int MODE_MAX = NONE; - final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id); - final String finalFileName = DictionaryInfoUtils.getCacheFileName(id, locale, context); + final String clientId = context.getString(R.string.dictionary_pack_client_id); + final Uri.Builder wordListUriBuilder; + try { + wordListUriBuilder = getContentUriBuilderForType(clientId, + providerClient, QUERY_PATH_DATAFILE, wordlistId /* extraPath */); + } catch (RemoteException e) { + Log.e(TAG, "Can't communicate with the dictionary pack", e); + return null; + } + final String finalFileName = + DictionaryInfoUtils.getCacheFileName(wordlistId, locale, context); String tempFileName; try { - tempFileName = BinaryDictionaryGetter.getTempFileName(id, context); + tempFileName = BinaryDictionaryGetter.getTempFileName(wordlistId, context); } catch (IOException e) { Log.e(TAG, "Can't open the temporary file", e); return null; @@ -236,7 +251,7 @@ public final class BinaryDictionaryFileDumper { final Uri wordListUri = wordListUriBuilder.build(); try { // Open input. - afd = openAssetFileDescriptor(resolver, wordListUri); + afd = openAssetFileDescriptor(providerClient, wordListUri); // If we can't open it at all, don't even try a number of times. if (null == afd) return null; originalSourceStream = afd.createInputStream(); @@ -284,10 +299,10 @@ public final class BinaryDictionaryFileDumper { } wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT, QUERY_PARAMETER_SUCCESS); - if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) { + if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) { Log.e(TAG, "Could not have the dictionary pack delete a word list"); } - BinaryDictionaryGetter.removeFilesWithIdExcept(context, id, finalFile); + BinaryDictionaryGetter.removeFilesWithIdExcept(context, wordlistId, finalFile); // Success! Close files (through the finally{} clause) and return. return AssetFileAddress.makeFromFileName(finalFileName); } catch (Exception e) { @@ -327,8 +342,12 @@ public final class BinaryDictionaryFileDumper { // as invalid. wordListUriBuilder.appendQueryParameter(QUERY_PARAMETER_DELETE_RESULT, QUERY_PARAMETER_FAILURE); - if (0 >= resolver.delete(wordListUriBuilder.build(), null, null)) { - Log.e(TAG, "In addition, we were unable to delete it."); + try { + if (0 >= providerClient.delete(wordListUriBuilder.build(), null, null)) { + Log.e(TAG, "In addition, we were unable to delete it."); + } + } catch (RemoteException e) { + Log.e(TAG, "In addition, communication with the dictionary provider was cut", e); } return null; } @@ -345,17 +364,27 @@ public final class BinaryDictionaryFileDumper { */ public static List<AssetFileAddress> cacheWordListsFromContentProvider(final Locale locale, final Context context, final boolean hasDefaultWordList) { - final ContentResolver resolver = context.getContentResolver(); - final List<WordListInfo> idList = getWordListWordListInfos(locale, context, - hasDefaultWordList); - final List<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList(); - for (WordListInfo id : idList) { - final AssetFileAddress afd = cacheWordList(id.mId, id.mLocale, resolver, context); - if (null != afd) { - fileAddressList.add(afd); + final ContentProviderClient providerClient = context.getContentResolver(). + acquireContentProviderClient(getProviderUriBuilder("").build()); + if (null == providerClient) { + Log.e(TAG, "Can't establish communication with the dictionary provider"); + return CollectionUtils.newArrayList(); + } + try { + final List<WordListInfo> idList = getWordListWordListInfos(locale, context, + hasDefaultWordList); + final ArrayList<AssetFileAddress> fileAddressList = CollectionUtils.newArrayList(); + for (WordListInfo id : idList) { + final AssetFileAddress afd = + cacheWordList(id.mId, id.mLocale, providerClient, context); + if (null != afd) { + fileAddressList.add(afd); + } } + return fileAddressList; + } finally { + providerClient.release(); } - return fileAddressList; } /** diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index a96738b3e..e913f2852 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -68,9 +68,13 @@ final class BinaryDictionaryGetter { /** * Generates a unique temporary file name in the app cache directory. */ - public static String getTempFileName(String id, Context context) throws IOException { - return File.createTempFile(DictionaryInfoUtils.replaceFileNameDangerousCharacters(id), - null).getAbsolutePath(); + public static String getTempFileName(final String id, final Context context) + throws IOException { + final String safeId = DictionaryInfoUtils.replaceFileNameDangerousCharacters(id); + // If the first argument is less than three chars, createTempFile throws a + // RuntimeException. We don't really care about what name we get, so just + // put a three-chars prefix makes us safe. + return File.createTempFile("xxx" + safeId, null).getAbsolutePath(); } /** diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index 422448edf..50e50233e 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -179,14 +179,13 @@ public final class Constants { public static final int CODE_DELETE = -4; public static final int CODE_SETTINGS = -5; public static final int CODE_SHORTCUT = -6; - public static final int CODE_ACTION_ENTER = -7; - public static final int CODE_ACTION_NEXT = -8; - public static final int CODE_ACTION_PREVIOUS = -9; - public static final int CODE_LANGUAGE_SWITCH = -10; - public static final int CODE_RESEARCH = -11; - public static final int CODE_SHIFT_ENTER = -12; + public static final int CODE_ACTION_NEXT = -7; + public static final int CODE_ACTION_PREVIOUS = -8; + public static final int CODE_LANGUAGE_SWITCH = -9; + public static final int CODE_RESEARCH = -10; + public static final int CODE_SHIFT_ENTER = -11; // Code value representing the code is not specified. - public static final int CODE_UNSPECIFIED = -13; + public static final int CODE_UNSPECIFIED = -12; public static boolean isLetterCode(final int code) { return code >= CODE_SPACE; @@ -200,7 +199,6 @@ public final class Constants { case CODE_DELETE: return "delete"; case CODE_SETTINGS: return "settings"; case CODE_SHORTCUT: return "shortcut"; - case CODE_ACTION_ENTER: return "actionEnter"; case CODE_ACTION_NEXT: return "actionNext"; case CODE_ACTION_PREVIOUS: return "actionPrevious"; case CODE_LANGUAGE_SWITCH: return "languageSwitch"; diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java index 7df266ef2..c2aade64d 100644 --- a/java/src/com/android/inputmethod/latin/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/DebugSettings.java @@ -57,7 +57,7 @@ public final class DebugSettings extends PreferenceFragment if (usabilityStudyPref instanceof CheckBoxPreference) { final CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref; checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE, - ResearchLogger.DEFAULT_USABILITY_STUDY_MODE)); + LatinImeLogger.getUsabilityStudyMode(prefs))); checkbox.setSummary(R.string.settings_warning_researcher_mode); } final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING); diff --git a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java index d2a946bf5..dcfa483f8 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java @@ -41,8 +41,6 @@ public class DictionaryInfoUtils { private static final String RESOURCE_PACKAGE_NAME = DictionaryInfoUtils.class.getPackage().getName(); private static final String DEFAULT_MAIN_DICT = "main"; - private static final String ID_CATEGORY_SEPARATOR = - BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR; private static final String MAIN_DICT_PREFIX = "main_"; // 6 digits - unicode is limited to 21 bits private static final int MAX_HEX_DIGITS_FOR_CODEPOINT = 6; @@ -51,24 +49,28 @@ public class DictionaryInfoUtils { private static final String LOCALE_COLUMN = "locale"; private static final String WORDLISTID_COLUMN = "id"; private static final String LOCAL_FILENAME_COLUMN = "filename"; + private static final String DESCRIPTION_COLUMN = "description"; private static final String DATE_COLUMN = "date"; private static final String FILESIZE_COLUMN = "filesize"; private static final String VERSION_COLUMN = "version"; + public final String mId; public final Locale mLocale; + public final String mDescription; public final AssetFileAddress mFileAddress; public final int mVersion; - public final String mId; - public DictionaryInfo(final Locale locale, final AssetFileAddress fileAddress, - final int version) { + public DictionaryInfo(final String id, final Locale locale, final String description, + final AssetFileAddress fileAddress, final int version) { + mId = id; mLocale = locale; + mDescription = description; mFileAddress = fileAddress; mVersion = version; - mId = DEFAULT_MAIN_DICT + ID_CATEGORY_SEPARATOR + mLocale; } public ContentValues toContentValues() { final ContentValues values = new ContentValues(); values.put(WORDLISTID_COLUMN, mId); values.put(LOCALE_COLUMN, mLocale.toString()); + values.put(DESCRIPTION_COLUMN, mDescription); values.put(LOCAL_FILENAME_COLUMN, mFileAddress.mFilename); values.put(DATE_COLUMN, new File(mFileAddress.mFilename).lastModified() / DateUtils.SECOND_IN_MILLIS); @@ -283,9 +285,11 @@ public class DictionaryInfoUtils { final AssetFileAddress fileAddress) { final FileHeader header = BinaryDictIOUtils.getDictionaryFileHeaderOrNull( new File(fileAddress.mFilename), fileAddress.mOffset, fileAddress.mLength); + final String id = header.getId(); final Locale locale = LocaleUtils.constructLocaleFromString(header.getLocaleString()); + final String description = header.getDescription(); final String version = header.getVersion(); - return new DictionaryInfo(locale, fileAddress, Integer.parseInt(version)); + return new DictionaryInfo(id, locale, description, fileAddress, Integer.parseInt(version)); } private static void addOrUpdateDictInfo(final ArrayList<DictionaryInfo> dictList, diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java index 2ad619b82..ecb20144b 100644 --- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java +++ b/java/src/com/android/inputmethod/latin/InputTypeUtils.java @@ -106,18 +106,13 @@ public final class InputTypeUtils implements InputType { } public static int getImeOptionsActionIdFromEditorInfo(final EditorInfo editorInfo) { - final int actionId = editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION; if ((editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) != 0) { return EditorInfo.IME_ACTION_NONE; } else if (editorInfo.actionLabel != null) { return IME_ACTION_CUSTOM_LABEL; } else { - return actionId; + // Note: this is different from editorInfo.actionId, hence "ImeOptionsActionId" + return editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION; } } - - public static int getConcreteActionIdFromEditorInfo(final EditorInfo editorInfo) { - final int actionId = getImeOptionsActionIdFromEditorInfo(editorInfo); - return actionId == InputTypeUtils.IME_ACTION_CUSTOM_LABEL ? editorInfo.actionId : actionId; - } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 73ace2bfa..252fb02c8 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -132,6 +132,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private View mKeyPreviewBackingView; private View mSuggestionsContainer; private SuggestionStripView mSuggestionStripView; + // Never null + private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; @UsedForTesting Suggest mSuggest; private CompletionInfo[] mApplicationSpecifiedCompletions; private ApplicationInfo mTargetApplicationInfo; @@ -165,7 +167,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private boolean mExpectingUpdateSelection; private int mDeleteCount; private long mLastKeyTime; - private int mActionId; private TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet(); // Member variables for remembering the current device orientation. @@ -427,7 +428,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction initSuggest(); if (ProductionFlag.IS_EXPERIMENTAL) { - ResearchLogger.getInstance().init(this, mKeyboardSwitcher); + ResearchLogger.getInstance().init(this, mKeyboardSwitcher, mSuggest); } mDisplayOrientation = getResources().getConfiguration().orientation; @@ -562,6 +563,9 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } mSettings.onDestroy(); unregisterReceiver(mReceiver); + if (ProductionFlag.IS_EXPERIMENTAL) { + ResearchLogger.getInstance().onDestroy(); + } // TODO: The experimental version is not supported by the Dictionary Pack Service yet. if (!ProductionFlag.IS_EXPERIMENTAL) { unregisterReceiver(mDictionaryPackInstallReceiver); @@ -729,6 +733,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // otherwise it will clear the suggestion strip. setPunctuationSuggestions(); } + mSuggestedWords = SuggestedWords.EMPTY; mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart); @@ -756,7 +761,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction mLastSelectionStart = editorInfo.initialSelStart; mLastSelectionEnd = editorInfo.initialSelEnd; - mActionId = InputTypeUtils.getConcreteActionIdFromEditorInfo(editorInfo); mHandler.cancelUpdateSuggestionStrip(); mHandler.cancelDoubleSpacePeriodTimer(); @@ -954,6 +958,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction LatinImeLogger.commit(); mKeyboardSwitcher.onHideWindow(); + if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { + AccessibleKeyboardViewProxy.getInstance().onHideWindow(); + } + if (TRACE) Debug.stopMethodTracing(); if (mOptionsDialog != null && mOptionsDialog.isShowing()) { mOptionsDialog.dismiss(); @@ -994,7 +1002,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction false /* isPrediction */); // When in fullscreen mode, show completions generated by the application final boolean isAutoCorrection = false; - setSuggestionStrip(suggestedWords, isAutoCorrection); + setSuggestedWords(suggestedWords, isAutoCorrection); setAutoCorrectionIndicator(isAutoCorrection); setSuggestionStripShown(true); if (ProductionFlag.IS_EXPERIMENTAL) { @@ -1119,7 +1127,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mSettings.getCurrent().mBigramPredictionEnabled) { clearSuggestionStrip(); } else { - setSuggestionStrip(mSettings.getCurrent().mSuggestPuncList, false); + setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false); } mConnection.resetCachesUponCursorMove(newCursorPosition); } @@ -1393,13 +1401,28 @@ public final class LatinIME extends InputMethodService implements KeyboardAction ResearchLogger.getInstance().onResearchKeySelected(this); } break; - case Constants.CODE_ACTION_ENTER: - if (EditorInfo.IME_ACTION_NONE != mActionId - && EditorInfo.IME_ACTION_UNSPECIFIED != mActionId) { - performEditorAction(mActionId); - break; + case Constants.CODE_ENTER: + final EditorInfo editorInfo = getCurrentInputEditorInfo(); + final int imeOptionsActionId = + InputTypeUtils.getImeOptionsActionIdFromEditorInfo(editorInfo); + if (InputTypeUtils.IME_ACTION_CUSTOM_LABEL == imeOptionsActionId) { + // Either we have an actionLabel and we should performEditorAction with actionId + // regardless of its value. + performEditorAction(editorInfo.actionId); + } else if (EditorInfo.IME_ACTION_NONE != imeOptionsActionId) { + // We didn't have an actionLabel, but we had another action to execute. + // EditorInfo.IME_ACTION_NONE explicitly means no action. In contrast, + // EditorInfo.IME_ACTION_UNSPECIFIED is the default value for an action, so it + // means there should be an action and the app didn't bother to set a specific + // code for it - presumably it only handles one. It does not have to be treated + // in any specific way: anything that is not IME_ACTION_NONE should be sent to + // performEditorAction. + performEditorAction(imeOptionsActionId); + } else { + // No action label, and the action from imeOptions is NONE: this is a regular + // enter key that should input a carriage return. + didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState); } - didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState); break; case Constants.CODE_SHIFT_ENTER: didAutoCorrect = handleNonSpecialCharacter(Constants.CODE_ENTER, x, y, spaceState); @@ -1967,8 +1990,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // Outside LatinIME, only used by the test suite. @UsedForTesting boolean isShowingPunctuationList() { - if (mSuggestionStripView == null) return false; - return mSettings.getCurrent().mSuggestPuncList == mSuggestionStripView.getSuggestions(); + if (mSuggestedWords == null) return false; + return mSettings.getCurrent().mSuggestPuncList == mSuggestedWords; } private boolean isSuggestionsStripVisible() { @@ -1984,11 +2007,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } private void clearSuggestionStrip() { - setSuggestionStrip(SuggestedWords.EMPTY, false); + setSuggestedWords(SuggestedWords.EMPTY, false); setAutoCorrectionIndicator(false); } - private void setSuggestionStrip(final SuggestedWords words, final boolean isAutoCorrection) { + private void setSuggestedWords(final SuggestedWords words, final boolean isAutoCorrection) { + mSuggestedWords = words; if (mSuggestionStripView != null) { mSuggestionStripView.setSuggestions(words); mKeyboardSwitcher.onAutoCorrectionStateChanged(isAutoCorrection); @@ -2071,15 +2095,16 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } private SuggestedWords getOlderSuggestions(final String typedWord) { - SuggestedWords previousSuggestions = mSuggestionStripView.getSuggestions(); - if (previousSuggestions == mSettings.getCurrent().mSuggestPuncList) { - previousSuggestions = SuggestedWords.EMPTY; + SuggestedWords previousSuggestedWords = mSuggestedWords; + if (previousSuggestedWords == mSettings.getCurrent().mSuggestPuncList) { + previousSuggestedWords = SuggestedWords.EMPTY; } if (typedWord == null) { - return previousSuggestions; + return previousSuggestedWords; } final ArrayList<SuggestedWords.SuggestedWordInfo> typedWordAndPreviousSuggestions = - SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, previousSuggestions); + SuggestedWords.getTypedWordAndPreviousSuggestions(typedWord, + previousSuggestedWords); return new SuggestedWords(typedWordAndPreviousSuggestions, false /* typedWordValid */, false /* hasAutoCorrectionCandidate */, @@ -2101,7 +2126,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } mWordComposer.setAutoCorrection(autoCorrection); final boolean isAutoCorrection = suggestedWords.willAutoCorrect(); - setSuggestionStrip(suggestedWords, isAutoCorrection); + setSuggestedWords(suggestedWords, isAutoCorrection); setAutoCorrectionIndicator(isAutoCorrection); setSuggestionStripShown(isSuggestionsStripVisible()); } @@ -2124,7 +2149,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction Stats.onAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer); } if (ProductionFlag.IS_EXPERIMENTAL) { - final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); + final SuggestedWords suggestedWords = mSuggestedWords; ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection, separatorString, mWordComposer.isBatchMode(), suggestedWords); } @@ -2149,7 +2174,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // interface @Override public void pickSuggestionManually(final int index, final String suggestion) { - final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); + final SuggestedWords suggestedWords = mSuggestedWords; // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput if (suggestion.length() == 1 && isShowingPunctuationList()) { // Word separators are suggested before the user inputs something. @@ -2181,6 +2206,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mSettings.getCurrent().isApplicationSpecifiedCompletionsOn() && mApplicationSpecifiedCompletions != null && index >= 0 && index < mApplicationSpecifiedCompletions.length) { + mSuggestedWords = SuggestedWords.EMPTY; if (mSuggestionStripView != null) { mSuggestionStripView.clear(); } @@ -2236,7 +2262,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction */ private void commitChosenWord(final String chosenWord, final int commitType, final String separatorString) { - final SuggestedWords suggestedWords = mSuggestionStripView.getSuggestions(); + final SuggestedWords suggestedWords = mSuggestedWords; mConnection.commitText(SuggestionSpanUtils.getTextWithSuggestionSpan( this, chosenWord, suggestedWords, mIsMainDictionaryAvailable), 1); // Add the word to the user history dictionary @@ -2253,7 +2279,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction if (mSettings.getCurrent().mBigramPredictionEnabled) { clearSuggestionStrip(); } else { - setSuggestionStrip(mSettings.getCurrent().mSuggestPuncList, false); + setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false); } setAutoCorrectionIndicator(false); setSuggestionStripShown(isSuggestionsStripVisible()); @@ -2550,6 +2576,12 @@ public final class LatinIME extends InputMethodService implements KeyboardAction dialog.show(); } + // TODO: can this be removed somehow without breaking the tests? + @UsedForTesting + /* package for test */ String getFirstSuggestedWord() { + return mSuggestedWords.size() > 0 ? mSuggestedWords.getWord(0) : null; + } + public void debugDumpStateAndCrashWithException(final String context) { final StringBuilder s = new StringBuilder(); s.append("Target application : ").append(mTargetApplicationInfo.name) diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java index e4e8b94b2..3f2b0a3f4 100644 --- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java +++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java @@ -37,6 +37,10 @@ public final class LatinImeLogger implements SharedPreferences.OnSharedPreferenc public static void commit() { } + public static boolean getUsabilityStudyMode(final SharedPreferences prefs) { + return false; + } + public static void onDestroy() { } diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java index fcf727041..5fde8158a 100644 --- a/java/src/com/android/inputmethod/latin/LocaleUtils.java +++ b/java/src/com/android/inputmethod/latin/LocaleUtils.java @@ -180,14 +180,15 @@ public final class LocaleUtils { synchronized (sLockForRunInLocale) { final Configuration conf = res.getConfiguration(); final Locale oldLocale = conf.locale; + final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale)); try { - if (newLocale != null && !newLocale.equals(oldLocale)) { + if (needsChange) { conf.locale = newLocale; res.updateConfiguration(conf, null); } return job(res); } finally { - if (newLocale != null && !newLocale.equals(oldLocale)) { + if (needsChange) { conf.locale = oldLocale; res.updateConfiguration(conf, null); } diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 02b44c7f6..435074bdb 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -18,6 +18,7 @@ package com.android.inputmethod.latin; import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; import android.content.res.Resources; import android.preference.PreferenceManager; @@ -64,6 +65,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail"; public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT = "pref_gesture_floating_preview_text"; + public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon"; public static final String PREF_INPUT_LANGUAGE = "input_language"; public static final String PREF_SELECTED_LANGUAGES = "selected_languages"; @@ -260,4 +262,16 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static boolean readUseFullscreenMode(final Resources res) { return res.getBoolean(R.bool.config_use_fullscreen_mode); } + + public static boolean readShowSetupWizardIcon(final SharedPreferences prefs, + final Context context) { + if (!prefs.contains(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) { + final ApplicationInfo appInfo = context.getApplicationInfo(); + final boolean isApplicationInSystemImage = + (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + // Default value + return !isApplicationInSystemImage; + } + return prefs.getBoolean(Settings.PREF_SHOW_SETUP_WIZARD_ICON, false); + } } diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java index edd064c0b..4c90e485a 100644 --- a/java/src/com/android/inputmethod/latin/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java @@ -31,6 +31,7 @@ import android.preference.PreferenceScreen; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager; import com.android.inputmethodcommon.InputMethodSettingsFragment; public final class SettingsFragment extends InputMethodSettingsFragment @@ -155,6 +156,10 @@ public final class SettingsFragment extends InputMethodSettingsFragment removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen()); } + final CheckBoxPreference showSetupWizardIcon = + (CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON); + showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, context)); + setupKeyLongpressTimeoutSettings(prefs, res); setupKeypressVibrationDurationSettings(prefs, res); setupKeypressSoundVolumeSettings(prefs, res); @@ -196,6 +201,8 @@ public final class SettingsFragment extends InputMethodSettingsFragment final boolean gestureInputEnabled = Settings.readGestureInputEnabled(prefs, res); setPreferenceEnabled(Settings.PREF_GESTURE_PREVIEW_TRAIL, gestureInputEnabled); setPreferenceEnabled(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, gestureInputEnabled); + } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) { + LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity()); } ensureConsistencyOfAutoCorrectionSettings(); updateVoiceModeSummary(); diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java index 81bc9f5d7..528028328 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java @@ -263,9 +263,10 @@ public final class UserHistoryDictionary extends ExpandableDictionary { UserHistoryDictIOUtils.readDictionaryBinary( new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener); } catch (FileNotFoundException e) { - Log.e(TAG, "when loading: file not found" + e); + // This is an expected condition: we don't have a user history dictionary for this + // language yet. It will be created sometime later. } catch (IOException e) { - Log.e(TAG, "IOException when open bytebuffer: " + e); + Log.e(TAG, "IOException on opening a bytebuffer", e); } finally { if (inStream != null) { try { @@ -328,7 +329,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { Thread.sleep(15000); Log.w(TAG, "End stress in closing"); } catch (InterruptedException e) { - Log.e(TAG, "In stress test: " + e); + Log.e(TAG, "In stress test", e); } } @@ -343,7 +344,7 @@ public final class UserHistoryDictionary extends ExpandableDictionary { out.flush(); out.close(); } catch (IOException e) { - Log.e(TAG, "IO Exception while writing file: " + e); + Log.e(TAG, "IO Exception while writing file", e); } finally { if (out != null) { try { diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java index acfcd5354..7a604dc6a 100644 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ b/java/src/com/android/inputmethod/latin/Utils.java @@ -28,6 +28,7 @@ import android.os.Process; import android.text.TextUtils; import android.util.Log; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import java.io.BufferedReader; @@ -77,6 +78,7 @@ public final class Utils { private RingCharBuffer() { // Intentional empty constructor for singleton. } + @UsedForTesting public static RingCharBuffer getInstance() { return sRingCharBuffer; } @@ -93,6 +95,7 @@ public final class Utils { return ret < 0 ? ret + BUFSIZE : ret; } // TODO: accept code points + @UsedForTesting public void push(char c, int x, int y) { if (!mEnabled) return; mCharBuf[mEnd] = c; diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 31f616dd9..f7cb4346a 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; @@ -177,7 +178,8 @@ public final class WordComposer { /** * Internal method to retrieve reasonable proximity info for a character. */ - private void addKeyInfo(final int codePoint, final Keyboard keyboard) { + @UsedForTesting + public void addKeyInfo(final int codePoint, final Keyboard keyboard) { final int x, y; final Key key; if (keyboard != null && (key = keyboard.getKey(codePoint)) != null) { diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index 83acca874..e1e5e5500 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -258,6 +258,8 @@ public final class FormatSpec { public final FormatOptions mFormatOptions; private static final String DICTIONARY_VERSION_ATTRIBUTE = "version"; private static final String DICTIONARY_LOCALE_ATTRIBUTE = "locale"; + private static final String DICTIONARY_ID_ATTRIBUTE = "dictionary"; + private static final String DICTIONARY_DESCRIPTION_ATTRIBUTE = "description"; public FileHeader(final int headerSize, final DictionaryOptions dictionaryOptions, final FormatOptions formatOptions) { mHeaderSize = headerSize; @@ -274,6 +276,18 @@ public final class FormatSpec { public String getVersion() { return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_VERSION_ATTRIBUTE); } + + // Helper method to get the dictionary ID as a String + public String getId() { + return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_ID_ATTRIBUTE); + } + + // Helper method to get the description + 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. + return mDictionaryOptions.mAttributes.get(FileHeader.DICTIONARY_DESCRIPTION_ATTRIBUTE); + } } private FormatSpec() { diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java new file mode 100644 index 000000000..1b893a65d --- /dev/null +++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.setup; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.os.Process; +import android.preference.PreferenceManager; +import android.util.Log; + +import com.android.inputmethod.compat.IntentCompatUtils; +import com.android.inputmethod.latin.RichInputMethodManager; +import com.android.inputmethod.latin.Settings; + +/** + * This class detects the {@link Intent#ACTION_MY_PACKAGE_REPLACED} broadcast intent when this IME + * package has been replaced by a newer version of the same package. This class also detects + * {@link Intent#ACTION_BOOT_COMPLETED} and {@link Intent#ACTION_USER_INITIALIZE} broadcast intent. + * + * If this IME has already been installed in the system image and a new version of this IME has + * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver and it + * will hide the setup wizard's icon. + * + * If this IME has already been installed in the data partition and a new version of this IME has + * been installed, {@link Intent#ACTION_MY_PACKAGE_REPLACED} is received by this receiver but it + * will not hide the setup wizard's icon, and the icon will appear on the launcher. + * + * If this IME hasn't been installed yet and has been newly installed, no + * {@link Intent#ACTION_MY_PACKAGE_REPLACED} will be sent and the setup wizard's icon will appear + * on the launcher. + * + * When the device has been booted, {@link Intent#ACTION_BOOT_COMPLETED} is received by this + * receiver and it checks whether the setup wizard's icon should be appeared or not on the launcher + * depending on which partition this IME is installed. + * + * When a multiuser account has been created, {@link Intent#ACTION_USER_INITIALIZE} is received + * by this receiver and it checks the whether the setup wizard's icon should be appeared or not on + * the launcher depending on which partition this IME is installed. + */ +public final class LauncherIconVisibilityManager extends BroadcastReceiver { + private static final String TAG = LauncherIconVisibilityManager.class.getSimpleName(); + + @Override + public void onReceive(final Context context, final Intent intent) { + if (shouldHandleThisIntent(intent, context)) { + updateSetupWizardIconVisibility(context); + } + + // The process that hosts this broadcast receiver is invoked and remains alive even after + // 1) the package has been re-installed, 2) the device has been booted, + // 3) a multiuser has been created. + // There is no good reason to keep the process alive if this IME isn't a current IME. + RichInputMethodManager.init(context); + if (!SetupActivity.isThisImeCurrent(context)) { + final int myPid = Process.myPid(); + Log.i(TAG, "Killing my process: pid=" + myPid); + Process.killProcess(myPid); + } + } + + private static boolean shouldHandleThisIntent(final Intent intent, final Context context) { + final String action = intent.getAction(); + if (Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) { + Log.i(TAG, "Package has been replaced: " + context.getPackageName()); + return true; + } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { + Log.i(TAG, "Boot has been completed"); + return true; + } else if (IntentCompatUtils.has_ACTION_USER_INITIALIZE(intent)) { + Log.i(TAG, "User initialize"); + return true; + } + return false; + } + + public static void updateSetupWizardIconVisibility(final Context context) { + final ComponentName setupWizardActivity = new ComponentName(context, SetupActivity.class); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + final boolean stateHasSet; + if (Settings.readShowSetupWizardIcon(prefs, context)) { + stateHasSet = setActivityState(context, setupWizardActivity, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED); + Log.i(TAG, (stateHasSet ? "Enable activity: " : "Activity has already been enabled: ") + + setupWizardActivity); + } else { + stateHasSet = setActivityState(context, setupWizardActivity, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED); + Log.i(TAG, (stateHasSet ? "Disable activity: " : "Activity has already been disabled: ") + + setupWizardActivity); + } + } + + private static boolean setActivityState(final Context context, + final ComponentName activityComponent, final int activityState) { + final PackageManager pm = context.getPackageManager(); + final int activityComponentState = pm.getComponentEnabledSetting(activityComponent); + if (activityComponentState == activityState) { + return false; + } + pm.setComponentEnabledSetting( + activityComponent, activityState, PackageManager.DONT_KILL_APP); + return true; + } +} diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java index fab894584..e009fbc39 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java @@ -17,23 +17,341 @@ package com.android.inputmethod.latin.setup; import android.app.Activity; +import android.content.Context; import android.content.Intent; +import android.content.res.Resources; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.os.Message; +import android.provider.Settings; +import android.view.View; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.TextView; +import com.android.inputmethod.compat.TextViewCompatUtils; +import com.android.inputmethod.compat.ViewCompatUtils; +import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.SettingsActivity; +import com.android.inputmethod.latin.StaticInnerHandlerWrapper; + +import java.util.HashMap; public final class SetupActivity extends Activity { + private SetupStepIndicatorView mStepIndicatorView; + private final SetupStepGroup mSetupSteps = new SetupStepGroup(); + private static final String STATE_STEP = "step"; + private int mStepNumber; + private static final int STEP_1 = 1; + private static final int STEP_2 = 2; + private static final int STEP_3 = 3; + + private final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this); + + static final class SettingsPoolingHandler extends StaticInnerHandlerWrapper<SetupActivity> { + private static final int MSG_POLLING_IME_SETTINGS = 0; + private static final long IME_SETTINGS_POLLING_INTERVAL = 200; + + public SettingsPoolingHandler(final SetupActivity outerInstance) { + super(outerInstance); + } + + @Override + public void handleMessage(final Message msg) { + final SetupActivity setupActivity = getOuterInstance(); + switch (msg.what) { + case MSG_POLLING_IME_SETTINGS: + if (SetupActivity.isThisImeEnabled(setupActivity)) { + setupActivity.invokeSetupWizardOfThisIme(); + return; + } + startPollingImeSettings(); + break; + } + } + + public void startPollingImeSettings() { + sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS), + IME_SETTINGS_POLLING_INTERVAL); + } + + public void cancelPollingImeSettings() { + removeMessages(MSG_POLLING_IME_SETTINGS); + } + } + @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { + setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar); super.onCreate(savedInstanceState); - // TODO: Implement setup wizard. + setContentView(R.layout.setup_wizard); + + RichInputMethodManager.init(this); + + if (savedInstanceState == null) { + mStepNumber = determineSetupStepNumber(); + } else { + mStepNumber = savedInstanceState.getInt(STATE_STEP); + } + + if (mStepNumber == STEP_3) { + // This IME already has been enabled and set as current IME. + // TODO: Implement tutorial. + invokeSettingsOfThisIme(); + finish(); + return; + } + + // TODO: Use sans-serif-thin font family depending on the system locale white list and + // the SDK version. + final TextView titleView = (TextView)findViewById(R.id.setup_title); + titleView.setText(getString(R.string.setup_title, getString(R.string.english_ime_name))); + + mStepIndicatorView = (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator); + + final SetupStep step1 = new SetupStep(findViewById(R.id.setup_step1), + R.string.setup_step1_title, R.string.setup_step1_instruction, + R.drawable.ic_settings_language, R.string.language_settings); + step1.setAction(new Runnable() { + @Override + public void run() { + invokeLanguageAndInputSettings(); + mHandler.startPollingImeSettings(); + } + }); + mSetupSteps.addStep(STEP_1, step1); + + final SetupStep step2 = new SetupStep(findViewById(R.id.setup_step2), + R.string.setup_step2_title, R.string.setup_step2_instruction, + 0 /* actionIcon */, R.string.select_input_method); + step2.setAction(new Runnable() { + @Override + public void run() { + // Invoke input method picker. + RichInputMethodManager.getInstance().getInputMethodManager() + .showInputMethodPicker(); + } + }); + mSetupSteps.addStep(STEP_2, step2); + + final SetupStep step3 = new SetupStep(findViewById(R.id.setup_step3), + R.string.setup_step3_title, 0 /* instruction */, + R.drawable.sym_keyboard_language_switch, R.string.setup_step3_instruction); + step3.setAction(new Runnable() { + @Override + public void run() { + invokeSubtypeEnablerOfThisIme(); + } + }); + mSetupSteps.addStep(STEP_3, step3); + } + + private void invokeSetupWizardOfThisIme() { + final Intent intent = new Intent(); + intent.setClass(this, SetupActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } + + private void invokeSettingsOfThisIme() { final Intent intent = new Intent(); intent.setClass(this, SettingsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); - finish(); + } + + private void invokeLanguageAndInputSettings() { + final Intent intent = new Intent(); + intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + startActivity(intent); + } + + private void invokeSubtypeEnablerOfThisIme() { + final InputMethodInfo imi = + RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme(); + final Intent intent = new Intent(); + intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId()); + startActivity(intent); + } + + /** + * Check if the IME specified by the context is enabled. + * Note that {@link RichInputMethodManager} must have been initialized before calling this + * method. + * + * @param context package context of the IME to be checked. + * @return true if this IME is enabled. + */ + public static boolean isThisImeEnabled(final Context context) { + final String packageName = context.getPackageName(); + final InputMethodManager imm = RichInputMethodManager.getInstance().getInputMethodManager(); + for (final InputMethodInfo imi : imm.getEnabledInputMethodList()) { + if (packageName.equals(imi.getPackageName())) { + return true; + } + } + return false; + } + + /** + * Check if the IME specified by the context is the current IME. + * Note that {@link RichInputMethodManager} must have been initialized before calling this + * method. + * + * @param context package context of the IME to be checked. + * @return true if this IME is the current IME. + */ + public static boolean isThisImeCurrent(final Context context) { + final InputMethodInfo myImi = + RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme(); + final String currentImeId = Settings.Secure.getString( + context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); + return myImi.getId().equals(currentImeId); + } + + private int determineSetupStepNumber() { + mHandler.cancelPollingImeSettings(); + if (!isThisImeEnabled(this)) { + return STEP_1; + } + if (!isThisImeCurrent(this)) { + return STEP_2; + } + return STEP_3; + } + + @Override + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(STATE_STEP, mStepNumber); + } + + @Override + protected void onRestoreInstanceState(final Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mStepNumber = savedInstanceState.getInt(STATE_STEP); + } + + @Override + protected void onStart() { + super.onStart(); + mStepNumber = determineSetupStepNumber(); + } + + @Override + protected void onRestart() { + super.onRestart(); + mStepNumber = determineSetupStepNumber(); + } + + @Override + protected void onResume() { + super.onResume(); + updateSetupStepView(); + } + + @Override + public void onWindowFocusChanged(final boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (!hasFocus) { + return; + } + mStepNumber = determineSetupStepNumber(); + updateSetupStepView(); + } + + private void updateSetupStepView() { + final int layoutDirection = ViewCompatUtils.getLayoutDirection(mStepIndicatorView); + mStepIndicatorView.setIndicatorPosition( + getIndicatorPosition(mStepNumber, mSetupSteps.getTotalStep(), layoutDirection)); + mSetupSteps.enableStep(mStepNumber); + } + + private static float getIndicatorPosition(final int step, final int totalStep, + final int layoutDirection) { + final float pos = ((step - STEP_1) * 2 + 1) / (float)(totalStep * 2); + return (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos; + } + + static final class SetupStep implements View.OnClickListener { + private final View mRootView; + private final TextView mActionLabel; + private Runnable mAction; + + public SetupStep(final View rootView, final int title, final int instruction, + final int actionIcon, final int actionLabel) { + mRootView = rootView; + final Resources res = rootView.getResources(); + final String applicationName = res.getString(R.string.english_ime_name); + + final TextView titleView = (TextView)rootView.findViewById(R.id.setup_step_title); + titleView.setText(res.getString(title, applicationName)); + + final TextView instructionView = (TextView)rootView.findViewById( + R.id.setup_step_instruction); + if (instruction == 0) { + instructionView.setVisibility(View.GONE); + } else { + instructionView.setText(res.getString(instruction, applicationName)); + } + + mActionLabel = (TextView)rootView.findViewById(R.id.setup_step_action_label); + mActionLabel.setText(res.getString(actionLabel)); + if (actionIcon == 0) { + final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel); + ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0); + } else { + final int overrideColor = res.getColor(R.color.setup_text_action); + final Drawable icon = res.getDrawable(actionIcon); + icon.setColorFilter(overrideColor, PorterDuff.Mode.MULTIPLY); + icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); + TextViewCompatUtils.setCompoundDrawablesRelative( + mActionLabel, icon, null, null, null); + } + } + + public void setEnabled(final boolean enabled) { + mRootView.setVisibility(enabled ? View.VISIBLE : View.GONE); + } + + public void setAction(final Runnable action) { + mActionLabel.setOnClickListener(this); + mAction = action; + } + + @Override + public void onClick(final View v) { + if (mAction != null) { + mAction.run(); + } + } + } + + static final class SetupStepGroup { + private final HashMap<Integer, SetupStep> mGroup = CollectionUtils.newHashMap(); + + public void addStep(final int stepNo, final SetupStep step) { + mGroup.put(stepNo, step); + } + + public void enableStep(final int enableStepNo) { + for (final Integer stepNo : mGroup.keySet()) { + final SetupStep step = mGroup.get(stepNo); + step.setEnabled(stepNo == enableStepNo); + } + } + + public int getTotalStep() { + return mGroup.size(); + } } } diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java new file mode 100644 index 000000000..077a21793 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.setup; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.util.AttributeSet; +import android.view.View; + +import com.android.inputmethod.latin.R; + +public final class SetupStepIndicatorView extends View { + private final Path mIndicatorPath = new Path(); + private final Paint mIndicatorPaint = new Paint(); + private float mXRatio; + + public SetupStepIndicatorView(final Context context, final AttributeSet attrs) { + super(context, attrs); + mIndicatorPaint.setColor(getResources().getColor(R.color.setup_step_background)); + mIndicatorPaint.setStyle(Paint.Style.FILL); + } + + public void setIndicatorPosition(final float xRatio) { + mXRatio = xRatio; + invalidate(); + } + + @Override + protected void onDraw(final Canvas canvas) { + super.onDraw(canvas); + final int xPos = (int)(getWidth() * mXRatio); + final int height = getHeight(); + mIndicatorPath.rewind(); + mIndicatorPath.moveTo(xPos, 0); + mIndicatorPath.lineTo(xPos + height, height); + mIndicatorPath.lineTo(xPos - height, height); + mIndicatorPath.close(); + canvas.drawPath(mIndicatorPath, mIndicatorPaint); + } +} diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index bc51d5d62..5a29eee4e 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -644,10 +644,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick return false; } - public SuggestedWords getSuggestions() { - return mSuggestedWords; - } - public void clear() { mSuggestionsStrip.removeAllViews(); removeAllViews(); |