From e28eba5074664d5716b8e58b8d0a235746b261eb Mon Sep 17 00:00:00 2001 From: Ken Wakasa Date: Mon, 24 Jun 2013 01:11:32 +0900 Subject: Move util classes to the latin/utils directory Change-Id: I1c5b27c8edf231680edb8d96f63b9d04cfc6a6fa --- .../accessibility/AccessibilityEntityProvider.java | 4 +- .../accessibility/AccessibilityUtils.java | 2 +- .../accessibility/KeyCodeDescriptionMapper.java | 2 +- .../inputmethod/compat/SuggestionSpanUtils.java | 2 +- .../inputmethod/dictionarypack/ActionBatch.java | 51 +- .../DictionaryListInterfaceState.java | 2 +- .../dictionarypack/DictionaryProvider.java | 5 +- .../dictionarypack/MetadataDbHelper.java | 9 +- .../inputmethod/dictionarypack/UpdateHandler.java | 50 +- .../android/inputmethod/dictionarypack/Utils.java | 104 ----- .../inputmethod/event/EventInterpreter.java | 2 +- java/src/com/android/inputmethod/keyboard/Key.java | 2 +- .../com/android/inputmethod/keyboard/Keyboard.java | 2 +- .../android/inputmethod/keyboard/KeyboardId.java | 2 +- .../inputmethod/keyboard/KeyboardLayoutSet.java | 8 +- .../android/inputmethod/keyboard/KeyboardView.java | 2 +- .../inputmethod/keyboard/MainKeyboardView.java | 12 +- .../inputmethod/keyboard/MoreKeysKeyboard.java | 2 +- .../inputmethod/keyboard/MoreKeysKeyboardView.java | 2 +- .../inputmethod/keyboard/PointerTracker.java | 4 +- .../inputmethod/keyboard/ProximityInfo.java | 2 +- .../inputmethod/keyboard/TypefaceUtils.java | 2 +- .../internal/GestureFloatingPreviewText.java | 2 +- .../keyboard/internal/GestureStroke.java | 4 +- .../internal/GestureStrokeWithPreviewPoints.java | 2 +- .../keyboard/internal/GestureTrail.java | 2 +- .../keyboard/internal/GestureTrailsPreview.java | 4 +- .../keyboard/internal/KeyDrawParams.java | 2 +- .../keyboard/internal/KeySpecParser.java | 4 +- .../keyboard/internal/KeyStylesSet.java | 4 +- .../keyboard/internal/KeyVisualAttributes.java | 2 +- .../keyboard/internal/KeyboardBuilder.java | 8 +- .../keyboard/internal/KeyboardCodesSet.java | 2 +- .../keyboard/internal/KeyboardIconsSet.java | 2 +- .../keyboard/internal/KeyboardParams.java | 2 +- .../inputmethod/keyboard/internal/KeyboardRow.java | 2 +- .../keyboard/internal/KeyboardState.java | 2 +- .../keyboard/internal/KeyboardTextsSet.java | 2 +- .../inputmethod/keyboard/internal/KeysCache.java | 2 +- .../inputmethod/keyboard/internal/MoreKeySpec.java | 2 +- .../keyboard/internal/PointerTrackerQueue.java | 2 +- .../keyboard/internal/PreviewPlacerView.java | 4 +- .../keyboard/internal/SlidingKeyInputPreview.java | 2 +- .../keyboard/internal/TouchScreenRegulator.java | 2 +- .../latin/AdditionalFeaturesSettingUtils.java | 47 -- .../inputmethod/latin/AdditionalSubtype.java | 3 + .../latin/AdditionalSubtypeSettings.java | 3 + .../inputmethod/latin/BinaryDictionary.java | 4 + .../latin/BinaryDictionaryFileDumper.java | 5 +- .../inputmethod/latin/BinaryDictionaryGetter.java | 13 +- .../android/inputmethod/latin/BoundedTreeSet.java | 49 -- .../android/inputmethod/latin/CapsModeUtils.java | 266 ----------- .../android/inputmethod/latin/CollectionUtils.java | 100 ---- .../inputmethod/latin/CompletionInfoUtils.java | 43 -- .../latin/ContactsBinaryDictionary.java | 2 + .../android/inputmethod/latin/CoordinateUtils.java | 49 -- .../android/inputmethod/latin/DebugSettings.java | 1 + .../inputmethod/latin/DicTraverseSession.java | 2 + .../inputmethod/latin/DictionaryCollection.java | 5 +- .../inputmethod/latin/DictionaryFactory.java | 2 + .../inputmethod/latin/DictionaryInfoUtils.java | 365 --------------- .../DictionaryPackInstallBroadcastReceiver.java | 1 + .../latin/ExpandableBinaryDictionary.java | 1 + .../inputmethod/latin/ExpandableDictionary.java | 3 +- .../latin/ExternalDictionaryGetterForDebug.java | 3 + .../android/inputmethod/latin/FeedbackUtils.java | 37 -- .../android/inputmethod/latin/InputAttributes.java | 3 + .../android/inputmethod/latin/InputPointers.java | 1 + .../android/inputmethod/latin/InputTypeUtils.java | 117 ----- .../com/android/inputmethod/latin/IntentUtils.java | 45 -- .../com/android/inputmethod/latin/JniUtils.java | 41 -- .../inputmethod/latin/LastComposedWord.java | 2 + .../com/android/inputmethod/latin/LatinIME.java | 12 +- .../com/android/inputmethod/latin/LocaleUtils.java | 260 ----------- .../inputmethod/latin/MetadataFileUriGetter.java | 36 -- .../inputmethod/latin/NativeSuggestOptions.java | 2 + .../PositionalInfoForUserDictPendingAddition.java | 106 ----- .../inputmethod/latin/RecapitalizeStatus.java | 192 -------- .../inputmethod/latin/ResizableIntArray.java | 146 ------ .../android/inputmethod/latin/ResourceUtils.java | 292 ------------ .../inputmethod/latin/RichInputConnection.java | 7 +- .../inputmethod/latin/RichInputMethodManager.java | 1 + .../com/android/inputmethod/latin/Settings.java | 4 +- .../inputmethod/latin/SettingsFragment.java | 7 +- .../android/inputmethod/latin/SettingsValues.java | 4 + .../latin/StaticInnerHandlerWrapper.java | 42 -- .../com/android/inputmethod/latin/StringUtils.java | 317 ------------- .../android/inputmethod/latin/SubtypeLocale.java | 8 +- .../src/com/android/inputmethod/latin/Suggest.java | 3 + .../android/inputmethod/latin/SuggestedWords.java | 3 + .../latin/TargetPackageInfoGetterTask.java | 70 --- .../inputmethod/latin/UserBinaryDictionary.java | 1 + .../inputmethod/latin/UserHistoryDictIOUtils.java | 165 ------- .../inputmethod/latin/UserHistoryDictionary.java | 9 +- .../latin/UserHistoryDictionaryBigramList.java | 1 + .../latin/UserHistoryForgettingCurveUtils.java | 222 --------- java/src/com/android/inputmethod/latin/Utils.java | 511 --------------------- .../android/inputmethod/latin/XmlParseUtils.java | 83 ---- .../latin/setup/SetupWizardActivity.java | 4 +- .../spellcheck/AndroidSpellCheckerService.java | 6 +- .../spellcheck/AndroidSpellCheckerSession.java | 2 +- .../AndroidWordLevelSpellCheckerSession.java | 4 +- .../latin/spellcheck/DictionaryPool.java | 2 +- .../spellcheck/SpellCheckerSettingsFragment.java | 2 +- .../latin/suggestions/MoreSuggestions.java | 2 +- .../suggestions/SuggestionStripLayoutHelper.java | 4 +- .../latin/suggestions/SuggestionStripView.java | 2 +- .../UserDictionaryAddWordContents.java | 8 +- .../latin/userdictionary/UserDictionaryList.java | 6 +- .../utils/AdditionalFeaturesSettingUtils.java | 48 ++ .../inputmethod/latin/utils/BoundedTreeSet.java | 49 ++ .../inputmethod/latin/utils/CapsModeUtils.java | 269 +++++++++++ .../inputmethod/latin/utils/CollectionUtils.java | 100 ++++ .../latin/utils/CompletionInfoUtils.java | 43 ++ .../inputmethod/latin/utils/CoordinateUtils.java | 49 ++ .../android/inputmethod/latin/utils/CsvUtils.java | 1 - .../latin/utils/DictionaryInfoUtils.java | 366 +++++++++++++++ .../inputmethod/latin/utils/FeedbackUtils.java | 37 ++ .../inputmethod/latin/utils/InputTypeUtils.java | 117 +++++ .../inputmethod/latin/utils/IntentUtils.java | 45 ++ .../android/inputmethod/latin/utils/JniUtils.java | 41 ++ .../inputmethod/latin/utils/LocaleUtils.java | 261 +++++++++++ .../android/inputmethod/latin/utils/LogUtils.java | 115 +++++ .../latin/utils/MetadataFileUriGetter.java | 38 ++ .../PositionalInfoForUserDictPendingAddition.java | 108 +++++ .../latin/utils/RecapitalizeStatus.java | 190 ++++++++ .../inputmethod/latin/utils/ResizableIntArray.java | 146 ++++++ .../inputmethod/latin/utils/ResourceUtils.java | 292 ++++++++++++ .../latin/utils/StaticInnerHandlerWrapper.java | 42 ++ .../inputmethod/latin/utils/StringUtils.java | 319 +++++++++++++ .../latin/utils/TargetPackageInfoGetterTask.java | 70 +++ .../latin/utils/UserHistoryDictIOUtils.java | 166 +++++++ .../utils/UserHistoryForgettingCurveUtils.java | 222 +++++++++ .../com/android/inputmethod/latin/utils/Utils.java | 506 ++++++++++++++++++++ .../inputmethod/latin/utils/XmlParseUtils.java | 83 ++++ .../inputmethod/research/ResearchLogger.java | 2 +- 136 files changed, 3958 insertions(+), 3859 deletions(-) delete mode 100644 java/src/com/android/inputmethod/dictionarypack/Utils.java delete mode 100644 java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/BoundedTreeSet.java delete mode 100644 java/src/com/android/inputmethod/latin/CapsModeUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/CollectionUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/CompletionInfoUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/CoordinateUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/FeedbackUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/InputTypeUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/IntentUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/JniUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/LocaleUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java delete mode 100644 java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java delete mode 100644 java/src/com/android/inputmethod/latin/RecapitalizeStatus.java delete mode 100644 java/src/com/android/inputmethod/latin/ResizableIntArray.java delete mode 100644 java/src/com/android/inputmethod/latin/ResourceUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java delete mode 100644 java/src/com/android/inputmethod/latin/StringUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java delete mode 100644 java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java delete mode 100644 java/src/com/android/inputmethod/latin/Utils.java delete mode 100644 java/src/com/android/inputmethod/latin/XmlParseUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/AdditionalFeaturesSettingUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java create mode 100644 java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/CollectionUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/IntentUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/JniUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/LocaleUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/LogUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java create mode 100644 java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java create mode 100644 java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java create mode 100644 java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java create mode 100644 java/src/com/android/inputmethod/latin/utils/ResourceUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java create mode 100644 java/src/com/android/inputmethod/latin/utils/StringUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java create mode 100644 java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/Utils.java create mode 100644 java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java (limited to 'java/src') diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java index 0576f666c..77f323440 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityEntityProvider.java @@ -35,8 +35,8 @@ import android.view.inputmethod.EditorInfo; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.latin.CollectionUtils; -import com.android.inputmethod.latin.CoordinateUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.CoordinateUtils; /** * Exposes a virtual view sub-tree for {@link KeyboardView} and generates diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java index ee52de1d1..8929dc7e9 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java @@ -33,8 +33,8 @@ import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.EditorInfo; import com.android.inputmethod.compat.SettingsSecureCompatUtils; -import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.InputTypeUtils; public final class AccessibilityUtils { private static final String TAG = AccessibilityUtils.class.getSimpleName(); diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java index 05d8269b7..41f5b9a24 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -25,9 +25,9 @@ import android.view.inputmethod.EditorInfo; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java index 141e08a8d..e195a5406 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java @@ -23,10 +23,10 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.style.SuggestionSpan; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.lang.reflect.Field; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java index bf2230553..be9f87930 100644 --- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java +++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java @@ -28,6 +28,8 @@ import android.util.Log; import com.android.inputmethod.compat.DownloadManagerCompatUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.LogUtils; +import com.android.inputmethod.latin.utils.Utils; import java.util.LinkedList; import java.util.Queue; @@ -98,7 +100,7 @@ public final class ActionBatch { final boolean mForceStartNow; public StartDownloadAction(final String clientId, final WordListMetadata wordList, final boolean forceStartNow) { - Utils.l("New download action for client ", clientId, " : ", wordList); + LogUtils.l("New download action for client ", clientId, " : ", wordList); mClientId = clientId; mWordList = wordList; mForceStartNow = forceStartNow; @@ -110,7 +112,7 @@ public final class ActionBatch { Log.e(TAG, "UpdateAction with a null parameter!"); return; } - Utils.l("Downloading word list"); + LogUtils.l("Downloading word list"); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -132,7 +134,7 @@ public final class ActionBatch { + " for an upgrade action. Fall back to download."); } // Download it. - Utils.l("Upgrade word list, downloading", mWordList.mRemoteFilename); + LogUtils.l("Upgrade word list, downloading", mWordList.mRemoteFilename); // TODO: if DownloadManager is disabled or not installed, download by ourselves if (null == manager) return; @@ -142,7 +144,7 @@ public final class ActionBatch { // DownloadManager also stupidly cuts the extension to replace with its own that it // gets from the content-type. We need to circumvent this. final String disambiguator = "#" + System.currentTimeMillis() - + com.android.inputmethod.latin.Utils.getVersionName(context) + ".dict"; + + Utils.getVersionName(context) + ".dict"; final Uri uri = Uri.parse(mWordList.mRemoteFilename + disambiguator); final Request request = new Request(uri); @@ -178,7 +180,7 @@ public final class ActionBatch { final long downloadId = UpdateHandler.registerDownloadRequest(manager, request, db, mWordList.mId, mWordList.mVersion); - Utils.l("Starting download of", uri, "with id", downloadId); + LogUtils.l("Starting download of", uri, "with id", downloadId); PrivateLog.log("Starting download of " + uri + ", id : " + downloadId); } } @@ -195,7 +197,8 @@ public final class ActionBatch { public InstallAfterDownloadAction(final String clientId, final ContentValues wordListValues) { - Utils.l("New InstallAfterDownloadAction for client ", clientId, " : ", wordListValues); + LogUtils.l("New InstallAfterDownloadAction for client ", clientId, " : ", + wordListValues); mClientId = clientId; mWordListValues = wordListValues; } @@ -213,7 +216,7 @@ public final class ActionBatch { + " for an InstallAfterDownload action. Bailing out."); return; } - Utils.l("Setting word list as installed"); + LogUtils.l("Setting word list as installed"); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); MetadataDbHelper.markEntryAsFinishedDownloadingAndInstalled(db, mWordListValues); } @@ -229,7 +232,7 @@ public final class ActionBatch { final WordListMetadata mWordList; public EnableAction(final String clientId, final WordListMetadata wordList) { - Utils.l("New EnableAction for client ", clientId, " : ", wordList); + LogUtils.l("New EnableAction for client ", clientId, " : ", wordList); mClientId = clientId; mWordList = wordList; } @@ -240,7 +243,7 @@ public final class ActionBatch { Log.e(TAG, "EnableAction with a null parameter!"); return; } - Utils.l("Enabling word list"); + LogUtils.l("Enabling word list"); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -264,7 +267,7 @@ public final class ActionBatch { // The word list to disable. May not be null. final WordListMetadata mWordList; public DisableAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New Disable action for client ", clientId, " : ", wordlist); + LogUtils.l("New Disable action for client ", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -275,7 +278,7 @@ public final class ActionBatch { Log.e(TAG, "DisableAction with a null word list!"); return; } - Utils.l("Disabling word list : " + mWordList); + LogUtils.l("Disabling word list : " + mWordList); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -311,7 +314,7 @@ public final class ActionBatch { // The word list to make available. May not be null. final WordListMetadata mWordList; public MakeAvailableAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New MakeAvailable action", clientId, " : ", wordlist); + LogUtils.l("New MakeAvailable action", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -328,7 +331,7 @@ public final class ActionBatch { Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' " + " for a makeavailable action. Marking as available anyway."); } - Utils.l("Making word list available : " + mWordList); + LogUtils.l("Making word list available : " + mWordList); // If mLocalFilename is null, then it's a remote file that hasn't been downloaded // yet, so we set the local filename to the empty string. final ContentValues values = MetadataDbHelper.makeContentValues(0, @@ -360,7 +363,7 @@ public final class ActionBatch { // The word list to mark pre-installed. May not be null. final WordListMetadata mWordList; public MarkPreInstalledAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New MarkPreInstalled action", clientId, " : ", wordlist); + LogUtils.l("New MarkPreInstalled action", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -377,7 +380,7 @@ public final class ActionBatch { Log.e(TAG, "Unexpected state of the word list '" + mWordList.mId + "' " + " for a markpreinstalled action. Marking as preinstalled anyway."); } - Utils.l("Marking word list preinstalled : " + mWordList); + LogUtils.l("Marking word list preinstalled : " + mWordList); // This word list is pre-installed : we don't have its file. We should reset // the local file name to the empty string so that we don't try to open it // accidentally. The remote filename may be set by the application if it so wishes. @@ -401,7 +404,7 @@ public final class ActionBatch { private final String mClientId; final WordListMetadata mWordList; public UpdateDataAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New UpdateData action for client ", clientId, " : ", wordlist); + LogUtils.l("New UpdateData action for client ", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -419,7 +422,7 @@ public final class ActionBatch { Log.e(TAG, "Trying to update data about a non-existing word list. Bailing out."); return; } - Utils.l("Updating data about a word list : " + mWordList); + LogUtils.l("Updating data about a word list : " + mWordList); final ContentValues values = MetadataDbHelper.makeContentValues( oldValues.getAsInteger(MetadataDbHelper.PENDINGID_COLUMN), oldValues.getAsInteger(MetadataDbHelper.TYPE_COLUMN), @@ -453,7 +456,7 @@ public final class ActionBatch { final boolean mHasNewerVersion; public ForgetAction(final String clientId, final WordListMetadata wordlist, final boolean hasNewerVersion) { - Utils.l("New TryRemove action for client ", clientId, " : ", wordlist); + LogUtils.l("New TryRemove action for client ", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; mHasNewerVersion = hasNewerVersion; @@ -465,7 +468,7 @@ public final class ActionBatch { Log.e(TAG, "TryRemoveAction with a null word list!"); return; } - Utils.l("Trying to remove word list : " + mWordList); + LogUtils.l("Trying to remove word list : " + mWordList); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -525,7 +528,7 @@ public final class ActionBatch { // The word list to delete. May not be null. final WordListMetadata mWordList; public StartDeleteAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New StartDelete action for client ", clientId, " : ", wordlist); + LogUtils.l("New StartDelete action for client ", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -536,7 +539,7 @@ public final class ActionBatch { Log.e(TAG, "StartDeleteAction with a null word list!"); return; } - Utils.l("Trying to delete word list : " + mWordList); + LogUtils.l("Trying to delete word list : " + mWordList); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -564,7 +567,7 @@ public final class ActionBatch { // The word list to delete. May not be null. final WordListMetadata mWordList; public FinishDeleteAction(final String clientId, final WordListMetadata wordlist) { - Utils.l("New FinishDelete action for client", clientId, " : ", wordlist); + LogUtils.l("New FinishDelete action for client", clientId, " : ", wordlist); mClientId = clientId; mWordList = wordlist; } @@ -575,7 +578,7 @@ public final class ActionBatch { Log.e(TAG, "FinishDeleteAction with a null word list!"); return; } - Utils.l("Trying to delete word list : " + mWordList); + LogUtils.l("Trying to delete word list : " + mWordList); final SQLiteDatabase db = MetadataDbHelper.getDb(context, mClientId); final ContentValues values = MetadataDbHelper.getContentValuesByWordListId(db, mWordList.mId, mWordList.mVersion); @@ -632,7 +635,7 @@ public final class ActionBatch { * @param reporter a Reporter to send errors to. */ public void execute(final Context context, final ProblemReporter reporter) { - Utils.l("Executing a batch of actions"); + LogUtils.l("Executing a batch of actions"); Queue remainingActions = mActions; while (!remainingActions.isEmpty()) { final Action a = remainingActions.poll(); diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java index 5ad5900d4..f1a2a8333 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java @@ -18,7 +18,7 @@ package com.android.inputmethod.dictionarypack; import android.view.View; -import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.HashMap; diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java index 4fbe16233..4aa4d4b43 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java @@ -31,6 +31,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.LogUtils; import java.io.File; import java.io.FileNotFoundException; @@ -219,7 +220,7 @@ public final class DictionaryProvider extends ContentProvider { @Override public Cursor query(final Uri uri, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) { - Utils.l("Uri =", uri); + LogUtils.l("Uri =", uri); PrivateLog.log("Query : " + uri); final String clientId = getClientId(uri); final int match = matchUri(uri); @@ -227,7 +228,7 @@ public final class DictionaryProvider extends ContentProvider { case DICTIONARY_V1_WHOLE_LIST: case DICTIONARY_V2_WHOLE_LIST: final Cursor c = MetadataDbHelper.queryDictionaries(getContext(), clientId); - Utils.l("List of dictionaries with count", c.getCount()); + LogUtils.l("List of dictionaries with count", c.getCount()); PrivateLog.log("Returned a list of " + c.getCount() + " items"); return c; case DICTIONARY_V2_DICT_INFO: diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java index dac12137d..c6cadb823 100644 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java +++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java @@ -25,6 +25,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.LogUtils; import java.io.File; import java.util.ArrayList; @@ -772,12 +773,12 @@ public class MetadataDbHelper extends SQLiteOpenHelper { if (TextUtils.isEmpty(valuesClientId) || null == valuesMetadataUri || null == valuesMetadataAdditionalId) { // We need all these columns to be filled in - Utils.l("Missing parameter for updateClientInfo"); + LogUtils.l("Missing parameter for updateClientInfo"); return; } if (!clientId.equals(valuesClientId)) { // Mismatch! The client violates the protocol. - Utils.l("Received an updateClientInfo request for ", clientId, " but the values " + LogUtils.l("Received an updateClientInfo request for ", clientId, " but the values " + "contain a different ID : ", valuesClientId); return; } @@ -847,7 +848,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { final ContentValues r) { switch (r.getAsInteger(TYPE_COLUMN)) { case TYPE_BULK: - Utils.l("Ended processing a wordlist"); + LogUtils.l("Ended processing a wordlist"); // Updating a bulk word list is a three-step operation: // - Add the new entry to the table // - Remove the old entry from the table @@ -869,7 +870,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { // the phone is suddenly cut during an update. final int filenameIndex = c.getColumnIndex(LOCAL_FILENAME_COLUMN); do { - Utils.l("Setting for removal", c.getString(filenameIndex)); + LogUtils.l("Setting for removal", c.getString(filenameIndex)); filenames.add(c.getString(filenameIndex)); } while (c.moveToNext()); } diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java index 3f917f13f..936d00d25 100644 --- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java +++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java @@ -38,6 +38,8 @@ import android.util.Log; import com.android.inputmethod.compat.ConnectivityManagerCompatUtils; import com.android.inputmethod.compat.DownloadManagerCompatUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.LogUtils; +import com.android.inputmethod.latin.utils.Utils; import java.io.File; import java.io.FileInputStream; @@ -183,8 +185,8 @@ public final class UpdateHandler { final String clientId = cursor.getString(0); final String metadataUri = MetadataDbHelper.getMetadataUriAsString(context, clientId); - PrivateLog.log("Update for clientId " + Utils.s(clientId)); - Utils.l("Update for clientId", clientId, " which uses URI ", metadataUri); + PrivateLog.log("Update for clientId " + LogUtils.s(clientId)); + LogUtils.l("Update for clientId", clientId, " which uses URI ", metadataUri); uris.add(metadataUri); } while (cursor.moveToNext()); } finally { @@ -211,14 +213,14 @@ public final class UpdateHandler { */ private static void updateClientsWithMetadataUri(final Context context, final boolean updateNow, final String metadataUri) { - PrivateLog.log("Update for metadata URI " + Utils.s(metadataUri)); + PrivateLog.log("Update for metadata URI " + LogUtils.s(metadataUri)); // Adding a disambiguator to circumvent a bug in older versions of DownloadManager. // DownloadManager also stupidly cuts the extension to replace with its own that it // gets from the content-type. We need to circumvent this. final String disambiguator = "#" + System.currentTimeMillis() - + com.android.inputmethod.latin.Utils.getVersionName(context) + ".json"; + + Utils.getVersionName(context) + ".json"; final Request metadataRequest = new Request(Uri.parse(metadataUri + disambiguator)); - Utils.l("Request =", metadataRequest); + LogUtils.l("Request =", metadataRequest); final Resources res = context.getResources(); // By default, download over roaming is allowed and all network types are allowed too. @@ -254,7 +256,7 @@ public final class UpdateHandler { final long downloadId; synchronized (sSharedIdProtector) { downloadId = manager.enqueue(metadataRequest); - Utils.l("Metadata download requested with id", downloadId); + LogUtils.l("Metadata download requested with id", downloadId); // If there is already a download in progress, it's been there for a while and // there is probably something wrong with download manager. It's best to just // overwrite the id and request it again. If the old one happens to finish @@ -326,11 +328,11 @@ public final class UpdateHandler { */ public static long registerDownloadRequest(final DownloadManager manager, final Request request, final SQLiteDatabase db, final String id, final int version) { - Utils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version); + LogUtils.l("RegisterDownloadRequest for word list id : ", id, ", version ", version); final long downloadId; synchronized (sSharedIdProtector) { downloadId = manager.enqueue(request); - Utils.l("Download requested with id", downloadId); + LogUtils.l("Download requested with id", downloadId); MetadataDbHelper.markEntryAsDownloading(db, id, version, downloadId); } return downloadId; @@ -416,7 +418,7 @@ public final class UpdateHandler { // Get and check the ID of the file that was downloaded final long fileId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, NOT_AN_ID); PrivateLog.log("Download finished with id " + fileId); - Utils.l("DownloadFinished with id", fileId); + LogUtils.l("DownloadFinished with id", fileId); if (NOT_AN_ID == fileId) return; // Spurious wake-up: ignore final DownloadManager manager = @@ -426,7 +428,7 @@ public final class UpdateHandler { final ArrayList recordList = getDownloadRecordsForCompletedDownloadInfo(context, downloadInfo); if (null == recordList) return; // It was someone else's download. - Utils.l("Received result for download ", fileId); + LogUtils.l("Received result for download ", fileId); // TODO: handle gracefully a null pointer here. This is practically impossible because // we come here only when DownloadManager explicitly called us when it ended a @@ -503,7 +505,7 @@ public final class UpdateHandler { private static void publishUpdateCycleCompletedEvent(final Context context) { // Even if this is not successful, we have to publish the new state. PrivateLog.log("Publishing update cycle completed event"); - Utils.l("Publishing update cycle completed event"); + LogUtils.l("Publishing update cycle completed event"); for (UpdateEventListener listener : linkedCopyOfList(sUpdateEventListeners)) { listener.updateCycleCompleted(); } @@ -517,12 +519,12 @@ public final class UpdateHandler { // {@link handleWordList(Context,InputStream,ContentValues)}. // Handle the downloaded file according to its type if (downloadRecord.isMetadata()) { - Utils.l("Data D/L'd is metadata for", downloadRecord.mClientId); + LogUtils.l("Data D/L'd is metadata for", downloadRecord.mClientId); // #handleMetadata() closes its InputStream argument handleMetadata(context, new ParcelFileDescriptor.AutoCloseInputStream( manager.openDownloadedFile(fileId)), downloadRecord.mClientId); } else { - Utils.l("Data D/L'd is a word list"); + LogUtils.l("Data D/L'd is a word list"); final int wordListStatus = downloadRecord.mAttributes.getAsInteger( MetadataDbHelper.STATUS_COLUMN); if (MetadataDbHelper.STATUS_DOWNLOADING == wordListStatus) { @@ -582,7 +584,7 @@ public final class UpdateHandler { */ private static void handleMetadata(final Context context, final InputStream stream, final String clientId) throws IOException, BadFormatException { - Utils.l("Entering handleMetadata"); + LogUtils.l("Entering handleMetadata"); final List newMetadata; final InputStreamReader reader = new InputStreamReader(stream); try { @@ -592,7 +594,7 @@ public final class UpdateHandler { reader.close(); } - Utils.l("Downloaded metadata :", newMetadata); + LogUtils.l("Downloaded metadata :", newMetadata); PrivateLog.log("Downloaded metadata\n" + newMetadata); final ActionBatch actions = computeUpgradeTo(context, clientId, newMetadata); @@ -617,7 +619,7 @@ public final class UpdateHandler { // DownloadManager does not have the ability to put the file directly where we want // it, so we had it download to a temporary place. Now we move it. It will be deleted // automatically by DownloadManager. - Utils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString( + LogUtils.l("Downloaded a new word list :", downloadRecord.mAttributes.getAsString( MetadataDbHelper.DESCRIPTION_COLUMN), "for", downloadRecord.mClientId); PrivateLog.log("Downloaded a new word list with description : " + downloadRecord.mAttributes.getAsString(MetadataDbHelper.DESCRIPTION_COLUMN) @@ -676,9 +678,9 @@ public final class UpdateHandler { */ private static void copyFile(final InputStream in, final OutputStream out) throws IOException { - Utils.l("Copying files"); + LogUtils.l("Copying files"); if (!(in instanceof FileInputStream) || !(out instanceof FileOutputStream)) { - Utils.l("Not the right types"); + LogUtils.l("Not the right types"); copyFileFallback(in, out); } else { try { @@ -687,7 +689,7 @@ public final class UpdateHandler { sourceChannel.transferTo(0, Integer.MAX_VALUE, destinationChannel); } catch (IOException e) { // Can't work with channels, or something went wrong. Copy by hand. - Utils.l("Won't work"); + LogUtils.l("Won't work"); copyFileFallback(in, out); } } @@ -702,7 +704,7 @@ public final class UpdateHandler { */ private static void copyFileFallback(final InputStream in, final OutputStream out) throws IOException { - Utils.l("Falling back to slow copy"); + LogUtils.l("Falling back to slow copy"); final byte[] buffer = new byte[FILE_COPY_BUFFER_SIZE]; for (int readBytes = in.read(buffer); readBytes >= 0; readBytes = in.read(buffer)) out.write(buffer, 0, readBytes); @@ -717,10 +719,10 @@ public final class UpdateHandler { */ private static String getTempFileName(final Context context, final String locale) throws IOException { - Utils.l("Entering openTempFileOutput"); + LogUtils.l("Entering openTempFileOutput"); final File dir = context.getFilesDir(); final File f = File.createTempFile(locale + "___", DICT_FILE_SUFFIX, dir); - Utils.l("File name is", f.getName()); + LogUtils.l("File name is", f.getName()); return f.getName(); } @@ -741,7 +743,7 @@ public final class UpdateHandler { final String clientId, List from, List to) { final ActionBatch actions = new ActionBatch(); // Upgrade existing word lists - Utils.l("Comparing dictionaries"); + LogUtils.l("Comparing dictionaries"); final Set wordListIds = new TreeSet(); // TODO: Can these be null? if (null == from) from = new ArrayList(); @@ -756,7 +758,7 @@ public final class UpdateHandler { final WordListMetadata newInfo = null == metadataInfo || metadataInfo.mFormatVersion > MAXIMUM_SUPPORTED_FORMAT_VERSION ? null : metadataInfo; - Utils.l("Considering updating ", id, "currentInfo =", currentInfo); + LogUtils.l("Considering updating ", id, "currentInfo =", currentInfo); if (null == currentInfo && null == newInfo) { // This may happen if a new word list appeared that we can't handle. diff --git a/java/src/com/android/inputmethod/dictionarypack/Utils.java b/java/src/com/android/inputmethod/dictionarypack/Utils.java deleted file mode 100644 index c4a42dbbf..000000000 --- a/java/src/com/android/inputmethod/dictionarypack/Utils.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.dictionarypack; - -import android.util.Log; - -/** - * A class for various utility methods, especially debugging. - */ -public final class Utils { - private final static String TAG = Utils.class.getSimpleName() + ":DEBUG --"; - private final static boolean DEBUG = DictionaryProvider.DEBUG; - - /** - * Calls .toString() on its non-null argument or returns "null" - * @param o the object to convert to a string - * @return the result of .toString() or null - */ - public static String s(final Object o) { - return null == o ? "null" : o.toString(); - } - - /** - * Get the string representation of the current stack trace, for debugging purposes. - * @return a readable, carriage-return-separated string for the current stack trace. - */ - public static String getStackTrace() { - final StringBuilder sb = new StringBuilder(); - try { - throw new RuntimeException(); - } catch (RuntimeException e) { - StackTraceElement[] frames = e.getStackTrace(); - // Start at 1 because the first frame is here and we don't care about it - for (int j = 1; j < frames.length; ++j) { - sb.append(frames[j].toString() + "\n"); - } - } - return sb.toString(); - } - - /** - * Get the stack trace contained in an exception as a human-readable string. - * @param e the exception - * @return the human-readable stack trace - */ - public static String getStackTrace(final Exception e) { - final StringBuilder sb = new StringBuilder(); - final StackTraceElement[] frames = e.getStackTrace(); - for (int j = 0; j < frames.length; ++j) { - sb.append(frames[j].toString() + "\n"); - } - return sb.toString(); - } - - /** - * Helper log method to ease null-checks and adding spaces. - * - * This sends all arguments to the log, separated by spaces. Any null argument is converted - * to the "null" string. It uses a very visible tag and log level for debugging purposes. - * - * @param args the stuff to send to the log - */ - public static void l(final Object... args) { - if (!DEBUG) return; - final StringBuilder sb = new StringBuilder(); - for (final Object o : args) { - sb.append(s(o).toString()); - sb.append(" "); - } - Log.e(TAG, sb.toString()); - } - - /** - * Helper log method to put stuff in red. - * - * This does the same as #l but prints in red - * - * @param args the stuff to send to the log - */ - public static void r(final Object... args) { - if (!DEBUG) return; - final StringBuilder sb = new StringBuilder("\u001B[31m"); - for (final Object o : args) { - sb.append(s(o).toString()); - sb.append(" "); - } - sb.append("\u001B[0m"); - Log.e(TAG, sb.toString()); - } -} diff --git a/java/src/com/android/inputmethod/event/EventInterpreter.java b/java/src/com/android/inputmethod/event/EventInterpreter.java index 6efe899bb..726b9206b 100644 --- a/java/src/com/android/inputmethod/event/EventInterpreter.java +++ b/java/src/com/android/inputmethod/event/EventInterpreter.java @@ -19,9 +19,9 @@ package com.android.inputmethod.event; import android.util.SparseArray; import android.view.KeyEvent; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 4ef8653f6..09f1145e9 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -41,7 +41,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardRow; import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.utils.StringUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index e87ecbc7e..fefac96fd 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -21,8 +21,8 @@ import android.util.SparseArray; import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.utils.CollectionUtils; /** * Loads an XML description of a keyboard and stores the attributes of the keys. A keyboard diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index 4c5dd25c4..d00521c58 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -25,8 +25,8 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.compat.EditorInfoCompatUtils; -import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.SubtypeLocale; +import com.android.inputmethod.latin.utils.InputTypeUtils; import java.util.Arrays; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index a074b6334..c4f5eb035 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -42,15 +42,15 @@ import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.keyboard.internal.KeysCache; import com.android.inputmethod.latin.AdditionalSubtype; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.InputAttributes; -import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.SubtypeLocale; import com.android.inputmethod.latin.SubtypeSwitcher; -import com.android.inputmethod.latin.XmlParseUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.InputTypeUtils; +import com.android.inputmethod.latin.utils.ResourceUtils; +import com.android.inputmethod.latin.utils.XmlParseUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index 7941fcba2..4cee4cfa6 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -32,11 +32,11 @@ import android.view.View; import com.android.inputmethod.keyboard.internal.KeyDrawParams; import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.research.ResearchLogger; import java.util.HashSet; diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 7f335027f..8a926d655 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -58,21 +58,21 @@ import com.android.inputmethod.keyboard.internal.PreviewPlacerView; import com.android.inputmethod.keyboard.internal.SlidingKeyInputPreview; import com.android.inputmethod.keyboard.internal.TouchScreenRegulator; import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.CoordinateUtils; import com.android.inputmethod.latin.DebugSettings; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.Settings; -import com.android.inputmethod.latin.StaticInnerHandlerWrapper; -import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.SubtypeLocale; import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.CoordinateUtils; +import com.android.inputmethod.latin.utils.ResourceUtils; +import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.utils.StringUtils; +import com.android.inputmethod.latin.utils.Utils.UsabilityStudyLogUtils; import com.android.inputmethod.research.ResearchLogger; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java index ae08a5953..3b1f6d510 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboard.java @@ -28,7 +28,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.utils.StringUtils; public final class MoreKeysKeyboard extends Keyboard { private final int mDefaultKeyCoordX; diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java index a82fb79bd..897ad1d13 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java @@ -23,8 +23,8 @@ import android.view.MotionEvent; import android.view.View; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.CoordinateUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.CoordinateUtils; /** * A view that renders a virtual {@link MoreKeysKeyboard}. It handles rendering of keys and diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 958aaf569..7b14259a0 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -27,13 +27,13 @@ import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokePara import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints; import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints.GestureStrokePreviewParams; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.CoordinateUtils; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index 0e86b051d..9b0a33cec 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -22,7 +22,7 @@ import android.util.Log; import com.android.inputmethod.keyboard.internal.TouchPositionCorrection; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.JniUtils; +import com.android.inputmethod.latin.utils.JniUtils; import java.util.Arrays; diff --git a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java b/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java index 6a54e119c..c3b952037 100644 --- a/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java +++ b/java/src/com/android/inputmethod/keyboard/TypefaceUtils.java @@ -21,7 +21,7 @@ import android.graphics.Rect; import android.graphics.Typeface; import android.util.SparseArray; -import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; public final class TypefaceUtils { private TypefaceUtils() { diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java index ab810a580..9bfddba42 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureFloatingPreviewText.java @@ -26,9 +26,9 @@ import android.text.TextUtils; import android.view.View; import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.CoordinateUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.utils.CoordinateUtils; /** * The class for single gesture preview text. The class for multiple gesture preview text will be diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index 70363e602..f29ade861 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -21,8 +21,8 @@ import android.util.Log; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResizableIntArray; -import com.android.inputmethod.latin.ResourceUtils; +import com.android.inputmethod.latin.utils.ResizableIntArray; +import com.android.inputmethod.latin.utils.ResourceUtils; public class GestureStroke { private static final String TAG = GestureStroke.class.getSimpleName(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index 8deadbf96..ecc67dd44 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -19,7 +19,7 @@ package com.android.inputmethod.keyboard.internal; import android.content.res.TypedArray; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResizableIntArray; +import com.android.inputmethod.latin.utils.ResizableIntArray; public final class GestureStrokeWithPreviewPoints extends GestureStroke { public static final int PREVIEW_CAPACITY = 256; diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java index fb69e22e7..aca667919 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java @@ -26,7 +26,7 @@ import android.os.SystemClock; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResizableIntArray; +import com.android.inputmethod.latin.utils.ResizableIntArray; /* * @attr ref R.styleable#MainKeyboardView_gestureTrailFadeoutStartDelay diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java index 1e4c43ee5..dff5177ce 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsPreview.java @@ -30,8 +30,8 @@ import android.view.View; import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.keyboard.internal.GestureTrail.Params; -import com.android.inputmethod.latin.CollectionUtils; -import com.android.inputmethod.latin.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; /** * Draw gesture trail preview graphics during gesture. diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java index 5dcd842f7..1716fa049 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyDrawParams.java @@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard.internal; import android.graphics.Typeface; -import com.android.inputmethod.latin.ResourceUtils; +import com.android.inputmethod.latin.utils.ResourceUtils; public final class KeyDrawParams { public Typeface mTypeface; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java index ba449eeb3..22f5b3dd1 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java @@ -21,10 +21,10 @@ import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED; import android.text.TextUtils; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.StringUtils; import java.util.ArrayList; import java.util.Arrays; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java index a048ad09f..6aab3e7b3 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java @@ -20,9 +20,9 @@ import android.content.res.TypedArray; import android.util.Log; import android.util.SparseArray; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.XmlParseUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.XmlParseUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java index 6ddd2a6fb..7a2622cbb 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyVisualAttributes.java @@ -21,7 +21,7 @@ import android.graphics.Typeface; import android.util.SparseIntArray; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; +import com.android.inputmethod.latin.utils.ResourceUtils; public final class KeyVisualAttributes { public final Typeface mTypeface; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java index be178f516..26bbd7de9 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java @@ -29,12 +29,12 @@ import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.latin.LocaleUtils.RunInLocale; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; -import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.SubtypeLocale; -import com.android.inputmethod.latin.XmlParseUtils; +import com.android.inputmethod.latin.utils.LocaleUtils.RunInLocale; +import com.android.inputmethod.latin.utils.ResourceUtils; +import com.android.inputmethod.latin.utils.StringUtils; +import com.android.inputmethod.latin.utils.XmlParseUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java index a9e04bccf..d65aae2dc 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java @@ -16,8 +16,8 @@ package com.android.inputmethod.keyboard.internal; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java index 4ac2549c7..4e3f7618b 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java @@ -22,8 +22,8 @@ import android.graphics.drawable.Drawable; import android.util.Log; import android.util.SparseIntArray; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java index 84319eb33..a57b83ac0 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java @@ -20,8 +20,8 @@ import android.util.SparseIntArray; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.TreeSet; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java index 855f65507..5fe84a704 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java @@ -23,7 +23,7 @@ import android.util.Xml; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; +import com.android.inputmethod.latin.utils.ResourceUtils; import org.xmlpull.v1.XmlPullParser; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java index e1cee427e..370d0cb93 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardState.java @@ -20,7 +20,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.RecapitalizeStatus; +import com.android.inputmethod.latin.utils.RecapitalizeStatus; /** * Keyboard state machine. diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 7ec1c9406..467c15ffd 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -20,7 +20,7 @@ import android.content.Context; import android.content.res.Resources; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java index db154a391..7c2e3e174 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java @@ -17,7 +17,7 @@ package com.android.inputmethod.keyboard.internal; import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java index b38d79f7b..110936f8f 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java @@ -19,7 +19,7 @@ package com.android.inputmethod.keyboard.internal; import android.text.TextUtils; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.StringUtils; +import com.android.inputmethod.latin.utils.StringUtils; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java index 31ef3cd8f..3dbb7b7e9 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java @@ -18,7 +18,7 @@ package com.android.inputmethod.keyboard.internal; import android.util.Log; -import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java index 23761103f..3388c5701 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java @@ -24,8 +24,8 @@ import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; import android.widget.RelativeLayout; -import com.android.inputmethod.latin.CollectionUtils; -import com.android.inputmethod.latin.CoordinateUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.CoordinateUtils; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java index 33dbbafa3..5c9d36702 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java +++ b/java/src/com/android/inputmethod/keyboard/internal/SlidingKeyInputPreview.java @@ -23,8 +23,8 @@ import android.graphics.Path; import android.view.View; import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.CoordinateUtils; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.CoordinateUtils; /** * Draw rubber band preview graphics during sliding key input. diff --git a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java index d6b1cc6fc..fddd9856e 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java +++ b/java/src/com/android/inputmethod/keyboard/internal/TouchScreenRegulator.java @@ -23,8 +23,8 @@ import android.view.MotionEvent; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.utils.ResourceUtils; import com.android.inputmethod.research.ResearchLogger; public final class TouchScreenRegulator { diff --git a/java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java b/java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java deleted file mode 100644 index 0fdaea50c..000000000 --- a/java/src/com/android/inputmethod/latin/AdditionalFeaturesSettingUtils.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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; - -import com.android.inputmethodcommon.InputMethodSettingsFragment; - -import android.content.Context; -import android.content.SharedPreferences; - -/** - * Utility class for managing additional features settings. - */ -public class AdditionalFeaturesSettingUtils { - public static final int ADDITIONAL_FEATURES_SETTINGS_SIZE = 0; - - private AdditionalFeaturesSettingUtils() { - // This utility class is not publicly instantiable. - } - - public static void addAdditionalFeaturesPreferences( - final Context context, final InputMethodSettingsFragment settingsFragment) { - // do nothing. - } - - public static void readAdditionalFeaturesPreferencesIntoArray( - final SharedPreferences prefs, final int[] additionalFeaturesPreferences) { - // do nothing. - } - - public static int[] getAdditionalNativeSuggestOptions() { - return Settings.getInstance().getCurrent().mAdditionalFeaturesSettingValues; - } -} diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java index 85b14d849..0bddeb5ce 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtype.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtype.java @@ -25,6 +25,9 @@ import android.os.Build; import android.text.TextUtils; import android.view.inputmethod.InputMethodSubtype; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.StringUtils; + import java.util.ArrayList; public final class AdditionalSubtype { diff --git a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java index ff5e33949..8128e3a17 100644 --- a/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java +++ b/java/src/com/android/inputmethod/latin/AdditionalSubtypeSettings.java @@ -44,6 +44,9 @@ import android.widget.Spinner; import android.widget.SpinnerAdapter; import android.widget.Toast; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.IntentUtils; + import java.util.ArrayList; import java.util.TreeSet; diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index aad129d76..05b1a2ee1 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -21,6 +21,10 @@ import android.util.SparseArray; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.utils.AdditionalFeaturesSettingUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.JniUtils; +import com.android.inputmethod.latin.utils.StringUtils; import java.util.ArrayList; import java.util.Arrays; diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index c038db87c..321e80611 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -28,7 +28,10 @@ import android.text.TextUtils; import android.util.Log; import com.android.inputmethod.dictionarypack.DictionaryPackConstants; -import com.android.inputmethod.latin.DictionaryInfoUtils.DictionaryInfo; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.DictionaryInfoUtils; +import com.android.inputmethod.latin.utils.DictionaryInfoUtils.DictionaryInfo; +import com.android.inputmethod.latin.utils.MetadataFileUriGetter; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 272f36e4f..d0af59db0 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -16,10 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.latin.define.ProductionFlag; -import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; -import com.android.inputmethod.latin.makedict.FormatSpec; - import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; @@ -27,6 +23,13 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetFileDescriptor; import android.util.Log; +import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; +import com.android.inputmethod.latin.makedict.FormatSpec; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.DictionaryInfoUtils; +import com.android.inputmethod.latin.utils.LocaleUtils; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -39,7 +42,7 @@ import java.util.Locale; /** * Helper class to get the address of a mmap'able dictionary file. */ -final class BinaryDictionaryGetter { +final public class BinaryDictionaryGetter { /** * Used for Log actions from this class diff --git a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/BoundedTreeSet.java deleted file mode 100644 index 489a74ef1..000000000 --- a/java/src/com/android/inputmethod/latin/BoundedTreeSet.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - -import java.util.Collection; -import java.util.Comparator; -import java.util.TreeSet; - -/** - * A TreeSet that is bounded in size and throws everything that's smaller than its limit - */ -public final class BoundedTreeSet extends TreeSet { - private final int mCapacity; - public BoundedTreeSet(final Comparator comparator, final int capacity) { - super(comparator); - mCapacity = capacity; - } - - @Override - public boolean add(final SuggestedWordInfo e) { - if (size() < mCapacity) return super.add(e); - if (comparator().compare(e, last()) > 0) return false; - super.add(e); - pollLast(); // removes the last element - return true; - } - - @Override - public boolean addAll(final Collection e) { - if (null == e) return false; - return super.addAll(e); - } -} diff --git a/java/src/com/android/inputmethod/latin/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/CapsModeUtils.java deleted file mode 100644 index 4b8d1ac11..000000000 --- a/java/src/com/android/inputmethod/latin/CapsModeUtils.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * 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; - -import android.text.InputType; -import android.text.TextUtils; - -import java.util.Locale; - -public final class CapsModeUtils { - private CapsModeUtils() { - // This utility class is not publicly instantiable. - } - - /** - * Apply an auto-caps mode to a string. - * - * This intentionally does NOT apply manual caps mode. It only changes the capitalization if - * the mode is one of the auto-caps modes. - * @param s The string to capitalize. - * @param capitalizeMode The mode in which to capitalize. - * @param locale The locale for capitalizing. - * @return The capitalized string. - */ - public static String applyAutoCapsMode(final String s, final int capitalizeMode, - final Locale locale) { - if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) { - return s.toUpperCase(locale); - } else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) { - return StringUtils.capitalizeFirstCodePoint(s, locale); - } else { - return s; - } - } - - /** - * Return whether a constant represents an auto-caps mode (either auto-shift or auto-shift-lock) - * @param mode The mode to test for - * @return true if this represents an auto-caps mode, false otherwise - */ - public static boolean isAutoCapsMode(final int mode) { - return WordComposer.CAPS_MODE_AUTO_SHIFTED == mode - || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode; - } - - /** - * Determine what caps mode should be in effect at the current offset in - * the text. Only the mode bits set in reqModes will be - * checked. Note that the caps mode flags here are explicitly defined - * to match those in {@link InputType}. - * - * This code is a straight copy of TextUtils.getCapsMode (modulo namespace and formatting - * issues). This will change in the future as we simplify the code for our use and fix bugs. - * - * @param cs The text that should be checked for caps modes. - * @param reqModes The modes to be checked: may be any combination of - * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and - * {@link TextUtils#CAP_MODE_SENTENCES}. - * @param locale The locale to consider for capitalization rules - * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs - * - * @return Returns the actual capitalization modes that can be in effect - * at the current position, which is any combination of - * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and - * {@link TextUtils#CAP_MODE_SENTENCES}. - */ - public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale, - final boolean hasSpaceBefore) { - // Quick description of what we want to do: - // CAP_MODE_CHARACTERS is always on. - // CAP_MODE_WORDS is on if there is some whitespace before the cursor. - // CAP_MODE_SENTENCES is on if there is some whitespace before the cursor, and the end - // of a sentence just before that. - // We ignore opening parentheses and the like just before the cursor for purposes of - // finding whitespace for WORDS and SENTENCES modes. - // The end of a sentence ends with a period, question mark or exclamation mark. If it's - // a period, it also needs not to be an abbreviation, which means it also needs to either - // be immediately preceded by punctuation, or by a string of only letters with single - // periods interleaved. - - // Step 1 : check for cap MODE_CHARACTERS. If it's looked for, it's always on. - if ((reqModes & (TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES)) == 0) { - // Here we are not looking for MODE_WORDS or MODE_SENTENCES, so since we already - // evaluated MODE_CHARACTERS, we can return. - return TextUtils.CAP_MODE_CHARACTERS & reqModes; - } - - // Step 2 : Skip (ignore at the end of input) any opening punctuation. This includes - // opening parentheses, brackets, opening quotes, everything that *opens* a span of - // text in the linguistic sense. In RTL languages, this is still an opening sign, although - // it may look like a right parenthesis for example. We also include double quote and - // single quote since they aren't start punctuation in the unicode sense, but should still - // be skipped for English. TODO: does this depend on the language? - int i; - if (hasSpaceBefore) { - i = cs.length() + 1; - } else { - for (i = cs.length(); i > 0; i--) { - final char c = cs.charAt(i - 1); - if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE - && Character.getType(c) != Character.START_PUNCTUATION) { - break; - } - } - } - - // We are now on the character that precedes any starting punctuation, so in the most - // frequent case this will be whitespace or a letter, although it may occasionally be a - // start of line, or some symbol. - - // Step 3 : Search for the start of a paragraph. From the starting point computed in step 2, - // we go back over any space or tab char sitting there. We find the start of a paragraph - // if the first char that's not a space or tab is a start of line (as in \n, start of text, - // or some other similar characters). - int j = i; - char prevChar = Constants.CODE_SPACE; - if (hasSpaceBefore) --j; - while (j > 0) { - prevChar = cs.charAt(j - 1); - if (!Character.isSpaceChar(prevChar) && prevChar != Constants.CODE_TAB) break; - j--; - } - if (j <= 0 || Character.isWhitespace(prevChar)) { - // There are only spacing chars between the start of the paragraph and the cursor, - // defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both - // MODE_WORDS and MODE_SENTENCES should be active. - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS - | TextUtils.CAP_MODE_SENTENCES) & reqModes; - } - if (i == j) { - // If we don't have whitespace before index i, it means neither MODE_WORDS - // nor mode sentences should be on so we can return right away. - return TextUtils.CAP_MODE_CHARACTERS & reqModes; - } - if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) { - // Here we know we have whitespace before the cursor (if not, we returned in the above - // if i == j clause), so we need MODE_WORDS to be on. And we don't need to evaluate - // MODE_SENTENCES so we can return right away. - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; - } - // Please note that because of the reqModes & CAP_MODE_SENTENCES test a few lines above, - // we know that MODE_SENTENCES is being requested. - - // Step 4 : Search for MODE_SENTENCES. - // English is a special case in that "American typography" rules, which are the most common - // in English, state that a sentence terminator immediately following a quotation mark - // should be swapped with it and de-duplicated (included in the quotation mark), - // e.g. <> - // No other language has such a rule as far as I know, instead putting inside the quotation - // mark as the exact thing quoted and handling the surrounding punctuation independently, - // e.g. <> - // Hence, specifically for English, we treat this special case here. - if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) { - for (; j > 0; j--) { - // Here we look to go over any closing punctuation. This is because in dominant - // variants of English, the final period is placed within double quotes and maybe - // other closing punctuation signs. This is generally not true in other languages. - final char c = cs.charAt(j - 1); - if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE - && Character.getType(c) != Character.END_PUNCTUATION) { - break; - } - } - } - - if (j <= 0) return TextUtils.CAP_MODE_CHARACTERS & reqModes; - char c = cs.charAt(--j); - - // We found the next interesting chunk of text ; next we need to determine if it's the - // end of a sentence. If we have a question mark or an exclamation mark, it's the end of - // a sentence. If it's neither, the only remaining case is the period so we get the opposite - // case out of the way. - if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) { - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes; - } - if (c != Constants.CODE_PERIOD || j <= 0) { - return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; - } - - // We found out that we have a period. We need to determine if this is a full stop or - // otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation - // looks like (\w\.){2,} - // To find out, we will have a simple state machine with the following states : - // START, WORD, PERIOD, ABBREVIATION - // On START : (just before the first period) - // letter => WORD - // whitespace => end with no caps (it was a stand-alone period) - // otherwise => end with caps (several periods/symbols in a row) - // On WORD : (within the word just before the first period) - // letter => WORD - // period => PERIOD - // otherwise => end with caps (it was a word with a full stop at the end) - // On PERIOD : (period within a potential abbreviation) - // letter => LETTER - // otherwise => end with caps (it was not an abbreviation) - // On LETTER : (letter within a potential abbreviation) - // letter => LETTER - // period => PERIOD - // otherwise => end with no caps (it was an abbreviation) - // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This - // should capitalize. - - final int START = 0; - final int WORD = 1; - final int PERIOD = 2; - final int LETTER = 3; - final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS - | TextUtils.CAP_MODE_SENTENCES) & reqModes; - final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; - int state = START; - while (j > 0) { - c = cs.charAt(--j); - switch (state) { - case START: - if (Character.isLetter(c)) { - state = WORD; - } else if (Character.isWhitespace(c)) { - return noCaps; - } else { - return caps; - } - break; - case WORD: - if (Character.isLetter(c)) { - state = WORD; - } else if (c == Constants.CODE_PERIOD) { - state = PERIOD; - } else { - return caps; - } - break; - case PERIOD: - if (Character.isLetter(c)) { - state = LETTER; - } else { - return caps; - } - break; - case LETTER: - if (Character.isLetter(c)) { - state = LETTER; - } else if (c == Constants.CODE_PERIOD) { - state = PERIOD; - } else { - return noCaps; - } - } - } - // Here we arrived at the start of the line. This should behave exactly like whitespace. - return (START == state || LETTER == state) ? noCaps : caps; - } -} diff --git a/java/src/com/android/inputmethod/latin/CollectionUtils.java b/java/src/com/android/inputmethod/latin/CollectionUtils.java deleted file mode 100644 index a8623cc63..000000000 --- a/java/src/com/android/inputmethod/latin/CollectionUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import android.util.SparseArray; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.Map; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.WeakHashMap; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; - -public final class CollectionUtils { - private CollectionUtils() { - // This utility class is not publicly instantiable. - } - - public static HashMap newHashMap() { - return new HashMap(); - } - - public static WeakHashMap newWeakHashMap() { - return new WeakHashMap(); - } - - public static TreeMap newTreeMap() { - return new TreeMap(); - } - - public static Map newSynchronizedTreeMap() { - final TreeMap treeMap = newTreeMap(); - return Collections.synchronizedMap(treeMap); - } - - public static ConcurrentHashMap newConcurrentHashMap() { - return new ConcurrentHashMap(); - } - - public static HashSet newHashSet() { - return new HashSet(); - } - - public static TreeSet newTreeSet() { - return new TreeSet(); - } - - public static ArrayList newArrayList() { - return new ArrayList(); - } - - public static ArrayList newArrayList(final int initialCapacity) { - return new ArrayList(initialCapacity); - } - - public static ArrayList newArrayList(final Collection collection) { - return new ArrayList(collection); - } - - public static LinkedList newLinkedList() { - return new LinkedList(); - } - - public static CopyOnWriteArrayList newCopyOnWriteArrayList() { - return new CopyOnWriteArrayList(); - } - - public static CopyOnWriteArrayList newCopyOnWriteArrayList( - final Collection collection) { - return new CopyOnWriteArrayList(collection); - } - - public static CopyOnWriteArrayList newCopyOnWriteArrayList(final E[] array) { - return new CopyOnWriteArrayList(array); - } - - public static SparseArray newSparseArray() { - return new SparseArray(); - } -} diff --git a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java b/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java deleted file mode 100644 index 792a446c9..000000000 --- a/java/src/com/android/inputmethod/latin/CompletionInfoUtils.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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; - -import android.text.TextUtils; -import android.view.inputmethod.CompletionInfo; - -import java.util.Arrays; - -/** - * Utilities to do various stuff with CompletionInfo. - */ -public class CompletionInfoUtils { - private CompletionInfoUtils() { - // This utility class is not publicly instantiable. - } - - public static CompletionInfo[] removeNulls(final CompletionInfo[] src) { - int j = 0; - final CompletionInfo[] dst = new CompletionInfo[src.length]; - for (int i = 0; i < src.length; ++i) { - if (null != src[i] && !TextUtils.isEmpty(src[i].getText())) { - dst[j] = src[i]; - ++j; - } - } - return Arrays.copyOfRange(dst, 0, j); - } -} diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index b9db9a092..110be9db3 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -30,6 +30,8 @@ import android.provider.ContactsContract.Contacts; import android.text.TextUtils; import android.util.Log; +import com.android.inputmethod.latin.utils.StringUtils; + import java.util.List; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/latin/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/CoordinateUtils.java deleted file mode 100644 index af270e1e4..000000000 --- a/java/src/com/android/inputmethod/latin/CoordinateUtils.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2012 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; - -public final class CoordinateUtils { - private static final int INDEX_X = 0; - private static final int INDEX_Y = 1; - private static final int ARRAY_SIZE = INDEX_Y + 1; - - private CoordinateUtils() { - // This utility class is not publicly instantiable. - } - - public static int[] newInstance() { - return new int[ARRAY_SIZE]; - } - - public static int x(final int[] coords) { - return coords[INDEX_X]; - } - - public static int y(final int[] coords) { - return coords[INDEX_Y]; - } - - public static void set(final int[] coords, final int x, final int y) { - coords[INDEX_X] = x; - coords[INDEX_Y] = y; - } - - public static void copy(final int[] destination, final int[] source) { - destination[INDEX_X] = source[INDEX_X]; - destination[INDEX_Y] = source[INDEX_Y]; - } -} diff --git a/java/src/com/android/inputmethod/latin/DebugSettings.java b/java/src/com/android/inputmethod/latin/DebugSettings.java index 5969a63de..d1cb39cd9 100644 --- a/java/src/com/android/inputmethod/latin/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/DebugSettings.java @@ -29,6 +29,7 @@ import android.preference.PreferenceScreen; import android.util.Log; import com.android.inputmethod.keyboard.KeyboardSwitcher; +import com.android.inputmethod.latin.utils.Utils; import com.android.inputmethod.research.ResearchLogger; public final class DebugSettings extends PreferenceFragment diff --git a/java/src/com/android/inputmethod/latin/DicTraverseSession.java b/java/src/com/android/inputmethod/latin/DicTraverseSession.java index 534e2116b..45b281318 100644 --- a/java/src/com/android/inputmethod/latin/DicTraverseSession.java +++ b/java/src/com/android/inputmethod/latin/DicTraverseSession.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.utils.JniUtils; + import java.util.Locale; public final class DicTraverseSession { diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index ed2b44223..d05bb1e55 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -16,10 +16,11 @@ package com.android.inputmethod.latin; +import android.util.Log; + import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - -import android.util.Log; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.Collection; diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java index 4514ec2ec..5b98613a4 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java @@ -22,6 +22,8 @@ import android.content.res.Resources; import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.DictionaryInfoUtils; import java.io.File; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java deleted file mode 100644 index 9d478491a..000000000 --- a/java/src/com/android/inputmethod/latin/DictionaryInfoUtils.java +++ /dev/null @@ -1,365 +0,0 @@ -/* - * 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; - -import android.content.ContentValues; -import android.content.Context; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.text.format.DateUtils; -import android.util.Log; - -import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; -import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; -import com.android.inputmethod.latin.makedict.UnsupportedFormatException; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Locale; - -/** - * This class encapsulates the logic for the Latin-IME side of dictionary information management. - */ -public class DictionaryInfoUtils { - private static final String TAG = DictionaryInfoUtils.class.getSimpleName(); - // This class must be located in the same package as LatinIME.java. - private static final String RESOURCE_PACKAGE_NAME = - DictionaryInfoUtils.class.getPackage().getName(); - private static final String DEFAULT_MAIN_DICT = "main"; - 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; - - public static class DictionaryInfo { - 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 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; - } - 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); - values.put(FILESIZE_COLUMN, mFileAddress.mLength); - values.put(VERSION_COLUMN, mVersion); - return values; - } - } - - private DictionaryInfoUtils() { - // Private constructor to forbid instantation of this helper class. - } - - /** - * Returns whether we may want to use this character as part of a file name. - * - * This basically only accepts ascii letters and numbers, and rejects everything else. - */ - private static boolean isFileNameCharacter(int codePoint) { - if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit - if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase - if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase - return codePoint == '_'; // Underscore - } - - /** - * Escapes a string for any characters that may be suspicious for a file or directory name. - * - * Concretely this does a sort of URL-encoding except it will encode everything that's not - * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which - * we cannot allow here) - */ - // TODO: create a unit test for this method - public static String replaceFileNameDangerousCharacters(final String name) { - // This assumes '%' is fully available as a non-separator, normal - // character in a file name. This is probably true for all file systems. - final StringBuilder sb = new StringBuilder(); - final int nameLength = name.length(); - for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) { - final int codePoint = name.codePointAt(i); - if (DictionaryInfoUtils.isFileNameCharacter(codePoint)) { - sb.appendCodePoint(codePoint); - } else { - sb.append(String.format((Locale)null, "%%%1$0" + MAX_HEX_DIGITS_FOR_CODEPOINT + "x", - codePoint)); - } - } - return sb.toString(); - } - - /** - * Helper method to get the top level cache directory. - */ - private static String getWordListCacheDirectory(final Context context) { - return context.getFilesDir() + File.separator + "dicts"; - } - - /** - * Helper method to get the top level temp directory. - */ - public static String getWordListTempDirectory(final Context context) { - return context.getFilesDir() + File.separator + "tmp"; - } - - /** - * Reverse escaping done by replaceFileNameDangerousCharacters. - */ - public static String getWordListIdFromFileName(final String fname) { - final StringBuilder sb = new StringBuilder(); - final int fnameLength = fname.length(); - for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) { - final int codePoint = fname.codePointAt(i); - if ('%' != codePoint) { - sb.appendCodePoint(codePoint); - } else { - // + 1 to pass the % sign - final int encodedCodePoint = Integer.parseInt( - fname.substring(i + 1, i + 1 + MAX_HEX_DIGITS_FOR_CODEPOINT), 16); - i += MAX_HEX_DIGITS_FOR_CODEPOINT; - sb.appendCodePoint(encodedCodePoint); - } - } - return sb.toString(); - } - - /** - * Helper method to the list of cache directories, one for each distinct locale. - */ - public static File[] getCachedDirectoryList(final Context context) { - return new File(DictionaryInfoUtils.getWordListCacheDirectory(context)).listFiles(); - } - - /** - * 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. - */ - public static String getCategoryFromFileName(final String fileName) { - final String id = getWordListIdFromFileName(fileName); - final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR); - // An id is supposed to be in format category:locale, so splitting on the separator - // should yield a 2-elements array - if (2 != idArray.length) return null; - return idArray[0]; - } - - /** - * Find out the cache directory associated with a specific locale. - */ - private static String getCacheDirectoryForLocale(final String locale, final Context context) { - final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale); - final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator - + relativeDirectoryName; - final File directory = new File(absoluteDirectoryName); - if (!directory.exists()) { - if (!directory.mkdirs()) { - Log.e(TAG, "Could not create the directory for locale" + locale); - } - } - return absoluteDirectoryName; - } - - /** - * Generates a file name for the id and locale passed as an argument. - * - * In the current implementation the file name returned will always be unique for - * any id/locale pair, but please do not expect that the id can be the same for - * different dictionaries with different locales. An id should be unique for any - * dictionary. - * The file name is pretty much an URL-encoded version of the id inside a directory - * named like the locale, except it will also escape characters that look dangerous - * to some file systems. - * @param id the id of the dictionary for which to get a file name - * @param locale the locale for which to get the file name as a string - * @param context the context to use for getting the directory - * @return the name of the file to be created - */ - public static String getCacheFileName(String id, String locale, Context context) { - final String fileName = replaceFileNameDangerousCharacters(id); - return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; - } - - public static boolean isMainWordListId(final String id) { - final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR); - // An id is supposed to be in format category:locale, so splitting on the separator - // should yield a 2-elements array - if (2 != idArray.length) return false; - return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); - } - - /** - * Helper method to return a dictionary res id for a locale, or 0 if none. - * @param locale dictionary locale - * @return main dictionary resource id - */ - public static int getMainDictionaryResourceIdIfAvailableForLocale(final Resources res, - final Locale locale) { - int resId; - // Try to find main_language_country dictionary. - if (!locale.getCountry().isEmpty()) { - final String dictLanguageCountry = - MAIN_DICT_PREFIX + locale.toString().toLowerCase(Locale.ROOT); - if ((resId = res.getIdentifier( - dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) { - return resId; - } - } - - // Try to find main_language dictionary. - final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage(); - if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) { - return resId; - } - - // Not found, return 0 - return 0; - } - - /** - * Returns a main dictionary resource id - * @param locale dictionary locale - * @return main dictionary resource id - */ - public static int getMainDictionaryResourceId(final Resources res, final Locale locale) { - int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale); - if (0 != resourceId) return resourceId; - return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME); - } - - /** - * Returns the id associated with the main word list for a specified locale. - * - * Word lists stored in Android Keyboard's resources are referred to as the "main" - * word lists. Since they can be updated like any other list, we need to assign a - * unique ID to them. This ID is just the name of the language (locale-wise) they - * are for, and this method returns this ID. - */ - public static String getMainDictId(final Locale locale) { - // 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 BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY + - BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.getLanguage().toString(); - } - - public static FileHeader getDictionaryFileHeaderOrNull(final File file) { - try { - return BinaryDictIOUtils.getDictionaryFileHeader(file, 0, file.length()); - } catch (UnsupportedFormatException e) { - return null; - } catch (IOException e) { - return null; - } - } - - private static DictionaryInfo createDictionaryInfoFromFileAddress( - 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(id, locale, description, fileAddress, Integer.parseInt(version)); - } - - private static void addOrUpdateDictInfo(final ArrayList dictList, - final DictionaryInfo newElement) { - final Iterator iter = dictList.iterator(); - while (iter.hasNext()) { - final DictionaryInfo thisDictInfo = iter.next(); - if (thisDictInfo.mLocale.equals(newElement.mLocale)) { - if (newElement.mVersion <= thisDictInfo.mVersion) { - return; - } - iter.remove(); - } - } - dictList.add(newElement); - } - - public static ArrayList getCurrentDictionaryFileNameAndVersionInfo( - final Context context) { - final ArrayList dictList = CollectionUtils.newArrayList(); - - // Retrieve downloaded dictionaries - final File[] directoryList = getCachedDirectoryList(context); - if (null != directoryList) { - for (final File directory : directoryList) { - final String localeString = getWordListIdFromFileName(directory.getName()); - File[] dicts = BinaryDictionaryGetter.getCachedWordLists(localeString, context); - for (final File dict : dicts) { - final String wordListId = getWordListIdFromFileName(dict.getName()); - if (!DictionaryInfoUtils.isMainWordListId(wordListId)) continue; - final Locale locale = LocaleUtils.constructLocaleFromString(localeString); - final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict); - final DictionaryInfo dictionaryInfo = - createDictionaryInfoFromFileAddress(fileAddress); - // Protect against cases of a less-specific dictionary being found, like an - // en dictionary being used for an en_US locale. In this case, the en dictionary - // should be used for en_US but discounted for listing purposes. - if (!dictionaryInfo.mLocale.equals(locale)) continue; - addOrUpdateDictInfo(dictList, dictionaryInfo); - } - } - } - - // Retrieve files from assets - final Resources resources = context.getResources(); - final AssetManager assets = resources.getAssets(); - for (final String localeString : assets.getLocales()) { - final Locale locale = LocaleUtils.constructLocaleFromString(localeString); - final int resourceId = - DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale( - context.getResources(), locale); - if (0 == resourceId) continue; - final AssetFileAddress fileAddress = - BinaryDictionaryGetter.loadFallbackResource(context, resourceId); - final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress); - // Protect against cases of a less-specific dictionary being found, like an - // en dictionary being used for an en_US locale. In this case, the en dictionary - // should be used for en_US but discounted for listing purposes. - if (!dictionaryInfo.mLocale.equals(locale)) continue; - addOrUpdateDictInfo(dictList, dictionaryInfo); - } - - return dictList; - } -} diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java index 56096127e..2dcfdb0b7 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java +++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java @@ -17,6 +17,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.dictionarypack.DictionaryPackConstants; +import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; import android.content.BroadcastReceiver; import android.content.Context; diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 887d657e9..f357e2a1e 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -28,6 +28,7 @@ import com.android.inputmethod.latin.makedict.FusionDictionary; import com.android.inputmethod.latin.makedict.FusionDictionary.Node; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.io.File; import java.io.FileOutputStream; diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java index 0dabdb835..a67c919b3 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java @@ -22,7 +22,8 @@ import android.util.Log; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams; import java.util.ArrayList; import java.util.LinkedList; diff --git a/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java index 9f91639a2..18f4920d9 100644 --- a/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java +++ b/java/src/com/android/inputmethod/latin/ExternalDictionaryGetterForDebug.java @@ -23,6 +23,9 @@ import android.content.DialogInterface.OnClickListener; import android.os.Environment; import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.DictionaryInfoUtils; +import com.android.inputmethod.latin.utils.LocaleUtils; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; diff --git a/java/src/com/android/inputmethod/latin/FeedbackUtils.java b/java/src/com/android/inputmethod/latin/FeedbackUtils.java deleted file mode 100644 index 0582763fe..000000000 --- a/java/src/com/android/inputmethod/latin/FeedbackUtils.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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; - -import android.content.Context; -import android.content.Intent; - -public class FeedbackUtils { - public static boolean isFeedbackFormSupported() { - return false; - } - - public static void showFeedbackForm(Context context) { - } - - public static int getAboutKeyboardTitleResId() { - return 0; - } - - public static Intent getAboutKeyboardIntent(Context context) { - return null; - } -} diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index 1f673e9b0..21b103e5a 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -20,6 +20,9 @@ import android.text.InputType; import android.util.Log; import android.view.inputmethod.EditorInfo; +import com.android.inputmethod.latin.utils.InputTypeUtils; +import com.android.inputmethod.latin.utils.StringUtils; + /** * Class to hold attributes of the input field. */ diff --git a/java/src/com/android/inputmethod/latin/InputPointers.java b/java/src/com/android/inputmethod/latin/InputPointers.java index 81c833000..e96a46e12 100644 --- a/java/src/com/android/inputmethod/latin/InputPointers.java +++ b/java/src/com/android/inputmethod/latin/InputPointers.java @@ -17,6 +17,7 @@ package com.android.inputmethod.latin; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.utils.ResizableIntArray; import android.util.Log; diff --git a/java/src/com/android/inputmethod/latin/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/InputTypeUtils.java deleted file mode 100644 index 46194f6e4..000000000 --- a/java/src/com/android/inputmethod/latin/InputTypeUtils.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import android.text.InputType; -import android.view.inputmethod.EditorInfo; - -public final class InputTypeUtils implements InputType { - private static final int WEB_TEXT_PASSWORD_INPUT_TYPE = - TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD; - private static final int WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE = - TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; - private static final int NUMBER_PASSWORD_INPUT_TYPE = - TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD; - private static final int TEXT_PASSWORD_INPUT_TYPE = - TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD; - private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE = - TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; - private static final int[] SUPPRESSING_AUTO_SPACES_FIELD_VARIATION = { - InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, - InputType.TYPE_TEXT_VARIATION_PASSWORD, - InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, - InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD }; - public static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1; - - private InputTypeUtils() { - // This utility class is not publicly instantiable. - } - - private static boolean isWebEditTextInputType(final int inputType) { - return inputType == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); - } - - private static boolean isWebPasswordInputType(final int inputType) { - return WEB_TEXT_PASSWORD_INPUT_TYPE != 0 - && inputType == WEB_TEXT_PASSWORD_INPUT_TYPE; - } - - private static boolean isWebEmailAddressInputType(final int inputType) { - return WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE != 0 - && inputType == WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE; - } - - private static boolean isNumberPasswordInputType(final int inputType) { - return NUMBER_PASSWORD_INPUT_TYPE != 0 - && inputType == NUMBER_PASSWORD_INPUT_TYPE; - } - - private static boolean isTextPasswordInputType(final int inputType) { - return inputType == TEXT_PASSWORD_INPUT_TYPE; - } - - private static boolean isWebEmailAddressVariation(int variation) { - return variation == TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; - } - - public static boolean isEmailVariation(final int variation) { - return variation == TYPE_TEXT_VARIATION_EMAIL_ADDRESS - || isWebEmailAddressVariation(variation); - } - - public static boolean isWebInputType(final int inputType) { - final int maskedInputType = - inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); - return isWebEditTextInputType(maskedInputType) || isWebPasswordInputType(maskedInputType) - || isWebEmailAddressInputType(maskedInputType); - } - - // Please refer to TextView.isPasswordInputType - public static boolean isPasswordInputType(final int inputType) { - final int maskedInputType = - inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); - return isTextPasswordInputType(maskedInputType) || isWebPasswordInputType(maskedInputType) - || isNumberPasswordInputType(maskedInputType); - } - - // Please refer to TextView.isVisiblePasswordInputType - public static boolean isVisiblePasswordInputType(final int inputType) { - final int maskedInputType = - inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); - return maskedInputType == TEXT_VISIBLE_PASSWORD_INPUT_TYPE; - } - - public static boolean isAutoSpaceFriendlyType(final int inputType) { - if (TYPE_CLASS_TEXT != (TYPE_MASK_CLASS & inputType)) return false; - final int variation = TYPE_MASK_VARIATION & inputType; - for (final int fieldVariation : SUPPRESSING_AUTO_SPACES_FIELD_VARIATION) { - if (variation == fieldVariation) return false; - } - return true; - } - - public static int getImeOptionsActionIdFromEditorInfo(final EditorInfo editorInfo) { - 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 { - // Note: this is different from editorInfo.actionId, hence "ImeOptionsActionId" - return editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION; - } - } -} diff --git a/java/src/com/android/inputmethod/latin/IntentUtils.java b/java/src/com/android/inputmethod/latin/IntentUtils.java deleted file mode 100644 index d175af504..000000000 --- a/java/src/com/android/inputmethod/latin/IntentUtils.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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; - -import android.content.Intent; -import android.text.TextUtils; - -public final class IntentUtils { - private static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; - // TODO: Can these be constants instead of literal String constants? - private static final String INPUT_METHOD_SUBTYPE_SETTINGS = - "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; - - private IntentUtils() { - // This utility class is not publicly instantiable. - } - - public static Intent getInputLanguageSelectionIntent(final String inputMethodId, - final int flagsForSubtypeSettings) { - // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS - final String action = INPUT_METHOD_SUBTYPE_SETTINGS; - final Intent intent = new Intent(action); - if (!TextUtils.isEmpty(inputMethodId)) { - intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId); - } - if (flagsForSubtypeSettings > 0) { - intent.setFlags(flagsForSubtypeSettings); - } - return intent; - } -} diff --git a/java/src/com/android/inputmethod/latin/JniUtils.java b/java/src/com/android/inputmethod/latin/JniUtils.java deleted file mode 100644 index 8aedee576..000000000 --- a/java/src/com/android/inputmethod/latin/JniUtils.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import android.util.Log; - -import com.android.inputmethod.latin.define.JniLibName; - -public final class JniUtils { - private static final String TAG = JniUtils.class.getSimpleName(); - - static { - try { - System.loadLibrary(JniLibName.JNI_LIB_NAME); - } catch (UnsatisfiedLinkError ule) { - Log.e(TAG, "Could not load native library " + JniLibName.JNI_LIB_NAME, ule); - } - } - - private JniUtils() { - // This utility class is not publicly instantiable. - } - - public static void loadNativeLibrary() { - // Ensures the static initializer is called - } -} diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java index 826dc11e7..cdeadc74f 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.utils.StringUtils; + import android.text.TextUtils; /** diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 52a9b9a6c..6689e50a4 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -76,9 +76,19 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.RichInputConnection.Range; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.Utils.Stats; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.suggestions.SuggestionStripView; +import com.android.inputmethod.latin.utils.CapsModeUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.CompletionInfoUtils; +import com.android.inputmethod.latin.utils.InputTypeUtils; +import com.android.inputmethod.latin.utils.IntentUtils; +import com.android.inputmethod.latin.utils.PositionalInfoForUserDictPendingAddition; +import com.android.inputmethod.latin.utils.RecapitalizeStatus; +import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; +import com.android.inputmethod.latin.utils.Utils; +import com.android.inputmethod.latin.utils.Utils.Stats; import com.android.inputmethod.research.ResearchLogger; import java.io.FileDescriptor; diff --git a/java/src/com/android/inputmethod/latin/LocaleUtils.java b/java/src/com/android/inputmethod/latin/LocaleUtils.java deleted file mode 100644 index a1e40502e..000000000 --- a/java/src/com/android/inputmethod/latin/LocaleUtils.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin; - -import android.content.res.Configuration; -import android.content.res.Resources; -import android.text.TextUtils; - -import java.util.HashMap; -import java.util.Locale; - -/** - * A class to help with handling Locales in string form. - * - * This file has the same meaning and features (and shares all of its code) with - * the one in the dictionary pack. They need to be kept synchronized; for any - * update/bugfix to this file, consider also updating/fixing the version in the - * dictionary pack. - */ -public final class LocaleUtils { - private static final HashMap EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap(); - private static final String LOCALE_AND_TIME_STR_SEPARATER = ","; - - private LocaleUtils() { - // Intentional empty constructor for utility class. - } - - // Locale match level constants. - // A higher level of match is guaranteed to have a higher numerical value. - // Some room is left within constants to add match cases that may arise necessary - // in the future, for example differentiating between the case where the countries - // are both present and different, and the case where one of the locales does not - // specify the countries. This difference is not needed now. - - // Nothing matches. - public static final int LOCALE_NO_MATCH = 0; - // The languages matches, but the country are different. Or, the reference locale requires a - // country and the tested locale does not have one. - public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3; - // The languages and country match, but the variants are different. Or, the reference locale - // requires a variant and the tested locale does not have one. - public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6; - // The required locale is null or empty so it will accept anything, and the tested locale - // is non-null and non-empty. - public static final int LOCALE_ANY_MATCH = 10; - // The language matches, and the tested locale specifies a country but the reference locale - // does not require one. - public static final int LOCALE_LANGUAGE_MATCH = 15; - // The language and the country match, and the tested locale specifies a variant but the - // reference locale does not require one. - public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20; - // The compared locales are fully identical. This is the best match level. - public static final int LOCALE_FULL_MATCH = 30; - - // The level at which a match is "normally" considered a locale match with standard algorithms. - // Don't use this directly, use #isMatch to test. - private static final int LOCALE_MATCH = LOCALE_ANY_MATCH; - - // Make this match the maximum match level. If this evolves to have more than 2 digits - // when written in base 10, also adjust the getMatchLevelSortedString method. - private static final int MATCH_LEVEL_MAX = 30; - - /** - * Return how well a tested locale matches a reference locale. - * - * This will check the tested locale against the reference locale and return a measure of how - * a well it matches the reference. The general idea is that the tested locale has to match - * every specified part of the required locale. A full match occur when they are equal, a - * partial match when the tested locale agrees with the reference locale but is more specific, - * and a difference when the tested locale does not comply with all requirements from the - * reference locale. - * In more detail, if the reference locale specifies at least a language and the testedLocale - * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the - * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH - * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and - * tested locale agree on the language, but not on the country, - * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country, - * and LOCALE_LANGUAGE_MATCH otherwise. - * If they agree on both the language and the country, but not on the variant, - * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale - * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches, - * LOCALE_FULL_MATCH is returned. - * Examples: - * en <=> en_US => LOCALE_LANGUAGE_MATCH - * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER - * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER - * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH - * sp_US <=> en_US => LOCALE_NO_MATCH - * de <=> de => LOCALE_FULL_MATCH - * en_US <=> en_US => LOCALE_FULL_MATCH - * "" <=> en_US => LOCALE_ANY_MATCH - * - * @param referenceLocale the reference locale to test against. - * @param testedLocale the locale to test. - * @return a constant that measures how well the tested locale matches the reference locale. - */ - public static int getMatchLevel(String referenceLocale, String testedLocale) { - if (TextUtils.isEmpty(referenceLocale)) { - return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH; - } - if (null == testedLocale) return LOCALE_NO_MATCH; - String[] referenceParams = referenceLocale.split("_", 3); - String[] testedParams = testedLocale.split("_", 3); - // By spec of String#split, [0] cannot be null and length cannot be 0. - if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH; - switch (referenceParams.length) { - case 1: - return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH; - case 2: - if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (!referenceParams[1].equals(testedParams[1])) - return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH; - return LOCALE_FULL_MATCH; - case 3: - if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (!referenceParams[1].equals(testedParams[1])) - return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; - if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; - if (!referenceParams[2].equals(testedParams[2])) - return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; - return LOCALE_FULL_MATCH; - } - // It should be impossible to come here - return LOCALE_NO_MATCH; - } - - /** - * Return a string that represents this match level, with better matches first. - * - * The strings are sorted in lexicographic order: a better match will always be less than - * a worse match when compared together. - */ - public static String getMatchLevelSortedString(int matchLevel) { - // This works because the match levels are 0~99 (actually 0~30) - // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel - return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel); - } - - /** - * Find out whether a match level should be considered a match. - * - * This method takes a match level as returned by the #getMatchLevel method, and returns whether - * it should be considered a match in the usual sense with standard Locale functions. - * - * @param level the match level, as returned by getMatchLevel. - * @return whether this is a match or not. - */ - public static boolean isMatch(int level) { - return LOCALE_MATCH <= level; - } - - static final Object sLockForRunInLocale = new Object(); - - public abstract static class RunInLocale { - protected abstract T job(Resources res); - - /** - * Execute {@link #job(Resources)} method in specified system locale exclusively. - * - * @param res the resources to use. Pass current resources. - * @param newLocale the locale to change to - * @return the value returned from {@link #job(Resources)}. - */ - public T runInLocale(final Resources res, final Locale newLocale) { - synchronized (sLockForRunInLocale) { - final Configuration conf = res.getConfiguration(); - final Locale oldLocale = conf.locale; - final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale)); - try { - if (needsChange) { - conf.locale = newLocale; - res.updateConfiguration(conf, null); - } - return job(res); - } finally { - if (needsChange) { - conf.locale = oldLocale; - res.updateConfiguration(conf, null); - } - } - } - } - } - - private static final HashMap sLocaleCache = CollectionUtils.newHashMap(); - - /** - * Creates a locale from a string specification. - */ - public static Locale constructLocaleFromString(final String localeStr) { - if (localeStr == null) - return null; - synchronized (sLocaleCache) { - if (sLocaleCache.containsKey(localeStr)) - return sLocaleCache.get(localeStr); - Locale retval = null; - String[] localeParams = localeStr.split("_", 3); - if (localeParams.length == 1) { - retval = new Locale(localeParams[0]); - } else if (localeParams.length == 2) { - retval = new Locale(localeParams[0], localeParams[1]); - } else if (localeParams.length == 3) { - retval = new Locale(localeParams[0], localeParams[1], localeParams[2]); - } - if (retval != null) { - sLocaleCache.put(localeStr, retval); - } - return retval; - } - } - - public static HashMap localeAndTimeStrToHashMap(String str) { - if (TextUtils.isEmpty(str)) { - return EMPTY_LT_HASH_MAP; - } - final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER); - final int N = ss.length; - if (N < 2 || N % 2 != 0) { - return EMPTY_LT_HASH_MAP; - } - final HashMap retval = CollectionUtils.newHashMap(); - for (int i = 0; i < N / 2; ++i) { - final String localeStr = ss[i * 2]; - final long time = Long.valueOf(ss[i * 2 + 1]); - retval.put(localeStr, time); - } - return retval; - } - - public static String localeAndTimeHashMapToStr(HashMap map) { - if (map == null || map.isEmpty()) { - return ""; - } - final StringBuilder builder = new StringBuilder(); - for (String localeStr : map.keySet()) { - if (builder.length() > 0) { - builder.append(LOCALE_AND_TIME_STR_SEPARATER); - } - final Long time = map.get(localeStr); - builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER); - builder.append(String.valueOf(time)); - } - return builder.toString(); - } -} diff --git a/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java b/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java deleted file mode 100644 index a98ecc7b6..000000000 --- a/java/src/com/android/inputmethod/latin/MetadataFileUriGetter.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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; - -import android.content.Context; - -/** - * Helper class to get the metadata URI and the additional ID. - */ -public class MetadataFileUriGetter { - private MetadataFileUriGetter() { - // This helper class is not instantiable. - } - - public static String getMetadataUri(final Context context) { - return context.getString(R.string.dictionary_pack_metadata_uri); - } - - public static String getMetadataAdditionalId(final Context context) { - return ""; - } -} diff --git a/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java b/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java index 291551301..07ccd75e6 100644 --- a/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java +++ b/java/src/com/android/inputmethod/latin/NativeSuggestOptions.java @@ -16,6 +16,8 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.utils.AdditionalFeaturesSettingUtils; + public class NativeSuggestOptions { // Need to update suggest_options.h when you add, remove or reorder options. private static final int IS_GESTURE = 0; diff --git a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java deleted file mode 100644 index a8800007a..000000000 --- a/java/src/com/android/inputmethod/latin/PositionalInfoForUserDictPendingAddition.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import android.view.inputmethod.EditorInfo; - -import java.util.Locale; - -/** - * Holder class for data about a word already committed but that may still be edited. - * - * When the user chooses to add a word to the user dictionary by pressing the appropriate - * suggestion, a dialog is presented to give a chance to edit the word before it is actually - * registered as a user dictionary word. If the word is actually modified, the IME needs to - * go back and replace the word that was committed with the amended version. - * The word we need to replace with will only be known after it's actually committed, so - * the IME needs to take a note of what it has to replace and where it is. - * This class encapsulates this data. - */ -public final class PositionalInfoForUserDictPendingAddition { - final private String mOriginalWord; - final private int mCursorPos; // Position of the cursor after the word - final private EditorInfo mEditorInfo; // On what binding this has been added - final private int mCapitalizedMode; - private String mActualWordBeingAdded; - - public PositionalInfoForUserDictPendingAddition(final String word, final int cursorPos, - final EditorInfo editorInfo, final int capitalizedMode) { - mOriginalWord = word; - mCursorPos = cursorPos; - mEditorInfo = editorInfo; - mCapitalizedMode = capitalizedMode; - } - - public void setActualWordBeingAdded(final String actualWordBeingAdded) { - mActualWordBeingAdded = actualWordBeingAdded; - } - - /** - * Try to replace the string at the remembered position with the actual word being added. - * - * After the user validated the word being added, the IME has to replace the old version - * (which has been committed in the text view) with the amended version if it's different. - * This method tries to do that, but may fail because the IME is not yet ready to do so - - * for example, it is still waiting for the new string, or it is waiting to return to the text - * view in which the amendment should be made. In these cases, we should keep the data - * and wait until all conditions are met. - * This method returns true if the replacement has been successfully made and this data - * can be forgotten; it returns false if the replacement can't be made yet and we need to - * keep this until a later time. - * The IME knows about the actual word being added through a callback called by the - * user dictionary facility of the device. When this callback comes, the keyboard may still - * be connected to the edition dialog, or it may have already returned to the original text - * field. Replacement has to work in both cases. - * Accordingly, this method is called at two different points in time : upon getting the - * event that a new word was added to the user dictionary, and upon starting up in a - * new text field. - * @param connection The RichInputConnection through which to contact the editor. - * @param editorInfo Information pertaining to the editor we are currently in. - * @param currentCursorPosition The current cursor position, for checking purposes. - * @param locale The locale for changing case, if necessary - * @return true if the edit has been successfully made, false if we need to try again later - */ - public boolean tryReplaceWithActualWord(final RichInputConnection connection, - final EditorInfo editorInfo, final int currentCursorPosition, final Locale locale) { - // If we still don't know the actual word being added, we need to try again later. - if (null == mActualWordBeingAdded) return false; - // The entered text and the registered text were the same anyway : we can - // return success right away even if focus has not returned yet to the text field we - // want to amend. - if (mActualWordBeingAdded.equals(mOriginalWord)) return true; - // Not the same text field : we need to try again later. This happens when the addition - // is reported by the user dictionary provider before the focus has moved back to the - // original text view, so the IME is still in the text view of the dialog and has no way to - // edit the original text view at this time. - if (!mEditorInfo.packageName.equals(editorInfo.packageName) - || mEditorInfo.fieldId != editorInfo.fieldId) { - return false; - } - // Same text field, but not the same cursor position : we give up, so we return success - // so that it won't be tried again - if (currentCursorPosition != mCursorPos) return true; - // We have made all the checks : do the replacement and report success - // If this was auto-capitalized, we need to restore the case before committing - final String wordWithCaseFixed = CapsModeUtils.applyAutoCapsMode(mActualWordBeingAdded, - mCapitalizedMode, locale); - connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(), - currentCursorPosition); - connection.commitText(wordWithCaseFixed, wordWithCaseFixed.length()); - return true; - } -} diff --git a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java deleted file mode 100644 index b9d7dcf78..000000000 --- a/java/src/com/android/inputmethod/latin/RecapitalizeStatus.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * 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; - -import com.android.inputmethod.latin.StringUtils; - -import java.util.Locale; - -/** - * The status of the current recapitalize process. - */ -public class RecapitalizeStatus { - public static final int NOT_A_RECAPITALIZE_MODE = -1; - public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0; - public static final int CAPS_MODE_ALL_LOWER = 1; - public static final int CAPS_MODE_FIRST_WORD_UPPER = 2; - public static final int CAPS_MODE_ALL_UPPER = 3; - // When adding a new mode, don't forget to update the CAPS_MODE_LAST constant. - public static final int CAPS_MODE_LAST = CAPS_MODE_ALL_UPPER; - - private static final int[] ROTATION_STYLE = { - CAPS_MODE_ORIGINAL_MIXED_CASE, - CAPS_MODE_ALL_LOWER, - CAPS_MODE_FIRST_WORD_UPPER, - CAPS_MODE_ALL_UPPER - }; - - private static final int getStringMode(final String string, final String separators) { - if (StringUtils.isIdenticalAfterUpcase(string)) { - return CAPS_MODE_ALL_UPPER; - } else if (StringUtils.isIdenticalAfterDowncase(string)) { - return CAPS_MODE_ALL_LOWER; - } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, separators)) { - return CAPS_MODE_FIRST_WORD_UPPER; - } else { - return CAPS_MODE_ORIGINAL_MIXED_CASE; - } - } - - /** - * We store the location of the cursor and the string that was there before the recapitalize - * action was done, and the location of the cursor and the string that was there after. - */ - private int mCursorStartBefore; - private String mStringBefore; - private int mCursorStartAfter; - private int mCursorEndAfter; - private int mRotationStyleCurrentIndex; - private boolean mSkipOriginalMixedCaseMode; - private Locale mLocale; - private String mSeparators; - private String mStringAfter; - private boolean mIsActive; - - public RecapitalizeStatus() { - // By default, initialize with dummy values that won't match any real recapitalize. - initialize(-1, -1, "", Locale.getDefault(), ""); - deactivate(); - } - - public void initialize(final int cursorStart, final int cursorEnd, final String string, - final Locale locale, final String separators) { - mCursorStartBefore = cursorStart; - mStringBefore = string; - mCursorStartAfter = cursorStart; - mCursorEndAfter = cursorEnd; - mStringAfter = string; - final int initialMode = getStringMode(mStringBefore, separators); - mLocale = locale; - mSeparators = separators; - if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) { - mRotationStyleCurrentIndex = 0; - mSkipOriginalMixedCaseMode = false; - } else { - // Find the current mode in the array. - int currentMode; - for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) { - if (ROTATION_STYLE[currentMode] == initialMode) { - break; - } - } - mRotationStyleCurrentIndex = currentMode; - mSkipOriginalMixedCaseMode = true; - } - mIsActive = true; - } - - public void deactivate() { - mIsActive = false; - } - - public boolean isActive() { - return mIsActive; - } - - public boolean isSetAt(final int cursorStart, final int cursorEnd) { - return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter; - } - - /** - * Rotate through the different possible capitalization modes. - */ - public void rotate() { - final String oldResult = mStringAfter; - int count = 0; // Protection against infinite loop. - do { - mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; - if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex] - && mSkipOriginalMixedCaseMode) { - mRotationStyleCurrentIndex = - (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; - } - ++count; - switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) { - case CAPS_MODE_ORIGINAL_MIXED_CASE: - mStringAfter = mStringBefore; - break; - case CAPS_MODE_ALL_LOWER: - mStringAfter = mStringBefore.toLowerCase(mLocale); - break; - case CAPS_MODE_FIRST_WORD_UPPER: - mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators, - mLocale); - break; - case CAPS_MODE_ALL_UPPER: - mStringAfter = mStringBefore.toUpperCase(mLocale); - break; - default: - mStringAfter = mStringBefore; - } - } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1); - mCursorEndAfter = mCursorStartAfter + mStringAfter.length(); - } - - /** - * Remove leading/trailing whitespace from the considered string. - */ - public void trim() { - final int len = mStringBefore.length(); - int nonWhitespaceStart = 0; - for (; nonWhitespaceStart < len; - nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) { - final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart); - if (!Character.isWhitespace(codePoint)) break; - } - int nonWhitespaceEnd = len; - for (; nonWhitespaceEnd > 0; - nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) { - final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd); - if (!Character.isWhitespace(codePoint)) break; - } - // If nonWhitespaceStart >= nonWhitespaceEnd, that means the selection contained only - // whitespace, so we leave it as is. - if ((0 != nonWhitespaceStart || len != nonWhitespaceEnd) - && nonWhitespaceStart < nonWhitespaceEnd) { - mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd; - mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart; - mStringAfter = mStringBefore = - mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd); - } - } - - public String getRecapitalizedString() { - return mStringAfter; - } - - public int getNewCursorStart() { - return mCursorStartAfter; - } - - public int getNewCursorEnd() { - return mCursorEndAfter; - } - - public int getCurrentMode() { - return ROTATION_STYLE[mRotationStyleCurrentIndex]; - } -} diff --git a/java/src/com/android/inputmethod/latin/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/ResizableIntArray.java deleted file mode 100644 index 691f0602a..000000000 --- a/java/src/com/android/inputmethod/latin/ResizableIntArray.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import java.util.Arrays; - -// TODO: This class is not thread-safe. -public final class ResizableIntArray { - private int[] mArray; - private int mLength; - - public ResizableIntArray(final int capacity) { - reset(capacity); - } - - public int get(final int index) { - if (index < mLength) { - return mArray[index]; - } - throw new ArrayIndexOutOfBoundsException("length=" + mLength + "; index=" + index); - } - - public void add(final int index, final int val) { - if (index < mLength) { - mArray[index] = val; - } else { - mLength = index; - add(val); - } - } - - public void add(final int val) { - final int currentLength = mLength; - ensureCapacity(currentLength + 1); - mArray[currentLength] = val; - mLength = currentLength + 1; - } - - /** - * Calculate the new capacity of {@code mArray}. - * @param minimumCapacity the minimum capacity that the {@code mArray} should have. - * @return the new capacity that the {@code mArray} should have. Returns zero when there is no - * need to expand {@code mArray}. - */ - private int calculateCapacity(final int minimumCapacity) { - final int currentCapcity = mArray.length; - if (currentCapcity < minimumCapacity) { - final int nextCapacity = currentCapcity * 2; - // The following is the same as return Math.max(minimumCapacity, nextCapacity); - return minimumCapacity > nextCapacity ? minimumCapacity : nextCapacity; - } - return 0; - } - - private void ensureCapacity(final int minimumCapacity) { - final int newCapacity = calculateCapacity(minimumCapacity); - if (newCapacity > 0) { - // TODO: Implement primitive array pool. - mArray = Arrays.copyOf(mArray, newCapacity); - } - } - - public int getLength() { - return mLength; - } - - public void setLength(final int newLength) { - ensureCapacity(newLength); - mLength = newLength; - } - - public void reset(final int capacity) { - // TODO: Implement primitive array pool. - mArray = new int[capacity]; - mLength = 0; - } - - public int[] getPrimitiveArray() { - return mArray; - } - - public void set(final ResizableIntArray ip) { - // TODO: Implement primitive array pool. - mArray = ip.mArray; - mLength = ip.mLength; - } - - public void copy(final ResizableIntArray ip) { - final int newCapacity = calculateCapacity(ip.mLength); - if (newCapacity > 0) { - // TODO: Implement primitive array pool. - mArray = new int[newCapacity]; - } - System.arraycopy(ip.mArray, 0, mArray, 0, ip.mLength); - mLength = ip.mLength; - } - - public void append(final ResizableIntArray src, final int startPos, final int length) { - if (length == 0) { - return; - } - final int currentLength = mLength; - final int newLength = currentLength + length; - ensureCapacity(newLength); - System.arraycopy(src.mArray, startPos, mArray, currentLength, length); - mLength = newLength; - } - - public void fill(final int value, final int startPos, final int length) { - if (startPos < 0 || length < 0) { - throw new IllegalArgumentException("startPos=" + startPos + "; length=" + length); - } - final int endPos = startPos + length; - ensureCapacity(endPos); - Arrays.fill(mArray, startPos, endPos, value); - if (mLength < endPos) { - mLength = endPos; - } - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mLength; i++) { - if (i != 0) { - sb.append(","); - } - sb.append(mArray[i]); - } - return "[" + sb + "]"; - } -} diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java deleted file mode 100644 index 0eb8b4f09..000000000 --- a/java/src/com/android/inputmethod/latin/ResourceUtils.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.os.Build; -import android.text.TextUtils; -import android.util.Log; -import android.util.TypedValue; - -import com.android.inputmethod.annotations.UsedForTesting; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.regex.PatternSyntaxException; - -public final class ResourceUtils { - private static final String TAG = ResourceUtils.class.getSimpleName(); - - public static final float UNDEFINED_RATIO = -1.0f; - public static final int UNDEFINED_DIMENSION = -1; - - private ResourceUtils() { - // This utility class is not publicly instantiable. - } - - private static final HashMap sDeviceOverrideValueMap = - CollectionUtils.newHashMap(); - - private static final String[] BUILD_KEYS_AND_VALUES = { - "HARDWARE", Build.HARDWARE, - "MODEL", Build.MODEL, - "BRAND", Build.BRAND, - "MANUFACTURER", Build.MANUFACTURER - }; - private static final HashMap sBuildKeyValues; - private static final String sBuildKeyValuesDebugString; - - static { - sBuildKeyValues = CollectionUtils.newHashMap(); - final ArrayList keyValuePairs = CollectionUtils.newArrayList(); - final int keyCount = BUILD_KEYS_AND_VALUES.length / 2; - for (int i = 0; i < keyCount; i++) { - final int index = i * 2; - final String key = BUILD_KEYS_AND_VALUES[index]; - final String value = BUILD_KEYS_AND_VALUES[index + 1]; - sBuildKeyValues.put(key, value); - keyValuePairs.add(key + '=' + value); - } - sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]"; - } - - public static String getDeviceOverrideValue(final Resources res, final int overrideResId) { - final int orientation = res.getConfiguration().orientation; - final String key = overrideResId + "-" + orientation; - if (sDeviceOverrideValueMap.containsKey(key)) { - return sDeviceOverrideValueMap.get(key); - } - - final String[] overrideArray = res.getStringArray(overrideResId); - final String overrideValue = findConstantForKeyValuePairs(sBuildKeyValues, overrideArray); - // The overrideValue might be an empty string. - if (overrideValue != null) { - Log.i(TAG, "Find override value:" - + " resource="+ res.getResourceEntryName(overrideResId) - + " build=" + sBuildKeyValuesDebugString - + " override=" + overrideValue); - sDeviceOverrideValueMap.put(key, overrideValue); - return overrideValue; - } - - String defaultValue = null; - try { - defaultValue = findDefaultConstant(overrideArray); - // The defaultValue might be an empty string. - if (defaultValue == null) { - Log.w(TAG, "Couldn't find override value nor default value:" - + " resource="+ res.getResourceEntryName(overrideResId) - + " build=" + sBuildKeyValuesDebugString); - } else { - Log.i(TAG, "Found default value:" - + " resource="+ res.getResourceEntryName(overrideResId) - + " build=" + sBuildKeyValuesDebugString - + " default=" + defaultValue); - } - } catch (final DeviceOverridePatternSyntaxError e) { - Log.w(TAG, "Syntax error, ignored", e); - } - sDeviceOverrideValueMap.put(key, defaultValue); - return defaultValue; - } - - @SuppressWarnings("serial") - static class DeviceOverridePatternSyntaxError extends Exception { - public DeviceOverridePatternSyntaxError(final String message, final String expression) { - this(message, expression, null); - } - - public DeviceOverridePatternSyntaxError(final String message, final String expression, - final Throwable throwable) { - super(message + ": " + expression, throwable); - } - } - - /** - * Find the condition that fulfills specified key value pairs from an array of - * "condition,constant", and return the corresponding string constant. A condition is - * "pattern1[:pattern2...] (or an empty string for the default). A pattern is - * "key=regexp_value" string. The condition matches only if all patterns of the condition - * are true for the specified key value pairs. - * - * For example, "condition,constant" has the following format. - * (See {@link ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()}) - * - HARDWARE=mako,constantForNexus4 - * - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4 - * - ,defaultConstant - * - * @param keyValuePairs attributes to be used to look for a matched condition. - * @param conditionConstantArray an array of "condition,constant" elements to be searched. - * @return the constant part of the matched "condition,constant" element. Returns null if no - * condition matches. - */ - @UsedForTesting - static String findConstantForKeyValuePairs(final HashMap keyValuePairs, - final String[] conditionConstantArray) { - if (conditionConstantArray == null || keyValuePairs == null) { - return null; - } - String foundValue = null; - for (final String conditionConstant : conditionConstantArray) { - final int posComma = conditionConstant.indexOf(','); - if (posComma < 0) { - Log.w(TAG, "Array element has no comma: " + conditionConstant); - continue; - } - final String condition = conditionConstant.substring(0, posComma); - if (condition.isEmpty()) { - // Default condition. The default condition should be searched by - // {@link #findConstantForDefault(String[])}. - continue; - } - try { - if (fulfillsCondition(keyValuePairs, condition)) { - // Take first match - if (foundValue == null) { - foundValue = conditionConstant.substring(posComma + 1); - } - // And continue walking through all conditions. - } - } catch (final DeviceOverridePatternSyntaxError e) { - Log.w(TAG, "Syntax error, ignored", e); - } - } - return foundValue; - } - - private static boolean fulfillsCondition(final HashMap keyValuePairs, - final String condition) throws DeviceOverridePatternSyntaxError { - final String[] patterns = condition.split(":"); - // Check all patterns in a condition are true - boolean matchedAll = true; - for (final String pattern : patterns) { - final int posEqual = pattern.indexOf('='); - if (posEqual < 0) { - throw new DeviceOverridePatternSyntaxError("Pattern has no '='", condition); - } - final String key = pattern.substring(0, posEqual); - final String value = keyValuePairs.get(key); - if (value == null) { - throw new DeviceOverridePatternSyntaxError("Unknown key", condition); - } - final String patternRegexpValue = pattern.substring(posEqual + 1); - try { - if (!value.matches(patternRegexpValue)) { - matchedAll = false; - // And continue walking through all patterns. - } - } catch (final PatternSyntaxException e) { - throw new DeviceOverridePatternSyntaxError("Syntax error", condition, e); - } - } - return matchedAll; - } - - @UsedForTesting - static String findDefaultConstant(final String[] conditionConstantArray) - throws DeviceOverridePatternSyntaxError { - if (conditionConstantArray == null) { - return null; - } - for (final String condition : conditionConstantArray) { - final int posComma = condition.indexOf(','); - if (posComma < 0) { - throw new DeviceOverridePatternSyntaxError("Array element has no comma", condition); - } - if (posComma == 0) { // condition is empty. - return condition.substring(posComma + 1); - } - } - return null; - } - - public static boolean isValidFraction(final float fraction) { - return fraction >= 0.0f; - } - - // {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size. - public static boolean isValidDimensionPixelSize(final int dimension) { - return dimension > 0; - } - - // {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset. - public static boolean isValidDimensionPixelOffset(final int dimension) { - return dimension >= 0; - } - - public static float getFraction(final TypedArray a, final int index, final float defValue) { - final TypedValue value = a.peekValue(index); - if (value == null || !isFractionValue(value)) { - return defValue; - } - return a.getFraction(index, 1, 1, defValue); - } - - public static float getFraction(final TypedArray a, final int index) { - return getFraction(a, index, UNDEFINED_RATIO); - } - - public static int getDimensionPixelSize(final TypedArray a, final int index) { - final TypedValue value = a.peekValue(index); - if (value == null || !isDimensionValue(value)) { - return ResourceUtils.UNDEFINED_DIMENSION; - } - return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION); - } - - public static float getDimensionOrFraction(final TypedArray a, final int index, final int base, - final float defValue) { - final TypedValue value = a.peekValue(index); - if (value == null) { - return defValue; - } - if (isFractionValue(value)) { - return a.getFraction(index, base, base, defValue); - } else if (isDimensionValue(value)) { - return a.getDimension(index, defValue); - } - return defValue; - } - - public static int getEnumValue(final TypedArray a, final int index, final int defValue) { - final TypedValue value = a.peekValue(index); - if (value == null) { - return defValue; - } - if (isIntegerValue(value)) { - return a.getInt(index, defValue); - } - return defValue; - } - - public static boolean isFractionValue(final TypedValue v) { - return v.type == TypedValue.TYPE_FRACTION; - } - - public static boolean isDimensionValue(final TypedValue v) { - return v.type == TypedValue.TYPE_DIMENSION; - } - - public static boolean isIntegerValue(final TypedValue v) { - return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT; - } - - public static boolean isStringValue(final TypedValue v) { - return v.type == TypedValue.TYPE_STRING; - } -} diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 980215de6..be7f93c72 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -28,6 +28,9 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.utils.CapsModeUtils; +import com.android.inputmethod.latin.utils.LogUtils; +import com.android.inputmethod.latin.utils.StringUtils; import com.android.inputmethod.research.ResearchLogger; import java.util.Locale; @@ -105,7 +108,7 @@ public final class RichInputConnection { + "\nActual text = " + reference.length() + " " + reference; ((LatinIME)mParent).debugDumpStateAndCrashWithException(context); } else { - Log.e(TAG, Utils.getStackTrace(2)); + Log.e(TAG, LogUtils.getStackTrace(2)); Log.e(TAG, "Exp <> Actual : " + mCurrentCursorPosition + " <> " + et.selectionStart); } } @@ -154,7 +157,7 @@ public final class RichInputConnection { if (mNestLevel != 1) { // TODO: exception instead Log.e(TAG, "Batch edit level incorrect : " + mNestLevel); - Log.e(TAG, Utils.getStackTrace(4)); + Log.e(TAG, LogUtils.getStackTrace(4)); } } diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java index 86f75635d..5b1a2dde4 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -28,6 +28,7 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.Collections; import java.util.HashMap; diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index a6149c6ec..084d3308c 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -23,7 +23,9 @@ import android.content.res.Resources; import android.preference.PreferenceManager; import android.util.Log; -import com.android.inputmethod.latin.LocaleUtils.RunInLocale; +import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.latin.utils.LocaleUtils.RunInLocale; +import com.android.inputmethod.latin.utils.ResourceUtils; import java.util.HashMap; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/latin/SettingsFragment.java b/java/src/com/android/inputmethod/latin/SettingsFragment.java index 8017ce161..46fa1913c 100644 --- a/java/src/com/android/inputmethod/latin/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/SettingsFragment.java @@ -35,16 +35,19 @@ import android.preference.PreferenceScreen; import android.util.Log; import android.view.inputmethod.InputMethodSubtype; -import java.util.TreeSet; - import com.android.inputmethod.dictionarypack.DictionarySettingsActivity; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager; import com.android.inputmethod.latin.userdictionary.UserDictionaryList; import com.android.inputmethod.latin.userdictionary.UserDictionarySettings; +import com.android.inputmethod.latin.utils.AdditionalFeaturesSettingUtils; +import com.android.inputmethod.latin.utils.FeedbackUtils; +import com.android.inputmethod.latin.utils.Utils; import com.android.inputmethod.research.ResearchLogger; import com.android.inputmethodcommon.InputMethodSettingsFragment; +import java.util.TreeSet; + public final class SettingsFragment extends InputMethodSettingsFragment implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = SettingsFragment.class.getSimpleName(); diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 09102447f..8eadf73b2 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -24,6 +24,10 @@ import android.view.inputmethod.EditorInfo; import com.android.inputmethod.keyboard.internal.KeySpecParser; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.utils.AdditionalFeaturesSettingUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.InputTypeUtils; +import com.android.inputmethod.latin.utils.StringUtils; import java.util.ArrayList; import java.util.Arrays; diff --git a/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java deleted file mode 100644 index e50af4d2d..000000000 --- a/java/src/com/android/inputmethod/latin/StaticInnerHandlerWrapper.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin; - -import android.os.Handler; -import android.os.Looper; - -import java.lang.ref.WeakReference; - -public class StaticInnerHandlerWrapper extends Handler { - private final WeakReference mOuterInstanceRef; - - public StaticInnerHandlerWrapper(final T outerInstance) { - this(outerInstance, Looper.myLooper()); - } - - public StaticInnerHandlerWrapper(final T outerInstance, final Looper looper) { - super(looper); - if (outerInstance == null) { - throw new NullPointerException("outerInstance is null"); - } - mOuterInstanceRef = new WeakReference(outerInstance); - } - - public T getOuterInstance() { - return mOuterInstanceRef.get(); - } -} diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java deleted file mode 100644 index c2fd4fb32..000000000 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import android.text.TextUtils; - -import java.util.ArrayList; -import java.util.Locale; - -public final class StringUtils { - public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case - public static final int CAPITALIZE_FIRST = 1; // First only - public static final int CAPITALIZE_ALL = 2; // All caps - - private StringUtils() { - // This utility class is not publicly instantiable. - } - - public static int codePointCount(final String text) { - if (TextUtils.isEmpty(text)) return 0; - return text.codePointCount(0, text.length()); - } - - public static boolean containsInArray(final String text, final String[] array) { - for (final String element : array) { - if (text.equals(element)) return true; - } - return false; - } - - /** - * Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax. - * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain - * a comma character in it. - */ - private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ","; - - public static boolean containsInCommaSplittableText(final String text, - final String extraValues) { - if (TextUtils.isEmpty(extraValues)) { - return false; - } - return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT)); - } - - public static String appendToCommaSplittableTextIfNotExists(final String text, - final String extraValues) { - if (TextUtils.isEmpty(extraValues)) { - return text; - } - if (containsInCommaSplittableText(text, extraValues)) { - return extraValues; - } - return extraValues + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + text; - } - - public static String removeFromCommaSplittableTextIfExists(final String text, - final String extraValues) { - if (TextUtils.isEmpty(extraValues)) { - return ""; - } - final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT); - if (!containsInArray(text, elements)) { - return extraValues; - } - final ArrayList result = CollectionUtils.newArrayList(elements.length - 1); - for (final String element : elements) { - if (!text.equals(element)) { - result.add(element); - } - } - return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result); - } - - /** - * Remove duplicates from an array of strings. - * - * This method will always keep the first occurrence of all strings at their position - * in the array, removing the subsequent ones. - */ - public static void removeDupes(final ArrayList suggestions) { - if (suggestions.size() < 2) return; - int i = 1; - // Don't cache suggestions.size(), since we may be removing items - while (i < suggestions.size()) { - final String cur = suggestions.get(i); - // Compare each suggestion with each previous suggestion - for (int j = 0; j < i; j++) { - final String previous = suggestions.get(j); - if (TextUtils.equals(cur, previous)) { - suggestions.remove(i); - i--; - break; - } - } - i++; - } - } - - public static String capitalizeFirstCodePoint(final String s, final Locale locale) { - if (s.length() <= 1) { - return s.toUpperCase(locale); - } - // Please refer to the comment below in - // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings - final int cutoff = s.offsetByCodePoints(0, 1); - return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff); - } - - public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) { - if (s.length() <= 1) { - return s.toUpperCase(locale); - } - // TODO: fix the bugs below - // - This does not work for Greek, because it returns upper case instead of title case. - // - It does not work for Serbian, because it fails to account for the "lj" character, - // which should be "Lj" in title case and "LJ" in upper case. - // - It does not work for Dutch, because it fails to account for the "ij" digraph when it's - // written as two separate code points. They are two different characters but both should - // be capitalized as "IJ" as if they were a single letter in most words (not all). If the - // unicode char for the ligature is used however, it works. - final int cutoff = s.offsetByCodePoints(0, 1); - return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff).toLowerCase(locale); - } - - private static final int[] EMPTY_CODEPOINTS = {}; - - public static int[] toCodePointArray(final String string) { - final int length = string.length(); - if (length <= 0) { - return EMPTY_CODEPOINTS; - } - final int[] codePoints = new int[string.codePointCount(0, length)]; - int destIndex = 0; - for (int index = 0; index < length; index = string.offsetByCodePoints(index, 1)) { - codePoints[destIndex] = string.codePointAt(index); - destIndex++; - } - return codePoints; - } - - // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE. - public static int getCapitalizationType(final String text) { - // If the first char is not uppercase, then the word is either all lower case or - // camel case, and in either case we return CAPITALIZE_NONE. - final int len = text.length(); - int index = 0; - for (; index < len; index = text.offsetByCodePoints(index, 1)) { - if (Character.isLetter(text.codePointAt(index))) { - break; - } - } - if (index == len) return CAPITALIZE_NONE; - if (!Character.isUpperCase(text.codePointAt(index))) { - return CAPITALIZE_NONE; - } - int capsCount = 1; - int letterCount = 1; - for (index = text.offsetByCodePoints(index, 1); index < len; - index = text.offsetByCodePoints(index, 1)) { - if (1 != capsCount && letterCount != capsCount) break; - final int codePoint = text.codePointAt(index); - if (Character.isUpperCase(codePoint)) { - ++capsCount; - ++letterCount; - } else if (Character.isLetter(codePoint)) { - // We need to discount non-letters since they may not be upper-case, but may - // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME") - ++letterCount; - } - } - // We know the first char is upper case. So we want to test if either every letter other - // than the first is lower case, or if they are all upper case. If the string is exactly - // one char long, then we will arrive here with letterCount 1, and this is correct, too. - if (1 == capsCount) return CAPITALIZE_FIRST; - return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); - } - - public static boolean isIdenticalAfterUpcase(final String text) { - final int len = text.length(); - for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { - final int codePoint = text.codePointAt(i); - if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) { - return false; - } - } - return true; - } - - public static boolean isIdenticalAfterDowncase(final String text) { - final int len = text.length(); - for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { - final int codePoint = text.codePointAt(i); - if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) { - return false; - } - } - return true; - } - - public static boolean isIdenticalAfterCapitalizeEachWord(final String text, - final String separators) { - boolean needCapsNext = true; - final int len = text.length(); - for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { - final int codePoint = text.codePointAt(i); - if (Character.isLetter(codePoint)) { - if ((needCapsNext && !Character.isUpperCase(codePoint)) - || (!needCapsNext && !Character.isLowerCase(codePoint))) { - return false; - } - } - // We need a capital letter next if this is a separator. - needCapsNext = (-1 != separators.indexOf(codePoint)); - } - return true; - } - - // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph - // which should be capitalized together in *some* cases. - public static String capitalizeEachWord(final String text, final String separators, - final Locale locale) { - final StringBuilder builder = new StringBuilder(); - boolean needCapsNext = true; - final int len = text.length(); - for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { - final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1)); - if (needCapsNext) { - builder.append(nextChar.toUpperCase(locale)); - } else { - builder.append(nextChar.toLowerCase(locale)); - } - // We need a capital letter next if this is a separator. - needCapsNext = (-1 != separators.indexOf(nextChar.codePointAt(0))); - } - return builder.toString(); - } - - /** - * Approximates whether the text before the cursor looks like a URL. - * - * This is not foolproof, but it should work well in the practice. - * Essentially it walks backward from the cursor until it finds something that's not a letter, - * digit, or common URL symbol like underscore. If it hasn't found a period yet, then it - * does not look like a URL. - * If the text: - * - starts with www and contains a period - * - starts with a slash preceded by either a slash, whitespace, or start-of-string - * Then it looks like a URL and we return true. Otherwise, we return false. - * - * Note: this method is called quite often, and should be fast. - * - * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the - * code complexity, but ideally it should not. It's acceptable for now. - */ - public static boolean lastPartLooksLikeURL(final CharSequence text) { - int i = text.length(); - if (0 == i) return false; - int wCount = 0; - int slashCount = 0; - boolean hasSlash = false; - boolean hasPeriod = false; - int codePoint = 0; - while (i > 0) { - codePoint = Character.codePointBefore(text, i); - if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') { - // Handwavy heuristic to see if that's a URL character. Anything between period - // and z. This includes all lower- and upper-case ascii letters, period, - // underscore, arrobase, question mark, equal sign. It excludes spaces, exclamation - // marks, double quotes... - // Anything that's not a URL-like character causes us to break from here and - // evaluate normally. - break; - } - if (Constants.CODE_PERIOD == codePoint) { - hasPeriod = true; - } - if (Constants.CODE_SLASH == codePoint) { - hasSlash = true; - if (2 == ++slashCount) { - return true; - } - } else { - slashCount = 0; - } - if ('w' == codePoint) { - ++wCount; - } else { - wCount = 0; - } - i = Character.offsetByCodePoints(text, i, -1); - } - // End of the text run. - // If it starts with www and includes a period, then it looks like a URL. - if (wCount >= 3 && hasPeriod) return true; - // If it starts with a slash, and the code point before is whitespace, it looks like an URL. - if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true; - // If it has both a period and a slash, it looks like an URL. - if (hasPeriod && hasSlash) return true; - // Otherwise, it doesn't look like an URL. - return false; - } -} diff --git a/java/src/com/android/inputmethod/latin/SubtypeLocale.java b/java/src/com/android/inputmethod/latin/SubtypeLocale.java index 4d88ecc0c..3f94aca3a 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeLocale.java +++ b/java/src/com/android/inputmethod/latin/SubtypeLocale.java @@ -25,7 +25,11 @@ import android.os.Build; import android.util.Log; import android.view.inputmethod.InputMethodSubtype; -import com.android.inputmethod.latin.LocaleUtils.RunInLocale; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.latin.utils.LocaleUtils.RunInLocale; +import com.android.inputmethod.latin.utils.LogUtils; +import com.android.inputmethod.latin.utils.StringUtils; import java.util.HashMap; import java.util.Locale; @@ -238,7 +242,7 @@ public final class SubtypeLocale { + " nameResId=" + subtype.getNameResId() + " locale=" + subtype.getLocale() + " extra=" + subtype.getExtraValue() - + "\n" + Utils.getStackTrace()); + + "\n" + LogUtils.getStackTrace()); return ""; } } diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index e783e6d51..8b31f4e99 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -22,6 +22,9 @@ import android.text.TextUtils; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.utils.BoundedTreeSet; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.StringUtils; import java.util.ArrayList; import java.util.Comparator; diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 7a16595a7..5b47dda0d 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -19,6 +19,9 @@ package com.android.inputmethod.latin; import android.text.TextUtils; import android.view.inputmethod.CompletionInfo; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.StringUtils; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; diff --git a/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java deleted file mode 100644 index 947b0c586..000000000 --- a/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.AsyncTask; -import android.util.LruCache; - -public final class TargetPackageInfoGetterTask extends - AsyncTask { - private static final int MAX_CACHE_ENTRIES = 64; // arbitrary - private static final LruCache sCache = - new LruCache(MAX_CACHE_ENTRIES); - - public static PackageInfo getCachedPackageInfo(final String packageName) { - if (null == packageName) return null; - return sCache.get(packageName); - } - - public static void removeCachedPackageInfo(final String packageName) { - sCache.remove(packageName); - } - - public interface OnTargetPackageInfoKnownListener { - public void onTargetPackageInfoKnown(final PackageInfo info); - } - - private Context mContext; - private final OnTargetPackageInfoKnownListener mListener; - - public TargetPackageInfoGetterTask(final Context context, - final OnTargetPackageInfoKnownListener listener) { - mContext = context; - mListener = listener; - } - - @Override - protected PackageInfo doInBackground(final String... packageName) { - final PackageManager pm = mContext.getPackageManager(); - mContext = null; // Bazooka-powered anti-leak device - try { - final PackageInfo packageInfo = pm.getPackageInfo(packageName[0], 0 /* flags */); - sCache.put(packageName[0], packageInfo); - return packageInfo; - } catch (android.content.pm.PackageManager.NameNotFoundException e) { - return null; - } - } - - @Override - protected void onPostExecute(final PackageInfo info) { - mListener.onTargetPackageInfoKnown(info); - } -} diff --git a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java index 90f92972a..ba84c1ad3 100644 --- a/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserBinaryDictionary.java @@ -28,6 +28,7 @@ import android.provider.UserDictionary.Words; import android.text.TextUtils; import com.android.inputmethod.compat.UserDictionaryCompatUtils; +import com.android.inputmethod.latin.utils.LocaleUtils; import java.util.Arrays; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java deleted file mode 100644 index 5233ed7d3..000000000 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; -import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; -import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface; -import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; -import com.android.inputmethod.latin.makedict.FusionDictionary; -import com.android.inputmethod.latin.makedict.FusionDictionary.Node; -import com.android.inputmethod.latin.makedict.PendingAttribute; -import com.android.inputmethod.latin.makedict.UnsupportedFormatException; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -/** - * Reads and writes Binary files for a UserHistoryDictionary. - * - * All the methods in this class are static. - */ -public final class UserHistoryDictIOUtils { - private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName(); - private static final boolean DEBUG = false; - - public interface OnAddWordListener { - public void setUnigram(final String word, final String shortcutTarget, final int frequency); - public void setBigram(final String word1, final String word2, final int frequency); - } - - @UsedForTesting - public interface BigramDictionaryInterface { - public int getFrequency(final String word1, final String word2); - } - - /** - * Writes dictionary to file. - */ - public static void writeDictionaryBinary(final OutputStream destination, - final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams, - final FormatOptions formatOptions) { - final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams); - try { - BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, formatOptions); - Log.d(TAG, "end writing"); - } catch (IOException e) { - Log.e(TAG, "IO exception while writing file", e); - } catch (UnsupportedFormatException e) { - Log.e(TAG, "Unsupported format", e); - } - } - - /** - * Constructs a new FusionDictionary from BigramDictionaryInterface. - */ - @UsedForTesting - static FusionDictionary constructFusionDictionary( - final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) { - final FusionDictionary fusionDict = new FusionDictionary(new Node(), - new FusionDictionary.DictionaryOptions(new HashMap(), false, - false)); - int profTotal = 0; - for (final String word1 : bigrams.keySet()) { - final HashMap word1Bigrams = bigrams.getBigrams(word1); - for (final String word2 : word1Bigrams.keySet()) { - final int freq = dict.getFrequency(word1, word2); - if (freq == -1) { - // don't add this bigram. - continue; - } - if (DEBUG) { - if (word1 == null) { - Log.d(TAG, "add unigram: " + word2 + "," + Integer.toString(freq)); - } else { - Log.d(TAG, "add bigram: " + word1 - + "," + word2 + "," + Integer.toString(freq)); - } - profTotal++; - } - if (word1 == null) { // unigram - fusionDict.add(word2, freq, null, false /* isNotAWord */); - } else { // bigram - if (FusionDictionary.findWordInTree(fusionDict.mRoot, word1) == null) { - fusionDict.add(word1, 2, null, false /* isNotAWord */); - } - fusionDict.setBigram(word1, word2, freq); - } - bigrams.updateBigram(word1, word2, (byte)freq); - } - } - if (DEBUG) { - Log.d(TAG, "add " + profTotal + "words"); - } - return fusionDict; - } - - /** - * Reads dictionary from file. - */ - public static void readDictionaryBinary(final FusionDictionaryBufferInterface buffer, - final OnAddWordListener dict) { - final Map unigrams = CollectionUtils.newTreeMap(); - final Map frequencies = CollectionUtils.newTreeMap(); - final Map> bigrams = CollectionUtils.newTreeMap(); - try { - BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies, - bigrams); - } catch (IOException e) { - Log.e(TAG, "IO exception while reading file", e); - } catch (UnsupportedFormatException e) { - Log.e(TAG, "Unsupported format", e); - } catch (ArrayIndexOutOfBoundsException e) { - Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file", e); - } - addWordsFromWordMap(unigrams, frequencies, bigrams, dict); - } - - /** - * Adds all unigrams and bigrams in maps to OnAddWordListener. - */ - @UsedForTesting - static void addWordsFromWordMap(final Map unigrams, - final Map frequencies, - final Map> bigrams, final OnAddWordListener to) { - for (Map.Entry entry : unigrams.entrySet()) { - final String word1 = entry.getValue(); - final int unigramFrequency = frequencies.get(entry.getKey()); - to.setUnigram(word1, null, unigramFrequency); - final ArrayList attrList = bigrams.get(entry.getKey()); - if (attrList != null) { - for (final PendingAttribute attr : attrList) { - final String word2 = unigrams.get(attr.mAddress); - if (word1 == null || word2 == null) { - Log.e(TAG, "Invalid bigram pair detected: " + word1 + ", " + word2); - continue; - } - to.setBigram(word1, word2, - BinaryDictInputOutput.reconstructBigramFrequency(unigramFrequency, - attr.mFrequency)); - } - } - } - - } -} diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java index ab5724724..ca2d8840b 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java @@ -24,10 +24,13 @@ import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.UserHistoryDictIOUtils.BigramDictionaryInterface; -import com.android.inputmethod.latin.UserHistoryDictIOUtils.OnAddWordListener; -import com.android.inputmethod.latin.UserHistoryForgettingCurveUtils.ForgettingCurveParams; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils; +import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.BigramDictionaryInterface; +import com.android.inputmethod.latin.utils.UserHistoryDictIOUtils.OnAddWordListener; +import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils; +import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils.ForgettingCurveParams; import java.io.File; import java.io.FileInputStream; diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java index 316f09603..28941c4a1 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionaryBigramList.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin; import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; import java.util.Set; diff --git a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java deleted file mode 100644 index 9053d709b..000000000 --- a/java/src/com/android/inputmethod/latin/UserHistoryForgettingCurveUtils.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2012 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; - -import android.text.format.DateUtils; -import android.util.Log; - -public final class UserHistoryForgettingCurveUtils { - private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName(); - private static final boolean DEBUG = false; - private static final int FC_FREQ_MAX = 127; - /* package */ static final int COUNT_MAX = 3; - private static final int FC_LEVEL_MAX = 3; - /* package */ static final int ELAPSED_TIME_MAX = 15; - private static final int ELAPSED_TIME_INTERVAL_HOURS = 6; - private static final long ELAPSED_TIME_INTERVAL_MILLIS = ELAPSED_TIME_INTERVAL_HOURS - * DateUtils.HOUR_IN_MILLIS; - private static final int HALF_LIFE_HOURS = 48; - private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1); - - private UserHistoryForgettingCurveUtils() { - // This utility class is not publicly instantiable. - } - - public static final class ForgettingCurveParams { - private byte mFc; - long mLastTouchedTime = 0; - private final boolean mIsValid; - - private void updateLastTouchedTime() { - mLastTouchedTime = System.currentTimeMillis(); - } - - public ForgettingCurveParams(boolean isValid) { - this(System.currentTimeMillis(), isValid); - } - - private ForgettingCurveParams(long now, boolean isValid) { - this(pushCount((byte)0, isValid), now, now, isValid); - } - - /** This constructor is called when the user history bigram dictionary is being restored. */ - public ForgettingCurveParams(int fc, long now, long last) { - // All words with level >= 1 had been saved. - // Invalid words with level == 0 had been saved. - // Valid words words with level == 0 had *not* been saved. - this(fc, now, last, fcToLevel((byte)fc) > 0); - } - - private ForgettingCurveParams(int fc, long now, long last, boolean isValid) { - mIsValid = isValid; - mFc = (byte)fc; - mLastTouchedTime = last; - updateElapsedTime(now); - } - - public boolean isValid() { - return mIsValid; - } - - public byte getFc() { - updateElapsedTime(System.currentTimeMillis()); - return mFc; - } - - public int getFrequency() { - updateElapsedTime(System.currentTimeMillis()); - return UserHistoryForgettingCurveUtils.fcToFreq(mFc); - } - - public int notifyTypedAgainAndGetFrequency() { - updateLastTouchedTime(); - // TODO: Check whether this word is valid or not - mFc = pushCount(mFc, false); - return UserHistoryForgettingCurveUtils.fcToFreq(mFc); - } - - private void updateElapsedTime(long now) { - final int elapsedTimeCount = - (int)((now - mLastTouchedTime) / ELAPSED_TIME_INTERVAL_MILLIS); - if (elapsedTimeCount <= 0) { - return; - } - if (elapsedTimeCount >= MAX_PUSH_ELAPSED) { - mLastTouchedTime = now; - mFc = 0; - return; - } - for (int i = 0; i < elapsedTimeCount; ++i) { - mLastTouchedTime += ELAPSED_TIME_INTERVAL_MILLIS; - mFc = pushElapsedTime(mFc); - } - } - } - - /* package */ static int fcToElapsedTime(byte fc) { - return fc & 0x0F; - } - - /* package */ static int fcToCount(byte fc) { - return (fc >> 4) & 0x03; - } - - /* package */ static int fcToLevel(byte fc) { - return (fc >> 6) & 0x03; - } - - private static int calcFreq(int elapsedTime, int count, int level) { - if (level <= 0) { - // Reserved words, just return -1 - return -1; - } - if (count == COUNT_MAX) { - // Temporary promote because it's frequently typed recently - ++level; - } - final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime)); - final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level)); - return MathUtils.SCORE_TABLE[l - 1][et]; - } - - /* pakcage */ static byte calcFc(int elapsedTime, int count, int level) { - final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime)); - final int c = Math.min(COUNT_MAX, Math.max(0, count)); - final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level)); - return (byte)(et | (c << 4) | (l << 6)); - } - - public static int fcToFreq(byte fc) { - final int elapsedTime = fcToElapsedTime(fc); - final int count = fcToCount(fc); - final int level = fcToLevel(fc); - return calcFreq(elapsedTime, count, level); - } - - public static byte pushElapsedTime(byte fc) { - int elapsedTime = fcToElapsedTime(fc); - int count = fcToCount(fc); - int level = fcToLevel(fc); - if (elapsedTime >= ELAPSED_TIME_MAX) { - // Downgrade level - elapsedTime = 0; - count = COUNT_MAX; - --level; - } else { - ++elapsedTime; - } - return calcFc(elapsedTime, count, level); - } - - public static byte pushCount(byte fc, boolean isValid) { - final int elapsedTime = fcToElapsedTime(fc); - int count = fcToCount(fc); - int level = fcToLevel(fc); - if ((elapsedTime == 0 && count >= COUNT_MAX) || (isValid && level == 0)) { - // Upgrade level - ++level; - count = 0; - if (DEBUG) { - Log.d(TAG, "Upgrade level."); - } - } else { - ++count; - } - return calcFc(0, count, level); - } - - // TODO: isValid should be false for a word whose frequency is 0, - // or that is not in the dictionary. - /** - * Check wheather we should save the bigram to the SQL DB or not - */ - public static boolean needsToSave(byte fc, boolean isValid, boolean addLevel0Bigram) { - int level = fcToLevel(fc); - if (level == 0) { - if (isValid || !addLevel0Bigram) { - return false; - } - } - final int elapsedTime = fcToElapsedTime(fc); - return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0); - } - - private static final class MathUtils { - public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1]; - static { - for (int i = 0; i < FC_LEVEL_MAX; ++i) { - final float initialFreq; - if (i >= 2) { - initialFreq = FC_FREQ_MAX; - } else if (i == 1) { - initialFreq = FC_FREQ_MAX / 2; - } else if (i == 0) { - initialFreq = FC_FREQ_MAX / 4; - } else { - continue; - } - for (int j = 0; j < ELAPSED_TIME_MAX; ++j) { - final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS; - final float freq = initialFreq - * (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS); - final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq)); - SCORE_TABLE[i][j] = intFreq; - } - } - } - } -} diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java deleted file mode 100644 index 949720fda..000000000 --- a/java/src/com/android/inputmethod/latin/Utils.java +++ /dev/null @@ -1,511 +0,0 @@ -/* - * Copyright (C) 2010 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; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.inputmethodservice.InputMethodService; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -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; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.channels.FileChannel; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -public final class Utils { - private static final String TAG = Utils.class.getSimpleName(); - - private Utils() { - // This utility class is not publicly instantiable. - } - - /** - * Cancel an {@link AsyncTask}. - * - * @param mayInterruptIfRunning true if the thread executing this - * task should be interrupted; otherwise, in-progress tasks are allowed - * to complete. - */ - public static void cancelTask(final AsyncTask task, - final boolean mayInterruptIfRunning) { - if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { - task.cancel(mayInterruptIfRunning); - } - } - - /* package */ static final class RingCharBuffer { - private static RingCharBuffer sRingCharBuffer = new RingCharBuffer(); - private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC'; - private static final int INVALID_COORDINATE = -2; - /* package */ static final int BUFSIZE = 20; - private InputMethodService mContext; - private boolean mEnabled = false; - private int mEnd = 0; - /* package */ int mLength = 0; - private char[] mCharBuf = new char[BUFSIZE]; - private int[] mXBuf = new int[BUFSIZE]; - private int[] mYBuf = new int[BUFSIZE]; - - private RingCharBuffer() { - // Intentional empty constructor for singleton. - } - - @UsedForTesting - public static RingCharBuffer getInstance() { - return sRingCharBuffer; - } - - public static RingCharBuffer init(final InputMethodService context, final boolean enabled, - final boolean usabilityStudy) { - if (!(enabled || usabilityStudy)) { - return null; - } - sRingCharBuffer.mContext = context; - sRingCharBuffer.mEnabled = true; - UsabilityStudyLogUtils.getInstance().init(context); - return sRingCharBuffer; - } - - private static int normalize(final int in) { - int ret = in % BUFSIZE; - return ret < 0 ? ret + BUFSIZE : ret; - } - - // TODO: accept code points - @UsedForTesting - public void push(final char c, final int x, final int y) { - if (!mEnabled) { - return; - } - mCharBuf[mEnd] = c; - mXBuf[mEnd] = x; - mYBuf[mEnd] = y; - mEnd = normalize(mEnd + 1); - if (mLength < BUFSIZE) { - ++mLength; - } - } - - public char pop() { - if (mLength < 1) { - return PLACEHOLDER_DELIMITER_CHAR; - } - mEnd = normalize(mEnd - 1); - --mLength; - return mCharBuf[mEnd]; - } - - public char getBackwardNthChar(final int n) { - if (mLength <= n || n < 0) { - return PLACEHOLDER_DELIMITER_CHAR; - } - return mCharBuf[normalize(mEnd - n - 1)]; - } - - public int getPreviousX(final char c, final int back) { - final int index = normalize(mEnd - 2 - back); - if (mLength <= back - || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { - return INVALID_COORDINATE; - } - return mXBuf[index]; - } - - public int getPreviousY(final char c, final int back) { - int index = normalize(mEnd - 2 - back); - if (mLength <= back - || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { - return INVALID_COORDINATE; - } - return mYBuf[index]; - } - - public String getLastWord(final int ignoreCharCount) { - final StringBuilder sb = new StringBuilder(); - final LatinIME latinIme = (LatinIME)mContext; - int i = ignoreCharCount; - for (; i < mLength; ++i) { - final char c = mCharBuf[normalize(mEnd - 1 - i)]; - if (!latinIme.isWordSeparator(c)) { - break; - } - } - for (; i < mLength; ++i) { - char c = mCharBuf[normalize(mEnd - 1 - i)]; - if (!latinIme.isWordSeparator(c)) { - sb.append(c); - } else { - break; - } - } - return sb.reverse().toString(); - } - - public void reset() { - mLength = 0; - } - } - - // Get the current stack trace - public static String getStackTrace(final int limit) { - final StringBuilder sb = new StringBuilder(); - try { - throw new RuntimeException(); - } catch (final RuntimeException e) { - final StackTraceElement[] frames = e.getStackTrace(); - // Start at 1 because the first frame is here and we don't care about it - for (int j = 1; j < frames.length && j < limit + 1; ++j) { - sb.append(frames[j].toString() + "\n"); - } - } - return sb.toString(); - } - - public static String getStackTrace() { - return getStackTrace(Integer.MAX_VALUE - 1); - } - - public static final class UsabilityStudyLogUtils { - // TODO: remove code duplication with ResearchLog class - private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName(); - private static final String FILENAME = "log.txt"; - private final Handler mLoggingHandler; - private File mFile; - private File mDirectory; - private InputMethodService mIms; - private PrintWriter mWriter; - private final Date mDate; - private final SimpleDateFormat mDateFormat; - - private UsabilityStudyLogUtils() { - mDate = new Date(); - mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US); - - HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task", - Process.THREAD_PRIORITY_BACKGROUND); - handlerThread.start(); - mLoggingHandler = new Handler(handlerThread.getLooper()); - } - - // Initialization-on-demand holder - private static final class OnDemandInitializationHolder { - public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils(); - } - - public static UsabilityStudyLogUtils getInstance() { - return OnDemandInitializationHolder.sInstance; - } - - public void init(final InputMethodService ims) { - mIms = ims; - mDirectory = ims.getFilesDir(); - } - - private void createLogFileIfNotExist() { - if ((mFile == null || !mFile.exists()) - && (mDirectory != null && mDirectory.exists())) { - try { - mWriter = getPrintWriter(mDirectory, FILENAME, false); - } catch (final IOException e) { - Log.e(USABILITY_TAG, "Can't create log file."); - } - } - } - - public static void writeBackSpace(final int x, final int y) { - UsabilityStudyLogUtils.getInstance().write("\t" + x + "\t" + y); - } - - public static void writeChar(final char c, final int x, final int y) { - String inputChar = String.valueOf(c); - switch (c) { - case '\n': - inputChar = ""; - break; - case '\t': - inputChar = ""; - break; - case ' ': - inputChar = ""; - break; - } - UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y); - LatinImeLogger.onPrintAllUsabilityStudyLogs(); - } - - public void write(final String log) { - mLoggingHandler.post(new Runnable() { - @Override - public void run() { - createLogFileIfNotExist(); - final long currentTime = System.currentTimeMillis(); - mDate.setTime(currentTime); - - final String printString = String.format(Locale.US, "%s\t%d\t%s\n", - mDateFormat.format(mDate), currentTime, log); - if (LatinImeLogger.sDBG) { - Log.d(USABILITY_TAG, "Write: " + log); - } - mWriter.print(printString); - } - }); - } - - private synchronized String getBufferedLogs() { - mWriter.flush(); - final StringBuilder sb = new StringBuilder(); - final BufferedReader br = getBufferedReader(); - String line; - try { - while ((line = br.readLine()) != null) { - sb.append('\n'); - sb.append(line); - } - } catch (final IOException e) { - Log.e(USABILITY_TAG, "Can't read log file."); - } finally { - if (LatinImeLogger.sDBG) { - Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString()); - } - try { - br.close(); - } catch (final IOException e) { - // ignore. - } - } - return sb.toString(); - } - - public void emailResearcherLogsAll() { - mLoggingHandler.post(new Runnable() { - @Override - public void run() { - final Date date = new Date(); - date.setTime(System.currentTimeMillis()); - final String currentDateTimeString = - new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date); - if (mFile == null) { - Log.w(USABILITY_TAG, "No internal log file found."); - return; - } - if (mIms.checkCallingOrSelfPermission( - android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE"); - return; - } - mWriter.flush(); - final String destPath = Environment.getExternalStorageDirectory() - + "/research-" + currentDateTimeString + ".log"; - final File destFile = new File(destPath); - try { - final FileInputStream srcStream = new FileInputStream(mFile); - final FileOutputStream destStream = new FileOutputStream(destFile); - final FileChannel src = srcStream.getChannel(); - final FileChannel dest = destStream.getChannel(); - src.transferTo(0, src.size(), dest); - src.close(); - srcStream.close(); - dest.close(); - destStream.close(); - } catch (final FileNotFoundException e1) { - Log.w(USABILITY_TAG, e1); - return; - } catch (final IOException e2) { - Log.w(USABILITY_TAG, e2); - return; - } - if (destFile == null || !destFile.exists()) { - Log.w(USABILITY_TAG, "Dest file doesn't exist."); - return; - } - final Intent intent = new Intent(Intent.ACTION_SEND); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if (LatinImeLogger.sDBG) { - Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI()); - } - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath)); - intent.putExtra(Intent.EXTRA_SUBJECT, - "[Research Logs] " + currentDateTimeString); - mIms.startActivity(intent); - } - }); - } - - public void printAll() { - mLoggingHandler.post(new Runnable() { - @Override - public void run() { - mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0); - } - }); - } - - public void clearAll() { - mLoggingHandler.post(new Runnable() { - @Override - public void run() { - if (mFile != null && mFile.exists()) { - if (LatinImeLogger.sDBG) { - Log.d(USABILITY_TAG, "Delete log file."); - } - mFile.delete(); - mWriter.close(); - } - } - }); - } - - private BufferedReader getBufferedReader() { - createLogFileIfNotExist(); - try { - return new BufferedReader(new FileReader(mFile)); - } catch (final FileNotFoundException e) { - return null; - } - } - - private PrintWriter getPrintWriter(final File dir, final String filename, - final boolean renew) throws IOException { - mFile = new File(dir, filename); - if (mFile.exists()) { - if (renew) { - mFile.delete(); - } - } - return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */); - } - } - - public static final class Stats { - public static void onNonSeparator(final char code, final int x, final int y) { - RingCharBuffer.getInstance().push(code, x, y); - LatinImeLogger.logOnInputChar(); - } - - public static void onSeparator(final int code, final int x, final int y) { - // Helper method to log a single code point separator - // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils - onSeparator(new String(new int[]{code}, 0, 1), x, y); - } - - public static void onSeparator(final String separator, final int x, final int y) { - final int length = separator.length(); - for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) { - int codePoint = Character.codePointAt(separator, i); - // TODO: accept code points - RingCharBuffer.getInstance().push((char)codePoint, x, y); - } - LatinImeLogger.logOnInputSeparator(); - } - - public static void onAutoCorrection(final String typedWord, final String correctedWord, - final String separatorString, final WordComposer wordComposer) { - final boolean isBatchMode = wordComposer.isBatchMode(); - if (!isBatchMode && TextUtils.isEmpty(typedWord)) { - return; - } - // TODO: this fails when the separator is more than 1 code point long, but - // the backend can't handle it yet. The only case when this happens is with - // smileys and other multi-character keys. - final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE - : separatorString.codePointAt(0); - if (!isBatchMode) { - LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint); - } else { - if (!TextUtils.isEmpty(correctedWord)) { - // We must make sure that InputPointer contains only the relative timestamps, - // not actual timestamps. - LatinImeLogger.logOnAutoCorrectionForGeometric( - "", correctedWord, codePoint, wordComposer.getInputPointers()); - } - } - } - - public static void onAutoCorrectionCancellation() { - LatinImeLogger.logOnAutoCorrectionCancelled(); - } - } - - public static String getDebugInfo(final SuggestedWords suggestions, final int pos) { - if (!LatinImeLogger.sDBG) { - return null; - } - final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); - if (wordInfo == null) { - return null; - } - final String info = wordInfo.getDebugString(); - if (TextUtils.isEmpty(info)) { - return null; - } - return info; - } - - public static int getAcitivityTitleResId(final Context context, - final Class cls) { - final ComponentName cn = new ComponentName(context, cls); - try { - final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0); - if (ai != null) { - return ai.labelRes; - } - } catch (final NameNotFoundException e) { - Log.e(TAG, "Failed to get settings activity title res id.", e); - } - return 0; - } - - public static String getVersionName(final Context context) { - try { - if (context == null) { - return ""; - } - final String packageName = context.getPackageName(); - final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); - return info.versionName; - } catch (final NameNotFoundException e) { - Log.e(TAG, "Could not find version info.", e); - } - return ""; - } -} diff --git a/java/src/com/android/inputmethod/latin/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/XmlParseUtils.java deleted file mode 100644 index 48e5ed30a..000000000 --- a/java/src/com/android/inputmethod/latin/XmlParseUtils.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.latin; - -import android.content.res.TypedArray; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; - -public final class XmlParseUtils { - private XmlParseUtils() { - // This utility class is not publicly instantiable. - } - - @SuppressWarnings("serial") - public static class ParseException extends XmlPullParserException { - public ParseException(final String msg, final XmlPullParser parser) { - super(msg + " at " + parser.getPositionDescription()); - } - } - - @SuppressWarnings("serial") - public static final class IllegalStartTag extends ParseException { - public IllegalStartTag(final XmlPullParser parser, final String tag, final String parent) { - super("Illegal start tag " + tag + " in " + parent, parser); - } - } - - @SuppressWarnings("serial") - public static final class IllegalEndTag extends ParseException { - public IllegalEndTag(final XmlPullParser parser, final String tag, final String parent) { - super("Illegal end tag " + tag + " in " + parent, parser); - } - } - - @SuppressWarnings("serial") - public static final class IllegalAttribute extends ParseException { - public IllegalAttribute(final XmlPullParser parser, final String tag, - final String attribute) { - super("Tag " + tag + " has illegal attribute " + attribute, parser); - } - } - - @SuppressWarnings("serial") - public static final class NonEmptyTag extends ParseException{ - public NonEmptyTag(final XmlPullParser parser, final String tag) { - super(tag + " must be empty tag", parser); - } - } - - public static void checkEndTag(final String tag, final XmlPullParser parser) - throws XmlPullParserException, IOException { - if (parser.next() == XmlPullParser.END_TAG && tag.equals(parser.getName())) - return; - throw new NonEmptyTag(parser, tag); - } - - public static void checkAttributeExists(final TypedArray attr, final int attrId, - final String attrName, final String tag, final XmlPullParser parser) - throws XmlPullParserException { - if (attr.hasValue(attrId)) { - return; - } - throw new ParseException( - "No " + attrName + " attribute found in <" + tag + "/>", parser); - } -} diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java index 13fa9d9c8..978c0642d 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java @@ -35,10 +35,10 @@ import android.widget.VideoView; 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.SettingsActivity; -import com.android.inputmethod.latin.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 13fcaf48a..692e7392c 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -25,17 +25,17 @@ import android.view.textservice.SuggestionsInfo; import com.android.inputmethod.keyboard.KeyboardLayoutSet; import com.android.inputmethod.latin.BinaryDictionary; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.ContactsBinaryDictionary; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.DictionaryCollection; import com.android.inputmethod.latin.DictionaryFactory; -import com.android.inputmethod.latin.LocaleUtils; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.SynchronouslyLoadedContactsBinaryDictionary; import com.android.inputmethod.latin.SynchronouslyLoadedUserBinaryDictionary; import com.android.inputmethod.latin.UserBinaryDictionary; +import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.latin.utils.StringUtils; import java.lang.ref.WeakReference; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java index 9a1114f7f..ddda52d71 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java @@ -23,7 +23,7 @@ import android.view.textservice.SentenceSuggestionsInfo; import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; -import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 16e9fb77e..6719e98da 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -30,11 +30,11 @@ import android.view.textservice.TextInfo; import com.android.inputmethod.compat.SuggestionsInfoCompatUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.LocaleUtils; -import com.android.inputmethod.latin.StringUtils; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService.SuggestionsGatherer; +import com.android.inputmethod.latin.utils.LocaleUtils; +import com.android.inputmethod.latin.utils.StringUtils; import java.util.ArrayList; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java index a20e09ee8..ac8f68781 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java @@ -19,10 +19,10 @@ package com.android.inputmethod.latin.spellcheck; import android.util.Log; import com.android.inputmethod.keyboard.ProximityInfo; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.Locale; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java index 5ce9d8e47..da5812603 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerSettingsFragment.java @@ -21,7 +21,7 @@ import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.Utils; +import com.android.inputmethod.latin.utils.Utils; /** * Preference screen. diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java index 322ae5b0f..2218b3bc6 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java @@ -31,7 +31,7 @@ import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.Utils; +import com.android.inputmethod.latin.utils.Utils; public final class MoreSuggestions extends Keyboard { public static final int SUGGESTION_CODE_BASE = 1024; diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index 916f483b9..b6caf9b7b 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -49,9 +49,9 @@ import com.android.inputmethod.keyboard.ViewLayoutUtils; import com.android.inputmethod.latin.AutoCorrection; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.ResourceUtils; import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.Utils; +import com.android.inputmethod.latin.utils.ResourceUtils; +import com.android.inputmethod.latin.utils.Utils; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index 2284a4607..fcebdd457 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -34,7 +34,6 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.keyboard.MoreKeysPanel; import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; -import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; @@ -42,6 +41,7 @@ import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener; +import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java index 89ec7466e..dad72963d 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java @@ -16,10 +16,6 @@ package com.android.inputmethod.latin.userdictionary; -import com.android.inputmethod.compat.UserDictionaryCompatUtils; -import com.android.inputmethod.latin.LocaleUtils; -import com.android.inputmethod.latin.R; - import android.app.Activity; import android.content.ContentResolver; import android.content.Context; @@ -30,6 +26,10 @@ import android.text.TextUtils; import android.view.View; import android.widget.EditText; +import com.android.inputmethod.compat.UserDictionaryCompatUtils; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.LocaleUtils; + import java.util.ArrayList; import java.util.Locale; import java.util.TreeSet; diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java index 6e64882b6..e7cf0d3af 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java @@ -16,9 +16,6 @@ package com.android.inputmethod.latin.userdictionary; -import com.android.inputmethod.latin.LocaleUtils; -import com.android.inputmethod.latin.R; - import android.app.Activity; import android.content.Intent; import android.database.Cursor; @@ -29,6 +26,9 @@ import android.preference.PreferenceGroup; import android.provider.UserDictionary; import android.text.TextUtils; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.LocaleUtils; + import java.util.Locale; import java.util.TreeSet; diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalFeaturesSettingUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalFeaturesSettingUtils.java new file mode 100644 index 000000000..18dfb3dba --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/AdditionalFeaturesSettingUtils.java @@ -0,0 +1,48 @@ +/* + * 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.utils; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.android.inputmethod.latin.Settings; +import com.android.inputmethodcommon.InputMethodSettingsFragment; + +/** + * Utility class for managing additional features settings. + */ +public class AdditionalFeaturesSettingUtils { + public static final int ADDITIONAL_FEATURES_SETTINGS_SIZE = 0; + + private AdditionalFeaturesSettingUtils() { + // This utility class is not publicly instantiable. + } + + public static void addAdditionalFeaturesPreferences( + final Context context, final InputMethodSettingsFragment settingsFragment) { + // do nothing. + } + + public static void readAdditionalFeaturesPreferencesIntoArray( + final SharedPreferences prefs, final int[] additionalFeaturesPreferences) { + // do nothing. + } + + public static int[] getAdditionalNativeSuggestOptions() { + return Settings.getInstance().getCurrent().mAdditionalFeaturesSettingValues; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java new file mode 100644 index 000000000..ae1fd3f79 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/BoundedTreeSet.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 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.utils; + +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; + +import java.util.Collection; +import java.util.Comparator; +import java.util.TreeSet; + +/** + * A TreeSet that is bounded in size and throws everything that's smaller than its limit + */ +public final class BoundedTreeSet extends TreeSet { + private final int mCapacity; + public BoundedTreeSet(final Comparator comparator, final int capacity) { + super(comparator); + mCapacity = capacity; + } + + @Override + public boolean add(final SuggestedWordInfo e) { + if (size() < mCapacity) return super.add(e); + if (comparator().compare(e, last()) > 0) return false; + super.add(e); + pollLast(); // removes the last element + return true; + } + + @Override + public boolean addAll(final Collection e) { + if (null == e) return false; + return super.addAll(e); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java new file mode 100644 index 000000000..2f91c5743 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java @@ -0,0 +1,269 @@ +/* + * 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.utils; + +import android.text.InputType; +import android.text.TextUtils; + +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.WordComposer; + +import java.util.Locale; + +public final class CapsModeUtils { + private CapsModeUtils() { + // This utility class is not publicly instantiable. + } + + /** + * Apply an auto-caps mode to a string. + * + * This intentionally does NOT apply manual caps mode. It only changes the capitalization if + * the mode is one of the auto-caps modes. + * @param s The string to capitalize. + * @param capitalizeMode The mode in which to capitalize. + * @param locale The locale for capitalizing. + * @return The capitalized string. + */ + public static String applyAutoCapsMode(final String s, final int capitalizeMode, + final Locale locale) { + if (WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == capitalizeMode) { + return s.toUpperCase(locale); + } else if (WordComposer.CAPS_MODE_AUTO_SHIFTED == capitalizeMode) { + return StringUtils.capitalizeFirstCodePoint(s, locale); + } else { + return s; + } + } + + /** + * Return whether a constant represents an auto-caps mode (either auto-shift or auto-shift-lock) + * @param mode The mode to test for + * @return true if this represents an auto-caps mode, false otherwise + */ + public static boolean isAutoCapsMode(final int mode) { + return WordComposer.CAPS_MODE_AUTO_SHIFTED == mode + || WordComposer.CAPS_MODE_AUTO_SHIFT_LOCKED == mode; + } + + /** + * Determine what caps mode should be in effect at the current offset in + * the text. Only the mode bits set in reqModes will be + * checked. Note that the caps mode flags here are explicitly defined + * to match those in {@link InputType}. + * + * This code is a straight copy of TextUtils.getCapsMode (modulo namespace and formatting + * issues). This will change in the future as we simplify the code for our use and fix bugs. + * + * @param cs The text that should be checked for caps modes. + * @param reqModes The modes to be checked: may be any combination of + * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and + * {@link TextUtils#CAP_MODE_SENTENCES}. + * @param locale The locale to consider for capitalization rules + * @param hasSpaceBefore Whether we should consider there is a space inserted at the end of cs + * + * @return Returns the actual capitalization modes that can be in effect + * at the current position, which is any combination of + * {@link TextUtils#CAP_MODE_CHARACTERS}, {@link TextUtils#CAP_MODE_WORDS}, and + * {@link TextUtils#CAP_MODE_SENTENCES}. + */ + public static int getCapsMode(final CharSequence cs, final int reqModes, final Locale locale, + final boolean hasSpaceBefore) { + // Quick description of what we want to do: + // CAP_MODE_CHARACTERS is always on. + // CAP_MODE_WORDS is on if there is some whitespace before the cursor. + // CAP_MODE_SENTENCES is on if there is some whitespace before the cursor, and the end + // of a sentence just before that. + // We ignore opening parentheses and the like just before the cursor for purposes of + // finding whitespace for WORDS and SENTENCES modes. + // The end of a sentence ends with a period, question mark or exclamation mark. If it's + // a period, it also needs not to be an abbreviation, which means it also needs to either + // be immediately preceded by punctuation, or by a string of only letters with single + // periods interleaved. + + // Step 1 : check for cap MODE_CHARACTERS. If it's looked for, it's always on. + if ((reqModes & (TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES)) == 0) { + // Here we are not looking for MODE_WORDS or MODE_SENTENCES, so since we already + // evaluated MODE_CHARACTERS, we can return. + return TextUtils.CAP_MODE_CHARACTERS & reqModes; + } + + // Step 2 : Skip (ignore at the end of input) any opening punctuation. This includes + // opening parentheses, brackets, opening quotes, everything that *opens* a span of + // text in the linguistic sense. In RTL languages, this is still an opening sign, although + // it may look like a right parenthesis for example. We also include double quote and + // single quote since they aren't start punctuation in the unicode sense, but should still + // be skipped for English. TODO: does this depend on the language? + int i; + if (hasSpaceBefore) { + i = cs.length() + 1; + } else { + for (i = cs.length(); i > 0; i--) { + final char c = cs.charAt(i - 1); + if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE + && Character.getType(c) != Character.START_PUNCTUATION) { + break; + } + } + } + + // We are now on the character that precedes any starting punctuation, so in the most + // frequent case this will be whitespace or a letter, although it may occasionally be a + // start of line, or some symbol. + + // Step 3 : Search for the start of a paragraph. From the starting point computed in step 2, + // we go back over any space or tab char sitting there. We find the start of a paragraph + // if the first char that's not a space or tab is a start of line (as in \n, start of text, + // or some other similar characters). + int j = i; + char prevChar = Constants.CODE_SPACE; + if (hasSpaceBefore) --j; + while (j > 0) { + prevChar = cs.charAt(j - 1); + if (!Character.isSpaceChar(prevChar) && prevChar != Constants.CODE_TAB) break; + j--; + } + if (j <= 0 || Character.isWhitespace(prevChar)) { + // There are only spacing chars between the start of the paragraph and the cursor, + // defined as a isWhitespace() char that is neither a isSpaceChar() nor a tab. Both + // MODE_WORDS and MODE_SENTENCES should be active. + return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS + | TextUtils.CAP_MODE_SENTENCES) & reqModes; + } + if (i == j) { + // If we don't have whitespace before index i, it means neither MODE_WORDS + // nor mode sentences should be on so we can return right away. + return TextUtils.CAP_MODE_CHARACTERS & reqModes; + } + if ((reqModes & TextUtils.CAP_MODE_SENTENCES) == 0) { + // Here we know we have whitespace before the cursor (if not, we returned in the above + // if i == j clause), so we need MODE_WORDS to be on. And we don't need to evaluate + // MODE_SENTENCES so we can return right away. + return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; + } + // Please note that because of the reqModes & CAP_MODE_SENTENCES test a few lines above, + // we know that MODE_SENTENCES is being requested. + + // Step 4 : Search for MODE_SENTENCES. + // English is a special case in that "American typography" rules, which are the most common + // in English, state that a sentence terminator immediately following a quotation mark + // should be swapped with it and de-duplicated (included in the quotation mark), + // e.g. <> + // No other language has such a rule as far as I know, instead putting inside the quotation + // mark as the exact thing quoted and handling the surrounding punctuation independently, + // e.g. <> + // Hence, specifically for English, we treat this special case here. + if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) { + for (; j > 0; j--) { + // Here we look to go over any closing punctuation. This is because in dominant + // variants of English, the final period is placed within double quotes and maybe + // other closing punctuation signs. This is generally not true in other languages. + final char c = cs.charAt(j - 1); + if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE + && Character.getType(c) != Character.END_PUNCTUATION) { + break; + } + } + } + + if (j <= 0) return TextUtils.CAP_MODE_CHARACTERS & reqModes; + char c = cs.charAt(--j); + + // We found the next interesting chunk of text ; next we need to determine if it's the + // end of a sentence. If we have a question mark or an exclamation mark, it's the end of + // a sentence. If it's neither, the only remaining case is the period so we get the opposite + // case out of the way. + if (c == Constants.CODE_QUESTION_MARK || c == Constants.CODE_EXCLAMATION_MARK) { + return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_SENTENCES) & reqModes; + } + if (c != Constants.CODE_PERIOD || j <= 0) { + return (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; + } + + // We found out that we have a period. We need to determine if this is a full stop or + // otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation + // looks like (\w\.){2,} + // To find out, we will have a simple state machine with the following states : + // START, WORD, PERIOD, ABBREVIATION + // On START : (just before the first period) + // letter => WORD + // whitespace => end with no caps (it was a stand-alone period) + // otherwise => end with caps (several periods/symbols in a row) + // On WORD : (within the word just before the first period) + // letter => WORD + // period => PERIOD + // otherwise => end with caps (it was a word with a full stop at the end) + // On PERIOD : (period within a potential abbreviation) + // letter => LETTER + // otherwise => end with caps (it was not an abbreviation) + // On LETTER : (letter within a potential abbreviation) + // letter => LETTER + // period => PERIOD + // otherwise => end with no caps (it was an abbreviation) + // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This + // should capitalize. + + final int START = 0; + final int WORD = 1; + final int PERIOD = 2; + final int LETTER = 3; + final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS + | TextUtils.CAP_MODE_SENTENCES) & reqModes; + final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; + int state = START; + while (j > 0) { + c = cs.charAt(--j); + switch (state) { + case START: + if (Character.isLetter(c)) { + state = WORD; + } else if (Character.isWhitespace(c)) { + return noCaps; + } else { + return caps; + } + break; + case WORD: + if (Character.isLetter(c)) { + state = WORD; + } else if (c == Constants.CODE_PERIOD) { + state = PERIOD; + } else { + return caps; + } + break; + case PERIOD: + if (Character.isLetter(c)) { + state = LETTER; + } else { + return caps; + } + break; + case LETTER: + if (Character.isLetter(c)) { + state = LETTER; + } else if (c == Constants.CODE_PERIOD) { + state = PERIOD; + } else { + return noCaps; + } + } + } + // Here we arrived at the start of the line. This should behave exactly like whitespace. + return (START == state || LETTER == state) ? noCaps : caps; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java new file mode 100644 index 000000000..98f0d8b68 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2012 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.utils; + +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +public final class CollectionUtils { + private CollectionUtils() { + // This utility class is not publicly instantiable. + } + + public static HashMap newHashMap() { + return new HashMap(); + } + + public static WeakHashMap newWeakHashMap() { + return new WeakHashMap(); + } + + public static TreeMap newTreeMap() { + return new TreeMap(); + } + + public static Map newSynchronizedTreeMap() { + final TreeMap treeMap = newTreeMap(); + return Collections.synchronizedMap(treeMap); + } + + public static ConcurrentHashMap newConcurrentHashMap() { + return new ConcurrentHashMap(); + } + + public static HashSet newHashSet() { + return new HashSet(); + } + + public static TreeSet newTreeSet() { + return new TreeSet(); + } + + public static ArrayList newArrayList() { + return new ArrayList(); + } + + public static ArrayList newArrayList(final int initialCapacity) { + return new ArrayList(initialCapacity); + } + + public static ArrayList newArrayList(final Collection collection) { + return new ArrayList(collection); + } + + public static LinkedList newLinkedList() { + return new LinkedList(); + } + + public static CopyOnWriteArrayList newCopyOnWriteArrayList() { + return new CopyOnWriteArrayList(); + } + + public static CopyOnWriteArrayList newCopyOnWriteArrayList( + final Collection collection) { + return new CopyOnWriteArrayList(collection); + } + + public static CopyOnWriteArrayList newCopyOnWriteArrayList(final E[] array) { + return new CopyOnWriteArrayList(array); + } + + public static SparseArray newSparseArray() { + return new SparseArray(); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java new file mode 100644 index 000000000..5ccf0e079 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/CompletionInfoUtils.java @@ -0,0 +1,43 @@ +/* + * 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.utils; + +import android.text.TextUtils; +import android.view.inputmethod.CompletionInfo; + +import java.util.Arrays; + +/** + * Utilities to do various stuff with CompletionInfo. + */ +public class CompletionInfoUtils { + private CompletionInfoUtils() { + // This utility class is not publicly instantiable. + } + + public static CompletionInfo[] removeNulls(final CompletionInfo[] src) { + int j = 0; + final CompletionInfo[] dst = new CompletionInfo[src.length]; + for (int i = 0; i < src.length; ++i) { + if (null != src[i] && !TextUtils.isEmpty(src[i].getText())) { + dst[j] = src[i]; + ++j; + } + } + return Arrays.copyOfRange(dst, 0, j); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java new file mode 100644 index 000000000..72f2cd2d9 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/CoordinateUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 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.utils; + +public final class CoordinateUtils { + private static final int INDEX_X = 0; + private static final int INDEX_Y = 1; + private static final int ARRAY_SIZE = INDEX_Y + 1; + + private CoordinateUtils() { + // This utility class is not publicly instantiable. + } + + public static int[] newInstance() { + return new int[ARRAY_SIZE]; + } + + public static int x(final int[] coords) { + return coords[INDEX_X]; + } + + public static int y(final int[] coords) { + return coords[INDEX_Y]; + } + + public static void set(final int[] coords, final int x, final int y) { + coords[INDEX_X] = x; + coords[INDEX_Y] = y; + } + + public static void copy(final int[] destination, final int[] source) { + destination[INDEX_X] = source[INDEX_X]; + destination[INDEX_Y] = source[INDEX_Y]; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java index 999c2f0de..159ebb1b9 100644 --- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java @@ -17,7 +17,6 @@ package com.android.inputmethod.latin.utils; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.CollectionUtils; import java.util.ArrayList; diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java new file mode 100644 index 000000000..b3d37d78c --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java @@ -0,0 +1,366 @@ +/* + * 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.utils; + +import android.content.ContentValues; +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.text.format.DateUtils; +import android.util.Log; + +import com.android.inputmethod.latin.AssetFileAddress; +import com.android.inputmethod.latin.BinaryDictionaryGetter; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; +import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; +import com.android.inputmethod.latin.makedict.UnsupportedFormatException; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Locale; + +/** + * This class encapsulates the logic for the Latin-IME side of dictionary information management. + */ +public class DictionaryInfoUtils { + private static final String TAG = DictionaryInfoUtils.class.getSimpleName(); + private static final String RESOURCE_PACKAGE_NAME = R.class.getPackage().getName(); + private static final String DEFAULT_MAIN_DICT = "main"; + 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; + + public static class DictionaryInfo { + 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 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; + } + 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); + values.put(FILESIZE_COLUMN, mFileAddress.mLength); + values.put(VERSION_COLUMN, mVersion); + return values; + } + } + + private DictionaryInfoUtils() { + // Private constructor to forbid instantation of this helper class. + } + + /** + * Returns whether we may want to use this character as part of a file name. + * + * This basically only accepts ascii letters and numbers, and rejects everything else. + */ + private static boolean isFileNameCharacter(int codePoint) { + if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit + if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase + if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase + return codePoint == '_'; // Underscore + } + + /** + * Escapes a string for any characters that may be suspicious for a file or directory name. + * + * Concretely this does a sort of URL-encoding except it will encode everything that's not + * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which + * we cannot allow here) + */ + // TODO: create a unit test for this method + public static String replaceFileNameDangerousCharacters(final String name) { + // This assumes '%' is fully available as a non-separator, normal + // character in a file name. This is probably true for all file systems. + final StringBuilder sb = new StringBuilder(); + final int nameLength = name.length(); + for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) { + final int codePoint = name.codePointAt(i); + if (DictionaryInfoUtils.isFileNameCharacter(codePoint)) { + sb.appendCodePoint(codePoint); + } else { + sb.append(String.format((Locale)null, "%%%1$0" + MAX_HEX_DIGITS_FOR_CODEPOINT + "x", + codePoint)); + } + } + return sb.toString(); + } + + /** + * Helper method to get the top level cache directory. + */ + private static String getWordListCacheDirectory(final Context context) { + return context.getFilesDir() + File.separator + "dicts"; + } + + /** + * Helper method to get the top level temp directory. + */ + public static String getWordListTempDirectory(final Context context) { + return context.getFilesDir() + File.separator + "tmp"; + } + + /** + * Reverse escaping done by replaceFileNameDangerousCharacters. + */ + public static String getWordListIdFromFileName(final String fname) { + final StringBuilder sb = new StringBuilder(); + final int fnameLength = fname.length(); + for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) { + final int codePoint = fname.codePointAt(i); + if ('%' != codePoint) { + sb.appendCodePoint(codePoint); + } else { + // + 1 to pass the % sign + final int encodedCodePoint = Integer.parseInt( + fname.substring(i + 1, i + 1 + MAX_HEX_DIGITS_FOR_CODEPOINT), 16); + i += MAX_HEX_DIGITS_FOR_CODEPOINT; + sb.appendCodePoint(encodedCodePoint); + } + } + return sb.toString(); + } + + /** + * Helper method to the list of cache directories, one for each distinct locale. + */ + public static File[] getCachedDirectoryList(final Context context) { + return new File(DictionaryInfoUtils.getWordListCacheDirectory(context)).listFiles(); + } + + /** + * 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. + */ + public static String getCategoryFromFileName(final String fileName) { + final String id = getWordListIdFromFileName(fileName); + final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR); + // An id is supposed to be in format category:locale, so splitting on the separator + // should yield a 2-elements array + if (2 != idArray.length) return null; + return idArray[0]; + } + + /** + * Find out the cache directory associated with a specific locale. + */ + private static String getCacheDirectoryForLocale(final String locale, final Context context) { + final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale); + final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator + + relativeDirectoryName; + final File directory = new File(absoluteDirectoryName); + if (!directory.exists()) { + if (!directory.mkdirs()) { + Log.e(TAG, "Could not create the directory for locale" + locale); + } + } + return absoluteDirectoryName; + } + + /** + * Generates a file name for the id and locale passed as an argument. + * + * In the current implementation the file name returned will always be unique for + * any id/locale pair, but please do not expect that the id can be the same for + * different dictionaries with different locales. An id should be unique for any + * dictionary. + * The file name is pretty much an URL-encoded version of the id inside a directory + * named like the locale, except it will also escape characters that look dangerous + * to some file systems. + * @param id the id of the dictionary for which to get a file name + * @param locale the locale for which to get the file name as a string + * @param context the context to use for getting the directory + * @return the name of the file to be created + */ + public static String getCacheFileName(String id, String locale, Context context) { + final String fileName = replaceFileNameDangerousCharacters(id); + return getCacheDirectoryForLocale(locale, context) + File.separator + fileName; + } + + public static boolean isMainWordListId(final String id) { + final String[] idArray = id.split(BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR); + // An id is supposed to be in format category:locale, so splitting on the separator + // should yield a 2-elements array + if (2 != idArray.length) return false; + return BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY.equals(idArray[0]); + } + + /** + * Helper method to return a dictionary res id for a locale, or 0 if none. + * @param locale dictionary locale + * @return main dictionary resource id + */ + public static int getMainDictionaryResourceIdIfAvailableForLocale(final Resources res, + final Locale locale) { + int resId; + // Try to find main_language_country dictionary. + if (!locale.getCountry().isEmpty()) { + final String dictLanguageCountry = + MAIN_DICT_PREFIX + locale.toString().toLowerCase(Locale.ROOT); + if ((resId = res.getIdentifier( + dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) { + return resId; + } + } + + // Try to find main_language dictionary. + final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage(); + if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) { + return resId; + } + + // Not found, return 0 + return 0; + } + + /** + * Returns a main dictionary resource id + * @param locale dictionary locale + * @return main dictionary resource id + */ + public static int getMainDictionaryResourceId(final Resources res, final Locale locale) { + int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale); + if (0 != resourceId) return resourceId; + return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME); + } + + /** + * Returns the id associated with the main word list for a specified locale. + * + * Word lists stored in Android Keyboard's resources are referred to as the "main" + * word lists. Since they can be updated like any other list, we need to assign a + * unique ID to them. This ID is just the name of the language (locale-wise) they + * are for, and this method returns this ID. + */ + public static String getMainDictId(final Locale locale) { + // 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 BinaryDictionaryGetter.MAIN_DICTIONARY_CATEGORY + + BinaryDictionaryGetter.ID_CATEGORY_SEPARATOR + locale.getLanguage().toString(); + } + + public static FileHeader getDictionaryFileHeaderOrNull(final File file) { + try { + return BinaryDictIOUtils.getDictionaryFileHeader(file, 0, file.length()); + } catch (UnsupportedFormatException e) { + return null; + } catch (IOException e) { + return null; + } + } + + private static DictionaryInfo createDictionaryInfoFromFileAddress( + 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(id, locale, description, fileAddress, Integer.parseInt(version)); + } + + private static void addOrUpdateDictInfo(final ArrayList dictList, + final DictionaryInfo newElement) { + final Iterator iter = dictList.iterator(); + while (iter.hasNext()) { + final DictionaryInfo thisDictInfo = iter.next(); + if (thisDictInfo.mLocale.equals(newElement.mLocale)) { + if (newElement.mVersion <= thisDictInfo.mVersion) { + return; + } + iter.remove(); + } + } + dictList.add(newElement); + } + + public static ArrayList getCurrentDictionaryFileNameAndVersionInfo( + final Context context) { + final ArrayList dictList = CollectionUtils.newArrayList(); + + // Retrieve downloaded dictionaries + final File[] directoryList = getCachedDirectoryList(context); + if (null != directoryList) { + for (final File directory : directoryList) { + final String localeString = getWordListIdFromFileName(directory.getName()); + File[] dicts = BinaryDictionaryGetter.getCachedWordLists(localeString, context); + for (final File dict : dicts) { + final String wordListId = getWordListIdFromFileName(dict.getName()); + if (!DictionaryInfoUtils.isMainWordListId(wordListId)) continue; + final Locale locale = LocaleUtils.constructLocaleFromString(localeString); + final AssetFileAddress fileAddress = AssetFileAddress.makeFromFile(dict); + final DictionaryInfo dictionaryInfo = + createDictionaryInfoFromFileAddress(fileAddress); + // Protect against cases of a less-specific dictionary being found, like an + // en dictionary being used for an en_US locale. In this case, the en dictionary + // should be used for en_US but discounted for listing purposes. + if (!dictionaryInfo.mLocale.equals(locale)) continue; + addOrUpdateDictInfo(dictList, dictionaryInfo); + } + } + } + + // Retrieve files from assets + final Resources resources = context.getResources(); + final AssetManager assets = resources.getAssets(); + for (final String localeString : assets.getLocales()) { + final Locale locale = LocaleUtils.constructLocaleFromString(localeString); + final int resourceId = + DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale( + context.getResources(), locale); + if (0 == resourceId) continue; + final AssetFileAddress fileAddress = + BinaryDictionaryGetter.loadFallbackResource(context, resourceId); + final DictionaryInfo dictionaryInfo = createDictionaryInfoFromFileAddress(fileAddress); + // Protect against cases of a less-specific dictionary being found, like an + // en dictionary being used for an en_US locale. In this case, the en dictionary + // should be used for en_US but discounted for listing purposes. + if (!dictionaryInfo.mLocale.equals(locale)) continue; + addOrUpdateDictInfo(dictList, dictionaryInfo); + } + + return dictList; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java b/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java new file mode 100644 index 000000000..ec7eaf4a0 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/FeedbackUtils.java @@ -0,0 +1,37 @@ +/* + * 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.utils; + +import android.content.Context; +import android.content.Intent; + +public class FeedbackUtils { + public static boolean isFeedbackFormSupported() { + return false; + } + + public static void showFeedbackForm(Context context) { + } + + public static int getAboutKeyboardTitleResId() { + return 0; + } + + public static Intent getAboutKeyboardIntent(Context context) { + return null; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java b/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java new file mode 100644 index 000000000..19cd34011 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/InputTypeUtils.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2012 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.utils; + +import android.text.InputType; +import android.view.inputmethod.EditorInfo; + +public final class InputTypeUtils implements InputType { + private static final int WEB_TEXT_PASSWORD_INPUT_TYPE = + TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_PASSWORD; + private static final int WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE = + TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; + private static final int NUMBER_PASSWORD_INPUT_TYPE = + TYPE_CLASS_NUMBER | TYPE_NUMBER_VARIATION_PASSWORD; + private static final int TEXT_PASSWORD_INPUT_TYPE = + TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD; + private static final int TEXT_VISIBLE_PASSWORD_INPUT_TYPE = + TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; + private static final int[] SUPPRESSING_AUTO_SPACES_FIELD_VARIATION = { + InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS, + InputType.TYPE_TEXT_VARIATION_PASSWORD, + InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD, + InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD }; + public static final int IME_ACTION_CUSTOM_LABEL = EditorInfo.IME_MASK_ACTION + 1; + + private InputTypeUtils() { + // This utility class is not publicly instantiable. + } + + private static boolean isWebEditTextInputType(final int inputType) { + return inputType == (TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); + } + + private static boolean isWebPasswordInputType(final int inputType) { + return WEB_TEXT_PASSWORD_INPUT_TYPE != 0 + && inputType == WEB_TEXT_PASSWORD_INPUT_TYPE; + } + + private static boolean isWebEmailAddressInputType(final int inputType) { + return WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE != 0 + && inputType == WEB_TEXT_EMAIL_ADDRESS_INPUT_TYPE; + } + + private static boolean isNumberPasswordInputType(final int inputType) { + return NUMBER_PASSWORD_INPUT_TYPE != 0 + && inputType == NUMBER_PASSWORD_INPUT_TYPE; + } + + private static boolean isTextPasswordInputType(final int inputType) { + return inputType == TEXT_PASSWORD_INPUT_TYPE; + } + + private static boolean isWebEmailAddressVariation(int variation) { + return variation == TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS; + } + + public static boolean isEmailVariation(final int variation) { + return variation == TYPE_TEXT_VARIATION_EMAIL_ADDRESS + || isWebEmailAddressVariation(variation); + } + + public static boolean isWebInputType(final int inputType) { + final int maskedInputType = + inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); + return isWebEditTextInputType(maskedInputType) || isWebPasswordInputType(maskedInputType) + || isWebEmailAddressInputType(maskedInputType); + } + + // Please refer to TextView.isPasswordInputType + public static boolean isPasswordInputType(final int inputType) { + final int maskedInputType = + inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); + return isTextPasswordInputType(maskedInputType) || isWebPasswordInputType(maskedInputType) + || isNumberPasswordInputType(maskedInputType); + } + + // Please refer to TextView.isVisiblePasswordInputType + public static boolean isVisiblePasswordInputType(final int inputType) { + final int maskedInputType = + inputType & (TYPE_MASK_CLASS | TYPE_MASK_VARIATION); + return maskedInputType == TEXT_VISIBLE_PASSWORD_INPUT_TYPE; + } + + public static boolean isAutoSpaceFriendlyType(final int inputType) { + if (TYPE_CLASS_TEXT != (TYPE_MASK_CLASS & inputType)) return false; + final int variation = TYPE_MASK_VARIATION & inputType; + for (final int fieldVariation : SUPPRESSING_AUTO_SPACES_FIELD_VARIATION) { + if (variation == fieldVariation) return false; + } + return true; + } + + public static int getImeOptionsActionIdFromEditorInfo(final EditorInfo editorInfo) { + 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 { + // Note: this is different from editorInfo.actionId, hence "ImeOptionsActionId" + return editorInfo.imeOptions & EditorInfo.IME_MASK_ACTION; + } + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/IntentUtils.java b/java/src/com/android/inputmethod/latin/utils/IntentUtils.java new file mode 100644 index 000000000..ea0168117 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/IntentUtils.java @@ -0,0 +1,45 @@ +/* + * 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.utils; + +import android.content.Intent; +import android.text.TextUtils; + +public final class IntentUtils { + private static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; + // TODO: Can these be constants instead of literal String constants? + private static final String INPUT_METHOD_SUBTYPE_SETTINGS = + "android.settings.INPUT_METHOD_SUBTYPE_SETTINGS"; + + private IntentUtils() { + // This utility class is not publicly instantiable. + } + + public static Intent getInputLanguageSelectionIntent(final String inputMethodId, + final int flagsForSubtypeSettings) { + // Refer to android.provider.Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS + final String action = INPUT_METHOD_SUBTYPE_SETTINGS; + final Intent intent = new Intent(action); + if (!TextUtils.isEmpty(inputMethodId)) { + intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId); + } + if (flagsForSubtypeSettings > 0) { + intent.setFlags(flagsForSubtypeSettings); + } + return intent; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/JniUtils.java b/java/src/com/android/inputmethod/latin/utils/JniUtils.java new file mode 100644 index 000000000..e7fdafaeb --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/JniUtils.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2012 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.utils; + +import android.util.Log; + +import com.android.inputmethod.latin.define.JniLibName; + +public final class JniUtils { + private static final String TAG = JniUtils.class.getSimpleName(); + + static { + try { + System.loadLibrary(JniLibName.JNI_LIB_NAME); + } catch (UnsatisfiedLinkError ule) { + Log.e(TAG, "Could not load native library " + JniLibName.JNI_LIB_NAME, ule); + } + } + + private JniUtils() { + // This utility class is not publicly instantiable. + } + + public static void loadNativeLibrary() { + // Ensures the static initializer is called + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java new file mode 100644 index 000000000..58d062bbd --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.utils; + +import android.content.res.Configuration; +import android.content.res.Resources; +import android.text.TextUtils; + +import java.util.HashMap; +import java.util.Locale; + +/** + * A class to help with handling Locales in string form. + * + * This file has the same meaning and features (and shares all of its code) with + * the one in the dictionary pack. They need to be kept synchronized; for any + * update/bugfix to this file, consider also updating/fixing the version in the + * dictionary pack. + */ +public final class LocaleUtils { + private static final HashMap EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap(); + private static final String LOCALE_AND_TIME_STR_SEPARATER = ","; + + private LocaleUtils() { + // Intentional empty constructor for utility class. + } + + // Locale match level constants. + // A higher level of match is guaranteed to have a higher numerical value. + // Some room is left within constants to add match cases that may arise necessary + // in the future, for example differentiating between the case where the countries + // are both present and different, and the case where one of the locales does not + // specify the countries. This difference is not needed now. + + // Nothing matches. + public static final int LOCALE_NO_MATCH = 0; + // The languages matches, but the country are different. Or, the reference locale requires a + // country and the tested locale does not have one. + public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3; + // The languages and country match, but the variants are different. Or, the reference locale + // requires a variant and the tested locale does not have one. + public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6; + // The required locale is null or empty so it will accept anything, and the tested locale + // is non-null and non-empty. + public static final int LOCALE_ANY_MATCH = 10; + // The language matches, and the tested locale specifies a country but the reference locale + // does not require one. + public static final int LOCALE_LANGUAGE_MATCH = 15; + // The language and the country match, and the tested locale specifies a variant but the + // reference locale does not require one. + public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20; + // The compared locales are fully identical. This is the best match level. + public static final int LOCALE_FULL_MATCH = 30; + + // The level at which a match is "normally" considered a locale match with standard algorithms. + // Don't use this directly, use #isMatch to test. + private static final int LOCALE_MATCH = LOCALE_ANY_MATCH; + + // Make this match the maximum match level. If this evolves to have more than 2 digits + // when written in base 10, also adjust the getMatchLevelSortedString method. + private static final int MATCH_LEVEL_MAX = 30; + + /** + * Return how well a tested locale matches a reference locale. + * + * This will check the tested locale against the reference locale and return a measure of how + * a well it matches the reference. The general idea is that the tested locale has to match + * every specified part of the required locale. A full match occur when they are equal, a + * partial match when the tested locale agrees with the reference locale but is more specific, + * and a difference when the tested locale does not comply with all requirements from the + * reference locale. + * In more detail, if the reference locale specifies at least a language and the testedLocale + * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the + * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH + * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and + * tested locale agree on the language, but not on the country, + * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country, + * and LOCALE_LANGUAGE_MATCH otherwise. + * If they agree on both the language and the country, but not on the variant, + * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale + * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches, + * LOCALE_FULL_MATCH is returned. + * Examples: + * en <=> en_US => LOCALE_LANGUAGE_MATCH + * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER + * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER + * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH + * sp_US <=> en_US => LOCALE_NO_MATCH + * de <=> de => LOCALE_FULL_MATCH + * en_US <=> en_US => LOCALE_FULL_MATCH + * "" <=> en_US => LOCALE_ANY_MATCH + * + * @param referenceLocale the reference locale to test against. + * @param testedLocale the locale to test. + * @return a constant that measures how well the tested locale matches the reference locale. + */ + public static int getMatchLevel(String referenceLocale, String testedLocale) { + if (TextUtils.isEmpty(referenceLocale)) { + return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH; + } + if (null == testedLocale) return LOCALE_NO_MATCH; + String[] referenceParams = referenceLocale.split("_", 3); + String[] testedParams = testedLocale.split("_", 3); + // By spec of String#split, [0] cannot be null and length cannot be 0. + if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH; + switch (referenceParams.length) { + case 1: + return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH; + case 2: + if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (!referenceParams[1].equals(testedParams[1])) + return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH; + return LOCALE_FULL_MATCH; + case 3: + if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (!referenceParams[1].equals(testedParams[1])) + return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; + if (!referenceParams[2].equals(testedParams[2])) + return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; + return LOCALE_FULL_MATCH; + } + // It should be impossible to come here + return LOCALE_NO_MATCH; + } + + /** + * Return a string that represents this match level, with better matches first. + * + * The strings are sorted in lexicographic order: a better match will always be less than + * a worse match when compared together. + */ + public static String getMatchLevelSortedString(int matchLevel) { + // This works because the match levels are 0~99 (actually 0~30) + // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel + return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel); + } + + /** + * Find out whether a match level should be considered a match. + * + * This method takes a match level as returned by the #getMatchLevel method, and returns whether + * it should be considered a match in the usual sense with standard Locale functions. + * + * @param level the match level, as returned by getMatchLevel. + * @return whether this is a match or not. + */ + public static boolean isMatch(int level) { + return LOCALE_MATCH <= level; + } + + static final Object sLockForRunInLocale = new Object(); + + // TODO: Make this an external class + public abstract static class RunInLocale { + protected abstract T job(Resources res); + + /** + * Execute {@link #job(Resources)} method in specified system locale exclusively. + * + * @param res the resources to use. Pass current resources. + * @param newLocale the locale to change to + * @return the value returned from {@link #job(Resources)}. + */ + public T runInLocale(final Resources res, final Locale newLocale) { + synchronized (sLockForRunInLocale) { + final Configuration conf = res.getConfiguration(); + final Locale oldLocale = conf.locale; + final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale)); + try { + if (needsChange) { + conf.locale = newLocale; + res.updateConfiguration(conf, null); + } + return job(res); + } finally { + if (needsChange) { + conf.locale = oldLocale; + res.updateConfiguration(conf, null); + } + } + } + } + } + + private static final HashMap sLocaleCache = CollectionUtils.newHashMap(); + + /** + * Creates a locale from a string specification. + */ + public static Locale constructLocaleFromString(final String localeStr) { + if (localeStr == null) + return null; + synchronized (sLocaleCache) { + if (sLocaleCache.containsKey(localeStr)) + return sLocaleCache.get(localeStr); + Locale retval = null; + String[] localeParams = localeStr.split("_", 3); + if (localeParams.length == 1) { + retval = new Locale(localeParams[0]); + } else if (localeParams.length == 2) { + retval = new Locale(localeParams[0], localeParams[1]); + } else if (localeParams.length == 3) { + retval = new Locale(localeParams[0], localeParams[1], localeParams[2]); + } + if (retval != null) { + sLocaleCache.put(localeStr, retval); + } + return retval; + } + } + + public static HashMap localeAndTimeStrToHashMap(String str) { + if (TextUtils.isEmpty(str)) { + return EMPTY_LT_HASH_MAP; + } + final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER); + final int N = ss.length; + if (N < 2 || N % 2 != 0) { + return EMPTY_LT_HASH_MAP; + } + final HashMap retval = CollectionUtils.newHashMap(); + for (int i = 0; i < N / 2; ++i) { + final String localeStr = ss[i * 2]; + final long time = Long.valueOf(ss[i * 2 + 1]); + retval.put(localeStr, time); + } + return retval; + } + + public static String localeAndTimeHashMapToStr(HashMap map) { + if (map == null || map.isEmpty()) { + return ""; + } + final StringBuilder builder = new StringBuilder(); + for (String localeStr : map.keySet()) { + if (builder.length() > 0) { + builder.append(LOCALE_AND_TIME_STR_SEPARATER); + } + final Long time = map.get(localeStr); + builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER); + builder.append(String.valueOf(time)); + } + return builder.toString(); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/LogUtils.java b/java/src/com/android/inputmethod/latin/utils/LogUtils.java new file mode 100644 index 000000000..a0d2e0495 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/LogUtils.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin.utils; + +import android.util.Log; + +import com.android.inputmethod.latin.LatinImeLogger; + +/** + * A class for logging and debugging utility methods. + */ +public final class LogUtils { + private final static String TAG = LogUtils.class.getSimpleName(); + private final static boolean sDBG = LatinImeLogger.sDBG; + + /** + * Calls .toString() on its non-null argument or returns "null" + * @param o the object to convert to a string + * @return the result of .toString() or null + */ + public static String s(final Object o) { + return null == o ? "null" : o.toString(); + } + + /** + * Get the string representation of the current stack trace, for debugging purposes. + * @return a readable, carriage-return-separated string for the current stack trace. + */ + public static String getStackTrace() { + return getStackTrace(Integer.MAX_VALUE - 1); + } + + /** + * Get the string representation of the current stack trace, for debugging purposes. + * @param limit the maximum number of stack frames to be returned. + * @return a readable, carriage-return-separated string for the current stack trace. + */ + public static String getStackTrace(final int limit) { + final StringBuilder sb = new StringBuilder(); + try { + throw new RuntimeException(); + } catch (final RuntimeException e) { + final StackTraceElement[] frames = e.getStackTrace(); + // Start at 1 because the first frame is here and we don't care about it + for (int j = 1; j < frames.length && j < limit + 1; ++j) { + sb.append(frames[j].toString() + "\n"); + } + } + return sb.toString(); + } + + /** + * Get the stack trace contained in an exception as a human-readable string. + * @param e the exception + * @return the human-readable stack trace + */ + public static String getStackTrace(final Exception e) { + final StringBuilder sb = new StringBuilder(); + final StackTraceElement[] frames = e.getStackTrace(); + for (int j = 0; j < frames.length; ++j) { + sb.append(frames[j].toString() + "\n"); + } + return sb.toString(); + } + + /** + * Helper log method to ease null-checks and adding spaces. + * + * This sends all arguments to the log, separated by spaces. Any null argument is converted + * to the "null" string. It uses a very visible tag and log level for debugging purposes. + * + * @param args the stuff to send to the log + */ + public static void l(final Object... args) { + if (!sDBG) return; + final StringBuilder sb = new StringBuilder(); + for (final Object o : args) { + sb.append(s(o).toString()); + sb.append(" "); + } + Log.e(TAG, sb.toString()); + } + + /** + * Helper log method to put stuff in red. + * + * This does the same as #l but prints in red + * + * @param args the stuff to send to the log + */ + public static void r(final Object... args) { + if (!sDBG) return; + final StringBuilder sb = new StringBuilder("\u001B[31m"); + for (final Object o : args) { + sb.append(s(o).toString()); + sb.append(" "); + } + sb.append("\u001B[0m"); + Log.e(TAG, sb.toString()); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java b/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java new file mode 100644 index 000000000..9ad319da6 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/MetadataFileUriGetter.java @@ -0,0 +1,38 @@ +/* + * 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.utils; + +import com.android.inputmethod.latin.R; + +import android.content.Context; + +/** + * Helper class to get the metadata URI and the additional ID. + */ +public class MetadataFileUriGetter { + private MetadataFileUriGetter() { + // This helper class is not instantiable. + } + + public static String getMetadataUri(final Context context) { + return context.getString(R.string.dictionary_pack_metadata_uri); + } + + public static String getMetadataAdditionalId(final Context context) { + return ""; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java b/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java new file mode 100644 index 000000000..1fc7eccc6 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/PositionalInfoForUserDictPendingAddition.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2012 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.utils; + +import android.view.inputmethod.EditorInfo; + +import com.android.inputmethod.latin.RichInputConnection; + +import java.util.Locale; + +/** + * Holder class for data about a word already committed but that may still be edited. + * + * When the user chooses to add a word to the user dictionary by pressing the appropriate + * suggestion, a dialog is presented to give a chance to edit the word before it is actually + * registered as a user dictionary word. If the word is actually modified, the IME needs to + * go back and replace the word that was committed with the amended version. + * The word we need to replace with will only be known after it's actually committed, so + * the IME needs to take a note of what it has to replace and where it is. + * This class encapsulates this data. + */ +public final class PositionalInfoForUserDictPendingAddition { + final private String mOriginalWord; + final private int mCursorPos; // Position of the cursor after the word + final private EditorInfo mEditorInfo; // On what binding this has been added + final private int mCapitalizedMode; + private String mActualWordBeingAdded; + + public PositionalInfoForUserDictPendingAddition(final String word, final int cursorPos, + final EditorInfo editorInfo, final int capitalizedMode) { + mOriginalWord = word; + mCursorPos = cursorPos; + mEditorInfo = editorInfo; + mCapitalizedMode = capitalizedMode; + } + + public void setActualWordBeingAdded(final String actualWordBeingAdded) { + mActualWordBeingAdded = actualWordBeingAdded; + } + + /** + * Try to replace the string at the remembered position with the actual word being added. + * + * After the user validated the word being added, the IME has to replace the old version + * (which has been committed in the text view) with the amended version if it's different. + * This method tries to do that, but may fail because the IME is not yet ready to do so - + * for example, it is still waiting for the new string, or it is waiting to return to the text + * view in which the amendment should be made. In these cases, we should keep the data + * and wait until all conditions are met. + * This method returns true if the replacement has been successfully made and this data + * can be forgotten; it returns false if the replacement can't be made yet and we need to + * keep this until a later time. + * The IME knows about the actual word being added through a callback called by the + * user dictionary facility of the device. When this callback comes, the keyboard may still + * be connected to the edition dialog, or it may have already returned to the original text + * field. Replacement has to work in both cases. + * Accordingly, this method is called at two different points in time : upon getting the + * event that a new word was added to the user dictionary, and upon starting up in a + * new text field. + * @param connection The RichInputConnection through which to contact the editor. + * @param editorInfo Information pertaining to the editor we are currently in. + * @param currentCursorPosition The current cursor position, for checking purposes. + * @param locale The locale for changing case, if necessary + * @return true if the edit has been successfully made, false if we need to try again later + */ + public boolean tryReplaceWithActualWord(final RichInputConnection connection, + final EditorInfo editorInfo, final int currentCursorPosition, final Locale locale) { + // If we still don't know the actual word being added, we need to try again later. + if (null == mActualWordBeingAdded) return false; + // The entered text and the registered text were the same anyway : we can + // return success right away even if focus has not returned yet to the text field we + // want to amend. + if (mActualWordBeingAdded.equals(mOriginalWord)) return true; + // Not the same text field : we need to try again later. This happens when the addition + // is reported by the user dictionary provider before the focus has moved back to the + // original text view, so the IME is still in the text view of the dialog and has no way to + // edit the original text view at this time. + if (!mEditorInfo.packageName.equals(editorInfo.packageName) + || mEditorInfo.fieldId != editorInfo.fieldId) { + return false; + } + // Same text field, but not the same cursor position : we give up, so we return success + // so that it won't be tried again + if (currentCursorPosition != mCursorPos) return true; + // We have made all the checks : do the replacement and report success + // If this was auto-capitalized, we need to restore the case before committing + final String wordWithCaseFixed = CapsModeUtils.applyAutoCapsMode(mActualWordBeingAdded, + mCapitalizedMode, locale); + connection.setComposingRegion(currentCursorPosition - mOriginalWord.length(), + currentCursorPosition); + connection.commitText(wordWithCaseFixed, wordWithCaseFixed.length()); + return true; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java new file mode 100644 index 000000000..0f5cd80db --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java @@ -0,0 +1,190 @@ +/* + * 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.utils; + +import java.util.Locale; + +/** + * The status of the current recapitalize process. + */ +public class RecapitalizeStatus { + public static final int NOT_A_RECAPITALIZE_MODE = -1; + public static final int CAPS_MODE_ORIGINAL_MIXED_CASE = 0; + public static final int CAPS_MODE_ALL_LOWER = 1; + public static final int CAPS_MODE_FIRST_WORD_UPPER = 2; + public static final int CAPS_MODE_ALL_UPPER = 3; + // When adding a new mode, don't forget to update the CAPS_MODE_LAST constant. + public static final int CAPS_MODE_LAST = CAPS_MODE_ALL_UPPER; + + private static final int[] ROTATION_STYLE = { + CAPS_MODE_ORIGINAL_MIXED_CASE, + CAPS_MODE_ALL_LOWER, + CAPS_MODE_FIRST_WORD_UPPER, + CAPS_MODE_ALL_UPPER + }; + + private static final int getStringMode(final String string, final String separators) { + if (StringUtils.isIdenticalAfterUpcase(string)) { + return CAPS_MODE_ALL_UPPER; + } else if (StringUtils.isIdenticalAfterDowncase(string)) { + return CAPS_MODE_ALL_LOWER; + } else if (StringUtils.isIdenticalAfterCapitalizeEachWord(string, separators)) { + return CAPS_MODE_FIRST_WORD_UPPER; + } else { + return CAPS_MODE_ORIGINAL_MIXED_CASE; + } + } + + /** + * We store the location of the cursor and the string that was there before the recapitalize + * action was done, and the location of the cursor and the string that was there after. + */ + private int mCursorStartBefore; + private String mStringBefore; + private int mCursorStartAfter; + private int mCursorEndAfter; + private int mRotationStyleCurrentIndex; + private boolean mSkipOriginalMixedCaseMode; + private Locale mLocale; + private String mSeparators; + private String mStringAfter; + private boolean mIsActive; + + public RecapitalizeStatus() { + // By default, initialize with dummy values that won't match any real recapitalize. + initialize(-1, -1, "", Locale.getDefault(), ""); + deactivate(); + } + + public void initialize(final int cursorStart, final int cursorEnd, final String string, + final Locale locale, final String separators) { + mCursorStartBefore = cursorStart; + mStringBefore = string; + mCursorStartAfter = cursorStart; + mCursorEndAfter = cursorEnd; + mStringAfter = string; + final int initialMode = getStringMode(mStringBefore, separators); + mLocale = locale; + mSeparators = separators; + if (CAPS_MODE_ORIGINAL_MIXED_CASE == initialMode) { + mRotationStyleCurrentIndex = 0; + mSkipOriginalMixedCaseMode = false; + } else { + // Find the current mode in the array. + int currentMode; + for (currentMode = ROTATION_STYLE.length - 1; currentMode > 0; --currentMode) { + if (ROTATION_STYLE[currentMode] == initialMode) { + break; + } + } + mRotationStyleCurrentIndex = currentMode; + mSkipOriginalMixedCaseMode = true; + } + mIsActive = true; + } + + public void deactivate() { + mIsActive = false; + } + + public boolean isActive() { + return mIsActive; + } + + public boolean isSetAt(final int cursorStart, final int cursorEnd) { + return cursorStart == mCursorStartAfter && cursorEnd == mCursorEndAfter; + } + + /** + * Rotate through the different possible capitalization modes. + */ + public void rotate() { + final String oldResult = mStringAfter; + int count = 0; // Protection against infinite loop. + do { + mRotationStyleCurrentIndex = (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; + if (CAPS_MODE_ORIGINAL_MIXED_CASE == ROTATION_STYLE[mRotationStyleCurrentIndex] + && mSkipOriginalMixedCaseMode) { + mRotationStyleCurrentIndex = + (mRotationStyleCurrentIndex + 1) % ROTATION_STYLE.length; + } + ++count; + switch (ROTATION_STYLE[mRotationStyleCurrentIndex]) { + case CAPS_MODE_ORIGINAL_MIXED_CASE: + mStringAfter = mStringBefore; + break; + case CAPS_MODE_ALL_LOWER: + mStringAfter = mStringBefore.toLowerCase(mLocale); + break; + case CAPS_MODE_FIRST_WORD_UPPER: + mStringAfter = StringUtils.capitalizeEachWord(mStringBefore, mSeparators, + mLocale); + break; + case CAPS_MODE_ALL_UPPER: + mStringAfter = mStringBefore.toUpperCase(mLocale); + break; + default: + mStringAfter = mStringBefore; + } + } while (mStringAfter.equals(oldResult) && count < ROTATION_STYLE.length + 1); + mCursorEndAfter = mCursorStartAfter + mStringAfter.length(); + } + + /** + * Remove leading/trailing whitespace from the considered string. + */ + public void trim() { + final int len = mStringBefore.length(); + int nonWhitespaceStart = 0; + for (; nonWhitespaceStart < len; + nonWhitespaceStart = mStringBefore.offsetByCodePoints(nonWhitespaceStart, 1)) { + final int codePoint = mStringBefore.codePointAt(nonWhitespaceStart); + if (!Character.isWhitespace(codePoint)) break; + } + int nonWhitespaceEnd = len; + for (; nonWhitespaceEnd > 0; + nonWhitespaceEnd = mStringBefore.offsetByCodePoints(nonWhitespaceEnd, -1)) { + final int codePoint = mStringBefore.codePointBefore(nonWhitespaceEnd); + if (!Character.isWhitespace(codePoint)) break; + } + // If nonWhitespaceStart >= nonWhitespaceEnd, that means the selection contained only + // whitespace, so we leave it as is. + if ((0 != nonWhitespaceStart || len != nonWhitespaceEnd) + && nonWhitespaceStart < nonWhitespaceEnd) { + mCursorEndAfter = mCursorStartBefore + nonWhitespaceEnd; + mCursorStartBefore = mCursorStartAfter = mCursorStartBefore + nonWhitespaceStart; + mStringAfter = mStringBefore = + mStringBefore.substring(nonWhitespaceStart, nonWhitespaceEnd); + } + } + + public String getRecapitalizedString() { + return mStringAfter; + } + + public int getNewCursorStart() { + return mCursorStartAfter; + } + + public int getNewCursorEnd() { + return mCursorEndAfter; + } + + public int getCurrentMode() { + return ROTATION_STYLE[mRotationStyleCurrentIndex]; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java new file mode 100644 index 000000000..4c7739a7a --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/ResizableIntArray.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2012 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.utils; + +import java.util.Arrays; + +// TODO: This class is not thread-safe. +public final class ResizableIntArray { + private int[] mArray; + private int mLength; + + public ResizableIntArray(final int capacity) { + reset(capacity); + } + + public int get(final int index) { + if (index < mLength) { + return mArray[index]; + } + throw new ArrayIndexOutOfBoundsException("length=" + mLength + "; index=" + index); + } + + public void add(final int index, final int val) { + if (index < mLength) { + mArray[index] = val; + } else { + mLength = index; + add(val); + } + } + + public void add(final int val) { + final int currentLength = mLength; + ensureCapacity(currentLength + 1); + mArray[currentLength] = val; + mLength = currentLength + 1; + } + + /** + * Calculate the new capacity of {@code mArray}. + * @param minimumCapacity the minimum capacity that the {@code mArray} should have. + * @return the new capacity that the {@code mArray} should have. Returns zero when there is no + * need to expand {@code mArray}. + */ + private int calculateCapacity(final int minimumCapacity) { + final int currentCapcity = mArray.length; + if (currentCapcity < minimumCapacity) { + final int nextCapacity = currentCapcity * 2; + // The following is the same as return Math.max(minimumCapacity, nextCapacity); + return minimumCapacity > nextCapacity ? minimumCapacity : nextCapacity; + } + return 0; + } + + private void ensureCapacity(final int minimumCapacity) { + final int newCapacity = calculateCapacity(minimumCapacity); + if (newCapacity > 0) { + // TODO: Implement primitive array pool. + mArray = Arrays.copyOf(mArray, newCapacity); + } + } + + public int getLength() { + return mLength; + } + + public void setLength(final int newLength) { + ensureCapacity(newLength); + mLength = newLength; + } + + public void reset(final int capacity) { + // TODO: Implement primitive array pool. + mArray = new int[capacity]; + mLength = 0; + } + + public int[] getPrimitiveArray() { + return mArray; + } + + public void set(final ResizableIntArray ip) { + // TODO: Implement primitive array pool. + mArray = ip.mArray; + mLength = ip.mLength; + } + + public void copy(final ResizableIntArray ip) { + final int newCapacity = calculateCapacity(ip.mLength); + if (newCapacity > 0) { + // TODO: Implement primitive array pool. + mArray = new int[newCapacity]; + } + System.arraycopy(ip.mArray, 0, mArray, 0, ip.mLength); + mLength = ip.mLength; + } + + public void append(final ResizableIntArray src, final int startPos, final int length) { + if (length == 0) { + return; + } + final int currentLength = mLength; + final int newLength = currentLength + length; + ensureCapacity(newLength); + System.arraycopy(src.mArray, startPos, mArray, currentLength, length); + mLength = newLength; + } + + public void fill(final int value, final int startPos, final int length) { + if (startPos < 0 || length < 0) { + throw new IllegalArgumentException("startPos=" + startPos + "; length=" + length); + } + final int endPos = startPos + length; + ensureCapacity(endPos); + Arrays.fill(mArray, startPos, endPos, value); + if (mLength < endPos) { + mLength = endPos; + } + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mLength; i++) { + if (i != 0) { + sb.append(","); + } + sb.append(mArray[i]); + } + return "[" + sb + "]"; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java new file mode 100644 index 000000000..ffec57548 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2012 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.utils; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.os.Build; +import android.text.TextUtils; +import android.util.Log; +import android.util.TypedValue; + +import com.android.inputmethod.annotations.UsedForTesting; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.regex.PatternSyntaxException; + +public final class ResourceUtils { + private static final String TAG = ResourceUtils.class.getSimpleName(); + + public static final float UNDEFINED_RATIO = -1.0f; + public static final int UNDEFINED_DIMENSION = -1; + + private ResourceUtils() { + // This utility class is not publicly instantiable. + } + + private static final HashMap sDeviceOverrideValueMap = + CollectionUtils.newHashMap(); + + private static final String[] BUILD_KEYS_AND_VALUES = { + "HARDWARE", Build.HARDWARE, + "MODEL", Build.MODEL, + "BRAND", Build.BRAND, + "MANUFACTURER", Build.MANUFACTURER + }; + private static final HashMap sBuildKeyValues; + private static final String sBuildKeyValuesDebugString; + + static { + sBuildKeyValues = CollectionUtils.newHashMap(); + final ArrayList keyValuePairs = CollectionUtils.newArrayList(); + final int keyCount = BUILD_KEYS_AND_VALUES.length / 2; + for (int i = 0; i < keyCount; i++) { + final int index = i * 2; + final String key = BUILD_KEYS_AND_VALUES[index]; + final String value = BUILD_KEYS_AND_VALUES[index + 1]; + sBuildKeyValues.put(key, value); + keyValuePairs.add(key + '=' + value); + } + sBuildKeyValuesDebugString = "[" + TextUtils.join(" ", keyValuePairs) + "]"; + } + + public static String getDeviceOverrideValue(final Resources res, final int overrideResId) { + final int orientation = res.getConfiguration().orientation; + final String key = overrideResId + "-" + orientation; + if (sDeviceOverrideValueMap.containsKey(key)) { + return sDeviceOverrideValueMap.get(key); + } + + final String[] overrideArray = res.getStringArray(overrideResId); + final String overrideValue = findConstantForKeyValuePairs(sBuildKeyValues, overrideArray); + // The overrideValue might be an empty string. + if (overrideValue != null) { + Log.i(TAG, "Find override value:" + + " resource="+ res.getResourceEntryName(overrideResId) + + " build=" + sBuildKeyValuesDebugString + + " override=" + overrideValue); + sDeviceOverrideValueMap.put(key, overrideValue); + return overrideValue; + } + + String defaultValue = null; + try { + defaultValue = findDefaultConstant(overrideArray); + // The defaultValue might be an empty string. + if (defaultValue == null) { + Log.w(TAG, "Couldn't find override value nor default value:" + + " resource="+ res.getResourceEntryName(overrideResId) + + " build=" + sBuildKeyValuesDebugString); + } else { + Log.i(TAG, "Found default value:" + + " resource="+ res.getResourceEntryName(overrideResId) + + " build=" + sBuildKeyValuesDebugString + + " default=" + defaultValue); + } + } catch (final DeviceOverridePatternSyntaxError e) { + Log.w(TAG, "Syntax error, ignored", e); + } + sDeviceOverrideValueMap.put(key, defaultValue); + return defaultValue; + } + + @SuppressWarnings("serial") + static class DeviceOverridePatternSyntaxError extends Exception { + public DeviceOverridePatternSyntaxError(final String message, final String expression) { + this(message, expression, null); + } + + public DeviceOverridePatternSyntaxError(final String message, final String expression, + final Throwable throwable) { + super(message + ": " + expression, throwable); + } + } + + /** + * Find the condition that fulfills specified key value pairs from an array of + * "condition,constant", and return the corresponding string constant. A condition is + * "pattern1[:pattern2...] (or an empty string for the default). A pattern is + * "key=regexp_value" string. The condition matches only if all patterns of the condition + * are true for the specified key value pairs. + * + * For example, "condition,constant" has the following format. + * (See {@link ResourceUtilsTests#testFindConstantForKeyValuePairsRegexp()}) + * - HARDWARE=mako,constantForNexus4 + * - MODEL=Nexus 4:MANUFACTURER=LGE,constantForNexus4 + * - ,defaultConstant + * + * @param keyValuePairs attributes to be used to look for a matched condition. + * @param conditionConstantArray an array of "condition,constant" elements to be searched. + * @return the constant part of the matched "condition,constant" element. Returns null if no + * condition matches. + */ + @UsedForTesting + static String findConstantForKeyValuePairs(final HashMap keyValuePairs, + final String[] conditionConstantArray) { + if (conditionConstantArray == null || keyValuePairs == null) { + return null; + } + String foundValue = null; + for (final String conditionConstant : conditionConstantArray) { + final int posComma = conditionConstant.indexOf(','); + if (posComma < 0) { + Log.w(TAG, "Array element has no comma: " + conditionConstant); + continue; + } + final String condition = conditionConstant.substring(0, posComma); + if (condition.isEmpty()) { + // Default condition. The default condition should be searched by + // {@link #findConstantForDefault(String[])}. + continue; + } + try { + if (fulfillsCondition(keyValuePairs, condition)) { + // Take first match + if (foundValue == null) { + foundValue = conditionConstant.substring(posComma + 1); + } + // And continue walking through all conditions. + } + } catch (final DeviceOverridePatternSyntaxError e) { + Log.w(TAG, "Syntax error, ignored", e); + } + } + return foundValue; + } + + private static boolean fulfillsCondition(final HashMap keyValuePairs, + final String condition) throws DeviceOverridePatternSyntaxError { + final String[] patterns = condition.split(":"); + // Check all patterns in a condition are true + boolean matchedAll = true; + for (final String pattern : patterns) { + final int posEqual = pattern.indexOf('='); + if (posEqual < 0) { + throw new DeviceOverridePatternSyntaxError("Pattern has no '='", condition); + } + final String key = pattern.substring(0, posEqual); + final String value = keyValuePairs.get(key); + if (value == null) { + throw new DeviceOverridePatternSyntaxError("Unknown key", condition); + } + final String patternRegexpValue = pattern.substring(posEqual + 1); + try { + if (!value.matches(patternRegexpValue)) { + matchedAll = false; + // And continue walking through all patterns. + } + } catch (final PatternSyntaxException e) { + throw new DeviceOverridePatternSyntaxError("Syntax error", condition, e); + } + } + return matchedAll; + } + + @UsedForTesting + static String findDefaultConstant(final String[] conditionConstantArray) + throws DeviceOverridePatternSyntaxError { + if (conditionConstantArray == null) { + return null; + } + for (final String condition : conditionConstantArray) { + final int posComma = condition.indexOf(','); + if (posComma < 0) { + throw new DeviceOverridePatternSyntaxError("Array element has no comma", condition); + } + if (posComma == 0) { // condition is empty. + return condition.substring(posComma + 1); + } + } + return null; + } + + public static boolean isValidFraction(final float fraction) { + return fraction >= 0.0f; + } + + // {@link Resources#getDimensionPixelSize(int)} returns at least one pixel size. + public static boolean isValidDimensionPixelSize(final int dimension) { + return dimension > 0; + } + + // {@link Resources#getDimensionPixelOffset(int)} may return zero pixel offset. + public static boolean isValidDimensionPixelOffset(final int dimension) { + return dimension >= 0; + } + + public static float getFraction(final TypedArray a, final int index, final float defValue) { + final TypedValue value = a.peekValue(index); + if (value == null || !isFractionValue(value)) { + return defValue; + } + return a.getFraction(index, 1, 1, defValue); + } + + public static float getFraction(final TypedArray a, final int index) { + return getFraction(a, index, UNDEFINED_RATIO); + } + + public static int getDimensionPixelSize(final TypedArray a, final int index) { + final TypedValue value = a.peekValue(index); + if (value == null || !isDimensionValue(value)) { + return ResourceUtils.UNDEFINED_DIMENSION; + } + return a.getDimensionPixelSize(index, ResourceUtils.UNDEFINED_DIMENSION); + } + + public static float getDimensionOrFraction(final TypedArray a, final int index, final int base, + final float defValue) { + final TypedValue value = a.peekValue(index); + if (value == null) { + return defValue; + } + if (isFractionValue(value)) { + return a.getFraction(index, base, base, defValue); + } else if (isDimensionValue(value)) { + return a.getDimension(index, defValue); + } + return defValue; + } + + public static int getEnumValue(final TypedArray a, final int index, final int defValue) { + final TypedValue value = a.peekValue(index); + if (value == null) { + return defValue; + } + if (isIntegerValue(value)) { + return a.getInt(index, defValue); + } + return defValue; + } + + public static boolean isFractionValue(final TypedValue v) { + return v.type == TypedValue.TYPE_FRACTION; + } + + public static boolean isDimensionValue(final TypedValue v) { + return v.type == TypedValue.TYPE_DIMENSION; + } + + public static boolean isIntegerValue(final TypedValue v) { + return v.type >= TypedValue.TYPE_FIRST_INT && v.type <= TypedValue.TYPE_LAST_INT; + } + + public static boolean isStringValue(final TypedValue v) { + return v.type == TypedValue.TYPE_STRING; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java new file mode 100644 index 000000000..44e5d17b4 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/StaticInnerHandlerWrapper.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.utils; + +import android.os.Handler; +import android.os.Looper; + +import java.lang.ref.WeakReference; + +public class StaticInnerHandlerWrapper extends Handler { + private final WeakReference mOuterInstanceRef; + + public StaticInnerHandlerWrapper(final T outerInstance) { + this(outerInstance, Looper.myLooper()); + } + + public StaticInnerHandlerWrapper(final T outerInstance, final Looper looper) { + super(looper); + if (outerInstance == null) { + throw new NullPointerException("outerInstance is null"); + } + mOuterInstanceRef = new WeakReference(outerInstance); + } + + public T getOuterInstance() { + return mOuterInstanceRef.get(); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java new file mode 100644 index 000000000..7406d855a --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2012 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.utils; + +import android.text.TextUtils; + +import com.android.inputmethod.latin.Constants; + +import java.util.ArrayList; +import java.util.Locale; + +public final class StringUtils { + public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case + public static final int CAPITALIZE_FIRST = 1; // First only + public static final int CAPITALIZE_ALL = 2; // All caps + + private StringUtils() { + // This utility class is not publicly instantiable. + } + + public static int codePointCount(final String text) { + if (TextUtils.isEmpty(text)) return 0; + return text.codePointCount(0, text.length()); + } + + public static boolean containsInArray(final String text, final String[] array) { + for (final String element : array) { + if (text.equals(element)) return true; + } + return false; + } + + /** + * Comma-Splittable Text is similar to Comma-Separated Values (CSV) but has much simpler syntax. + * Unlike CSV, Comma-Splittable Text has no escaping mechanism, so that the text can't contain + * a comma character in it. + */ + private static final String SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT = ","; + + public static boolean containsInCommaSplittableText(final String text, + final String extraValues) { + if (TextUtils.isEmpty(extraValues)) { + return false; + } + return containsInArray(text, extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT)); + } + + public static String appendToCommaSplittableTextIfNotExists(final String text, + final String extraValues) { + if (TextUtils.isEmpty(extraValues)) { + return text; + } + if (containsInCommaSplittableText(text, extraValues)) { + return extraValues; + } + return extraValues + SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT + text; + } + + public static String removeFromCommaSplittableTextIfExists(final String text, + final String extraValues) { + if (TextUtils.isEmpty(extraValues)) { + return ""; + } + final String[] elements = extraValues.split(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT); + if (!containsInArray(text, elements)) { + return extraValues; + } + final ArrayList result = CollectionUtils.newArrayList(elements.length - 1); + for (final String element : elements) { + if (!text.equals(element)) { + result.add(element); + } + } + return TextUtils.join(SEPARATOR_FOR_COMMA_SPLITTABLE_TEXT, result); + } + + /** + * Remove duplicates from an array of strings. + * + * This method will always keep the first occurrence of all strings at their position + * in the array, removing the subsequent ones. + */ + public static void removeDupes(final ArrayList suggestions) { + if (suggestions.size() < 2) return; + int i = 1; + // Don't cache suggestions.size(), since we may be removing items + while (i < suggestions.size()) { + final String cur = suggestions.get(i); + // Compare each suggestion with each previous suggestion + for (int j = 0; j < i; j++) { + final String previous = suggestions.get(j); + if (TextUtils.equals(cur, previous)) { + suggestions.remove(i); + i--; + break; + } + } + i++; + } + } + + public static String capitalizeFirstCodePoint(final String s, final Locale locale) { + if (s.length() <= 1) { + return s.toUpperCase(locale); + } + // Please refer to the comment below in + // {@link #capitalizeFirstAndDowncaseRest(String,Locale)} as this has the same shortcomings + final int cutoff = s.offsetByCodePoints(0, 1); + return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff); + } + + public static String capitalizeFirstAndDowncaseRest(final String s, final Locale locale) { + if (s.length() <= 1) { + return s.toUpperCase(locale); + } + // TODO: fix the bugs below + // - This does not work for Greek, because it returns upper case instead of title case. + // - It does not work for Serbian, because it fails to account for the "lj" character, + // which should be "Lj" in title case and "LJ" in upper case. + // - It does not work for Dutch, because it fails to account for the "ij" digraph when it's + // written as two separate code points. They are two different characters but both should + // be capitalized as "IJ" as if they were a single letter in most words (not all). If the + // unicode char for the ligature is used however, it works. + final int cutoff = s.offsetByCodePoints(0, 1); + return s.substring(0, cutoff).toUpperCase(locale) + s.substring(cutoff).toLowerCase(locale); + } + + private static final int[] EMPTY_CODEPOINTS = {}; + + public static int[] toCodePointArray(final String string) { + final int length = string.length(); + if (length <= 0) { + return EMPTY_CODEPOINTS; + } + final int[] codePoints = new int[string.codePointCount(0, length)]; + int destIndex = 0; + for (int index = 0; index < length; index = string.offsetByCodePoints(index, 1)) { + codePoints[destIndex] = string.codePointAt(index); + destIndex++; + } + return codePoints; + } + + // This method assumes the text is not null. For the empty string, it returns CAPITALIZE_NONE. + public static int getCapitalizationType(final String text) { + // If the first char is not uppercase, then the word is either all lower case or + // camel case, and in either case we return CAPITALIZE_NONE. + final int len = text.length(); + int index = 0; + for (; index < len; index = text.offsetByCodePoints(index, 1)) { + if (Character.isLetter(text.codePointAt(index))) { + break; + } + } + if (index == len) return CAPITALIZE_NONE; + if (!Character.isUpperCase(text.codePointAt(index))) { + return CAPITALIZE_NONE; + } + int capsCount = 1; + int letterCount = 1; + for (index = text.offsetByCodePoints(index, 1); index < len; + index = text.offsetByCodePoints(index, 1)) { + if (1 != capsCount && letterCount != capsCount) break; + final int codePoint = text.codePointAt(index); + if (Character.isUpperCase(codePoint)) { + ++capsCount; + ++letterCount; + } else if (Character.isLetter(codePoint)) { + // We need to discount non-letters since they may not be upper-case, but may + // still be part of a word (e.g. single quote or dash, as in "IT'S" or "FULL-TIME") + ++letterCount; + } + } + // We know the first char is upper case. So we want to test if either every letter other + // than the first is lower case, or if they are all upper case. If the string is exactly + // one char long, then we will arrive here with letterCount 1, and this is correct, too. + if (1 == capsCount) return CAPITALIZE_FIRST; + return (letterCount == capsCount ? CAPITALIZE_ALL : CAPITALIZE_NONE); + } + + public static boolean isIdenticalAfterUpcase(final String text) { + final int len = text.length(); + for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { + final int codePoint = text.codePointAt(i); + if (Character.isLetter(codePoint) && !Character.isUpperCase(codePoint)) { + return false; + } + } + return true; + } + + public static boolean isIdenticalAfterDowncase(final String text) { + final int len = text.length(); + for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { + final int codePoint = text.codePointAt(i); + if (Character.isLetter(codePoint) && !Character.isLowerCase(codePoint)) { + return false; + } + } + return true; + } + + public static boolean isIdenticalAfterCapitalizeEachWord(final String text, + final String separators) { + boolean needCapsNext = true; + final int len = text.length(); + for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { + final int codePoint = text.codePointAt(i); + if (Character.isLetter(codePoint)) { + if ((needCapsNext && !Character.isUpperCase(codePoint)) + || (!needCapsNext && !Character.isLowerCase(codePoint))) { + return false; + } + } + // We need a capital letter next if this is a separator. + needCapsNext = (-1 != separators.indexOf(codePoint)); + } + return true; + } + + // TODO: like capitalizeFirst*, this does not work perfectly for Dutch because of the IJ digraph + // which should be capitalized together in *some* cases. + public static String capitalizeEachWord(final String text, final String separators, + final Locale locale) { + final StringBuilder builder = new StringBuilder(); + boolean needCapsNext = true; + final int len = text.length(); + for (int i = 0; i < len; i = text.offsetByCodePoints(i, 1)) { + final String nextChar = text.substring(i, text.offsetByCodePoints(i, 1)); + if (needCapsNext) { + builder.append(nextChar.toUpperCase(locale)); + } else { + builder.append(nextChar.toLowerCase(locale)); + } + // We need a capital letter next if this is a separator. + needCapsNext = (-1 != separators.indexOf(nextChar.codePointAt(0))); + } + return builder.toString(); + } + + /** + * Approximates whether the text before the cursor looks like a URL. + * + * This is not foolproof, but it should work well in the practice. + * Essentially it walks backward from the cursor until it finds something that's not a letter, + * digit, or common URL symbol like underscore. If it hasn't found a period yet, then it + * does not look like a URL. + * If the text: + * - starts with www and contains a period + * - starts with a slash preceded by either a slash, whitespace, or start-of-string + * Then it looks like a URL and we return true. Otherwise, we return false. + * + * Note: this method is called quite often, and should be fast. + * + * TODO: This will return that "abc./def" and ".abc/def" look like URLs to keep down the + * code complexity, but ideally it should not. It's acceptable for now. + */ + public static boolean lastPartLooksLikeURL(final CharSequence text) { + int i = text.length(); + if (0 == i) return false; + int wCount = 0; + int slashCount = 0; + boolean hasSlash = false; + boolean hasPeriod = false; + int codePoint = 0; + while (i > 0) { + codePoint = Character.codePointBefore(text, i); + if (codePoint < Constants.CODE_PERIOD || codePoint > 'z') { + // Handwavy heuristic to see if that's a URL character. Anything between period + // and z. This includes all lower- and upper-case ascii letters, period, + // underscore, arrobase, question mark, equal sign. It excludes spaces, exclamation + // marks, double quotes... + // Anything that's not a URL-like character causes us to break from here and + // evaluate normally. + break; + } + if (Constants.CODE_PERIOD == codePoint) { + hasPeriod = true; + } + if (Constants.CODE_SLASH == codePoint) { + hasSlash = true; + if (2 == ++slashCount) { + return true; + } + } else { + slashCount = 0; + } + if ('w' == codePoint) { + ++wCount; + } else { + wCount = 0; + } + i = Character.offsetByCodePoints(text, i, -1); + } + // End of the text run. + // If it starts with www and includes a period, then it looks like a URL. + if (wCount >= 3 && hasPeriod) return true; + // If it starts with a slash, and the code point before is whitespace, it looks like an URL. + if (1 == slashCount && (0 == i || Character.isWhitespace(codePoint))) return true; + // If it has both a period and a slash, it looks like an URL. + if (hasPeriod && hasSlash) return true; + // Otherwise, it doesn't look like an URL. + return false; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java new file mode 100644 index 000000000..afbe2ecad --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2012 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.utils; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.AsyncTask; +import android.util.LruCache; + +public final class TargetPackageInfoGetterTask extends + AsyncTask { + private static final int MAX_CACHE_ENTRIES = 64; // arbitrary + private static final LruCache sCache = + new LruCache(MAX_CACHE_ENTRIES); + + public static PackageInfo getCachedPackageInfo(final String packageName) { + if (null == packageName) return null; + return sCache.get(packageName); + } + + public static void removeCachedPackageInfo(final String packageName) { + sCache.remove(packageName); + } + + public interface OnTargetPackageInfoKnownListener { + public void onTargetPackageInfoKnown(final PackageInfo info); + } + + private Context mContext; + private final OnTargetPackageInfoKnownListener mListener; + + public TargetPackageInfoGetterTask(final Context context, + final OnTargetPackageInfoKnownListener listener) { + mContext = context; + mListener = listener; + } + + @Override + protected PackageInfo doInBackground(final String... packageName) { + final PackageManager pm = mContext.getPackageManager(); + mContext = null; // Bazooka-powered anti-leak device + try { + final PackageInfo packageInfo = pm.getPackageInfo(packageName[0], 0 /* flags */); + sCache.put(packageName[0], packageInfo); + return packageInfo; + } catch (android.content.pm.PackageManager.NameNotFoundException e) { + return null; + } + } + + @Override + protected void onPostExecute(final PackageInfo info) { + mListener.onTargetPackageInfoKnown(info); + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java new file mode 100644 index 000000000..32eb0b2c5 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryDictIOUtils.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2012 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.utils; + +import android.util.Log; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.UserHistoryDictionaryBigramList; +import com.android.inputmethod.latin.makedict.BinaryDictIOUtils; +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput; +import com.android.inputmethod.latin.makedict.BinaryDictInputOutput.FusionDictionaryBufferInterface; +import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; +import com.android.inputmethod.latin.makedict.FusionDictionary; +import com.android.inputmethod.latin.makedict.FusionDictionary.Node; +import com.android.inputmethod.latin.makedict.PendingAttribute; +import com.android.inputmethod.latin.makedict.UnsupportedFormatException; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +/** + * Reads and writes Binary files for a UserHistoryDictionary. + * + * All the methods in this class are static. + */ +public final class UserHistoryDictIOUtils { + private static final String TAG = UserHistoryDictIOUtils.class.getSimpleName(); + private static final boolean DEBUG = false; + + public interface OnAddWordListener { + public void setUnigram(final String word, final String shortcutTarget, final int frequency); + public void setBigram(final String word1, final String word2, final int frequency); + } + + @UsedForTesting + public interface BigramDictionaryInterface { + public int getFrequency(final String word1, final String word2); + } + + /** + * Writes dictionary to file. + */ + public static void writeDictionaryBinary(final OutputStream destination, + final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams, + final FormatOptions formatOptions) { + final FusionDictionary fusionDict = constructFusionDictionary(dict, bigrams); + try { + BinaryDictInputOutput.writeDictionaryBinary(destination, fusionDict, formatOptions); + Log.d(TAG, "end writing"); + } catch (IOException e) { + Log.e(TAG, "IO exception while writing file", e); + } catch (UnsupportedFormatException e) { + Log.e(TAG, "Unsupported format", e); + } + } + + /** + * Constructs a new FusionDictionary from BigramDictionaryInterface. + */ + @UsedForTesting + static FusionDictionary constructFusionDictionary( + final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) { + final FusionDictionary fusionDict = new FusionDictionary(new Node(), + new FusionDictionary.DictionaryOptions(new HashMap(), false, + false)); + int profTotal = 0; + for (final String word1 : bigrams.keySet()) { + final HashMap word1Bigrams = bigrams.getBigrams(word1); + for (final String word2 : word1Bigrams.keySet()) { + final int freq = dict.getFrequency(word1, word2); + if (freq == -1) { + // don't add this bigram. + continue; + } + if (DEBUG) { + if (word1 == null) { + Log.d(TAG, "add unigram: " + word2 + "," + Integer.toString(freq)); + } else { + Log.d(TAG, "add bigram: " + word1 + + "," + word2 + "," + Integer.toString(freq)); + } + profTotal++; + } + if (word1 == null) { // unigram + fusionDict.add(word2, freq, null, false /* isNotAWord */); + } else { // bigram + if (FusionDictionary.findWordInTree(fusionDict.mRoot, word1) == null) { + fusionDict.add(word1, 2, null, false /* isNotAWord */); + } + fusionDict.setBigram(word1, word2, freq); + } + bigrams.updateBigram(word1, word2, (byte)freq); + } + } + if (DEBUG) { + Log.d(TAG, "add " + profTotal + "words"); + } + return fusionDict; + } + + /** + * Reads dictionary from file. + */ + public static void readDictionaryBinary(final FusionDictionaryBufferInterface buffer, + final OnAddWordListener dict) { + final Map unigrams = CollectionUtils.newTreeMap(); + final Map frequencies = CollectionUtils.newTreeMap(); + final Map> bigrams = CollectionUtils.newTreeMap(); + try { + BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies, + bigrams); + } catch (IOException e) { + Log.e(TAG, "IO exception while reading file", e); + } catch (UnsupportedFormatException e) { + Log.e(TAG, "Unsupported format", e); + } catch (ArrayIndexOutOfBoundsException e) { + Log.e(TAG, "ArrayIndexOutOfBoundsException while reading file", e); + } + addWordsFromWordMap(unigrams, frequencies, bigrams, dict); + } + + /** + * Adds all unigrams and bigrams in maps to OnAddWordListener. + */ + @UsedForTesting + static void addWordsFromWordMap(final Map unigrams, + final Map frequencies, + final Map> bigrams, final OnAddWordListener to) { + for (Map.Entry entry : unigrams.entrySet()) { + final String word1 = entry.getValue(); + final int unigramFrequency = frequencies.get(entry.getKey()); + to.setUnigram(word1, null, unigramFrequency); + final ArrayList attrList = bigrams.get(entry.getKey()); + if (attrList != null) { + for (final PendingAttribute attr : attrList) { + final String word2 = unigrams.get(attr.mAddress); + if (word1 == null || word2 == null) { + Log.e(TAG, "Invalid bigram pair detected: " + word1 + ", " + word2); + continue; + } + to.setBigram(word1, word2, + BinaryDictInputOutput.reconstructBigramFrequency(unigramFrequency, + attr.mFrequency)); + } + } + } + + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java new file mode 100644 index 000000000..9f842f976 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/UserHistoryForgettingCurveUtils.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2012 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.utils; + +import android.text.format.DateUtils; +import android.util.Log; + +public final class UserHistoryForgettingCurveUtils { + private static final String TAG = UserHistoryForgettingCurveUtils.class.getSimpleName(); + private static final boolean DEBUG = false; + private static final int FC_FREQ_MAX = 127; + /* package */ static final int COUNT_MAX = 3; + private static final int FC_LEVEL_MAX = 3; + /* package */ static final int ELAPSED_TIME_MAX = 15; + private static final int ELAPSED_TIME_INTERVAL_HOURS = 6; + private static final long ELAPSED_TIME_INTERVAL_MILLIS = ELAPSED_TIME_INTERVAL_HOURS + * DateUtils.HOUR_IN_MILLIS; + private static final int HALF_LIFE_HOURS = 48; + private static final int MAX_PUSH_ELAPSED = (FC_LEVEL_MAX + 1) * (ELAPSED_TIME_MAX + 1); + + private UserHistoryForgettingCurveUtils() { + // This utility class is not publicly instantiable. + } + + public static final class ForgettingCurveParams { + private byte mFc; + long mLastTouchedTime = 0; + private final boolean mIsValid; + + private void updateLastTouchedTime() { + mLastTouchedTime = System.currentTimeMillis(); + } + + public ForgettingCurveParams(boolean isValid) { + this(System.currentTimeMillis(), isValid); + } + + private ForgettingCurveParams(long now, boolean isValid) { + this(pushCount((byte)0, isValid), now, now, isValid); + } + + /** This constructor is called when the user history bigram dictionary is being restored. */ + public ForgettingCurveParams(int fc, long now, long last) { + // All words with level >= 1 had been saved. + // Invalid words with level == 0 had been saved. + // Valid words words with level == 0 had *not* been saved. + this(fc, now, last, fcToLevel((byte)fc) > 0); + } + + private ForgettingCurveParams(int fc, long now, long last, boolean isValid) { + mIsValid = isValid; + mFc = (byte)fc; + mLastTouchedTime = last; + updateElapsedTime(now); + } + + public boolean isValid() { + return mIsValid; + } + + public byte getFc() { + updateElapsedTime(System.currentTimeMillis()); + return mFc; + } + + public int getFrequency() { + updateElapsedTime(System.currentTimeMillis()); + return UserHistoryForgettingCurveUtils.fcToFreq(mFc); + } + + public int notifyTypedAgainAndGetFrequency() { + updateLastTouchedTime(); + // TODO: Check whether this word is valid or not + mFc = pushCount(mFc, false); + return UserHistoryForgettingCurveUtils.fcToFreq(mFc); + } + + private void updateElapsedTime(long now) { + final int elapsedTimeCount = + (int)((now - mLastTouchedTime) / ELAPSED_TIME_INTERVAL_MILLIS); + if (elapsedTimeCount <= 0) { + return; + } + if (elapsedTimeCount >= MAX_PUSH_ELAPSED) { + mLastTouchedTime = now; + mFc = 0; + return; + } + for (int i = 0; i < elapsedTimeCount; ++i) { + mLastTouchedTime += ELAPSED_TIME_INTERVAL_MILLIS; + mFc = pushElapsedTime(mFc); + } + } + } + + /* package */ static int fcToElapsedTime(byte fc) { + return fc & 0x0F; + } + + /* package */ static int fcToCount(byte fc) { + return (fc >> 4) & 0x03; + } + + /* package */ static int fcToLevel(byte fc) { + return (fc >> 6) & 0x03; + } + + private static int calcFreq(int elapsedTime, int count, int level) { + if (level <= 0) { + // Reserved words, just return -1 + return -1; + } + if (count == COUNT_MAX) { + // Temporary promote because it's frequently typed recently + ++level; + } + final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime)); + final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level)); + return MathUtils.SCORE_TABLE[l - 1][et]; + } + + /* pakcage */ static byte calcFc(int elapsedTime, int count, int level) { + final int et = Math.min(FC_FREQ_MAX, Math.max(0, elapsedTime)); + final int c = Math.min(COUNT_MAX, Math.max(0, count)); + final int l = Math.min(FC_LEVEL_MAX, Math.max(0, level)); + return (byte)(et | (c << 4) | (l << 6)); + } + + public static int fcToFreq(byte fc) { + final int elapsedTime = fcToElapsedTime(fc); + final int count = fcToCount(fc); + final int level = fcToLevel(fc); + return calcFreq(elapsedTime, count, level); + } + + public static byte pushElapsedTime(byte fc) { + int elapsedTime = fcToElapsedTime(fc); + int count = fcToCount(fc); + int level = fcToLevel(fc); + if (elapsedTime >= ELAPSED_TIME_MAX) { + // Downgrade level + elapsedTime = 0; + count = COUNT_MAX; + --level; + } else { + ++elapsedTime; + } + return calcFc(elapsedTime, count, level); + } + + public static byte pushCount(byte fc, boolean isValid) { + final int elapsedTime = fcToElapsedTime(fc); + int count = fcToCount(fc); + int level = fcToLevel(fc); + if ((elapsedTime == 0 && count >= COUNT_MAX) || (isValid && level == 0)) { + // Upgrade level + ++level; + count = 0; + if (DEBUG) { + Log.d(TAG, "Upgrade level."); + } + } else { + ++count; + } + return calcFc(0, count, level); + } + + // TODO: isValid should be false for a word whose frequency is 0, + // or that is not in the dictionary. + /** + * Check wheather we should save the bigram to the SQL DB or not + */ + public static boolean needsToSave(byte fc, boolean isValid, boolean addLevel0Bigram) { + int level = fcToLevel(fc); + if (level == 0) { + if (isValid || !addLevel0Bigram) { + return false; + } + } + final int elapsedTime = fcToElapsedTime(fc); + return (elapsedTime < ELAPSED_TIME_MAX - 1 || level > 0); + } + + private static final class MathUtils { + public static final int[][] SCORE_TABLE = new int[FC_LEVEL_MAX][ELAPSED_TIME_MAX + 1]; + static { + for (int i = 0; i < FC_LEVEL_MAX; ++i) { + final float initialFreq; + if (i >= 2) { + initialFreq = FC_FREQ_MAX; + } else if (i == 1) { + initialFreq = FC_FREQ_MAX / 2; + } else if (i == 0) { + initialFreq = FC_FREQ_MAX / 4; + } else { + continue; + } + for (int j = 0; j < ELAPSED_TIME_MAX; ++j) { + final float elapsedHours = j * ELAPSED_TIME_INTERVAL_HOURS; + final float freq = initialFreq + * (float)Math.pow(initialFreq, elapsedHours / HALF_LIFE_HOURS); + final int intFreq = Math.min(FC_FREQ_MAX, Math.max(0, (int)freq)); + SCORE_TABLE[i][j] = intFreq; + } + } + } + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/Utils.java b/java/src/com/android/inputmethod/latin/utils/Utils.java new file mode 100644 index 000000000..390d306c8 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/Utils.java @@ -0,0 +1,506 @@ +/* + * Copyright (C) 2010 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.utils; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.inputmethodservice.InputMethodService; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; +import android.text.TextUtils; +import android.util.Log; + +import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.LatinIME; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.WordComposer; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.channels.FileChannel; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +// TODO: Come up with a more descriptive class name +public final class Utils { + private static final String TAG = Utils.class.getSimpleName(); + + private Utils() { + // This utility class is not publicly instantiable. + } + + /** + * Cancel an {@link AsyncTask}. + * + * @param mayInterruptIfRunning true if the thread executing this + * task should be interrupted; otherwise, in-progress tasks are allowed + * to complete. + */ + public static void cancelTask(final AsyncTask task, + final boolean mayInterruptIfRunning) { + if (task != null && task.getStatus() != AsyncTask.Status.FINISHED) { + task.cancel(mayInterruptIfRunning); + } + } + + // TODO: Make this an external class + public /* for test */ static final class RingCharBuffer { + public /* for test */ static final int BUFSIZE = 20; + public /* for test */ int mLength = 0; + + private static RingCharBuffer sRingCharBuffer = new RingCharBuffer(); + private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC'; + private static final int INVALID_COORDINATE = -2; + private InputMethodService mContext; + private boolean mEnabled = false; + private int mEnd = 0; + private char[] mCharBuf = new char[BUFSIZE]; + private int[] mXBuf = new int[BUFSIZE]; + private int[] mYBuf = new int[BUFSIZE]; + + private RingCharBuffer() { + // Intentional empty constructor for singleton. + } + + @UsedForTesting + public static RingCharBuffer getInstance() { + return sRingCharBuffer; + } + + public static RingCharBuffer init(final InputMethodService context, final boolean enabled, + final boolean usabilityStudy) { + if (!(enabled || usabilityStudy)) { + return null; + } + sRingCharBuffer.mContext = context; + sRingCharBuffer.mEnabled = true; + UsabilityStudyLogUtils.getInstance().init(context); + return sRingCharBuffer; + } + + private static int normalize(final int in) { + int ret = in % BUFSIZE; + return ret < 0 ? ret + BUFSIZE : ret; + } + + // TODO: accept code points + @UsedForTesting + public void push(final char c, final int x, final int y) { + if (!mEnabled) { + return; + } + mCharBuf[mEnd] = c; + mXBuf[mEnd] = x; + mYBuf[mEnd] = y; + mEnd = normalize(mEnd + 1); + if (mLength < BUFSIZE) { + ++mLength; + } + } + + public char pop() { + if (mLength < 1) { + return PLACEHOLDER_DELIMITER_CHAR; + } + mEnd = normalize(mEnd - 1); + --mLength; + return mCharBuf[mEnd]; + } + + public char getBackwardNthChar(final int n) { + if (mLength <= n || n < 0) { + return PLACEHOLDER_DELIMITER_CHAR; + } + return mCharBuf[normalize(mEnd - n - 1)]; + } + + public int getPreviousX(final char c, final int back) { + final int index = normalize(mEnd - 2 - back); + if (mLength <= back + || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { + return INVALID_COORDINATE; + } + return mXBuf[index]; + } + + public int getPreviousY(final char c, final int back) { + int index = normalize(mEnd - 2 - back); + if (mLength <= back + || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { + return INVALID_COORDINATE; + } + return mYBuf[index]; + } + + public String getLastWord(final int ignoreCharCount) { + final StringBuilder sb = new StringBuilder(); + final LatinIME latinIme = (LatinIME)mContext; + int i = ignoreCharCount; + for (; i < mLength; ++i) { + final char c = mCharBuf[normalize(mEnd - 1 - i)]; + if (!latinIme.isWordSeparator(c)) { + break; + } + } + for (; i < mLength; ++i) { + char c = mCharBuf[normalize(mEnd - 1 - i)]; + if (!latinIme.isWordSeparator(c)) { + sb.append(c); + } else { + break; + } + } + return sb.reverse().toString(); + } + + public void reset() { + mLength = 0; + } + } + + // TODO: Make this an external class + public static final class UsabilityStudyLogUtils { + // TODO: remove code duplication with ResearchLog class + private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName(); + private static final String FILENAME = "log.txt"; + private final Handler mLoggingHandler; + private File mFile; + private File mDirectory; + private InputMethodService mIms; + private PrintWriter mWriter; + private final Date mDate; + private final SimpleDateFormat mDateFormat; + + private UsabilityStudyLogUtils() { + mDate = new Date(); + mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US); + + HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task", + Process.THREAD_PRIORITY_BACKGROUND); + handlerThread.start(); + mLoggingHandler = new Handler(handlerThread.getLooper()); + } + + // Initialization-on-demand holder + private static final class OnDemandInitializationHolder { + public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils(); + } + + public static UsabilityStudyLogUtils getInstance() { + return OnDemandInitializationHolder.sInstance; + } + + public void init(final InputMethodService ims) { + mIms = ims; + mDirectory = ims.getFilesDir(); + } + + private void createLogFileIfNotExist() { + if ((mFile == null || !mFile.exists()) + && (mDirectory != null && mDirectory.exists())) { + try { + mWriter = getPrintWriter(mDirectory, FILENAME, false); + } catch (final IOException e) { + Log.e(USABILITY_TAG, "Can't create log file."); + } + } + } + + public static void writeBackSpace(final int x, final int y) { + UsabilityStudyLogUtils.getInstance().write("\t" + x + "\t" + y); + } + + public static void writeChar(final char c, final int x, final int y) { + String inputChar = String.valueOf(c); + switch (c) { + case '\n': + inputChar = ""; + break; + case '\t': + inputChar = ""; + break; + case ' ': + inputChar = ""; + break; + } + UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y); + LatinImeLogger.onPrintAllUsabilityStudyLogs(); + } + + public void write(final String log) { + mLoggingHandler.post(new Runnable() { + @Override + public void run() { + createLogFileIfNotExist(); + final long currentTime = System.currentTimeMillis(); + mDate.setTime(currentTime); + + final String printString = String.format(Locale.US, "%s\t%d\t%s\n", + mDateFormat.format(mDate), currentTime, log); + if (LatinImeLogger.sDBG) { + Log.d(USABILITY_TAG, "Write: " + log); + } + mWriter.print(printString); + } + }); + } + + private synchronized String getBufferedLogs() { + mWriter.flush(); + final StringBuilder sb = new StringBuilder(); + final BufferedReader br = getBufferedReader(); + String line; + try { + while ((line = br.readLine()) != null) { + sb.append('\n'); + sb.append(line); + } + } catch (final IOException e) { + Log.e(USABILITY_TAG, "Can't read log file."); + } finally { + if (LatinImeLogger.sDBG) { + Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString()); + } + try { + br.close(); + } catch (final IOException e) { + // ignore. + } + } + return sb.toString(); + } + + public void emailResearcherLogsAll() { + mLoggingHandler.post(new Runnable() { + @Override + public void run() { + final Date date = new Date(); + date.setTime(System.currentTimeMillis()); + final String currentDateTimeString = + new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date); + if (mFile == null) { + Log.w(USABILITY_TAG, "No internal log file found."); + return; + } + if (mIms.checkCallingOrSelfPermission( + android.Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE"); + return; + } + mWriter.flush(); + final String destPath = Environment.getExternalStorageDirectory() + + "/research-" + currentDateTimeString + ".log"; + final File destFile = new File(destPath); + try { + final FileInputStream srcStream = new FileInputStream(mFile); + final FileOutputStream destStream = new FileOutputStream(destFile); + final FileChannel src = srcStream.getChannel(); + final FileChannel dest = destStream.getChannel(); + src.transferTo(0, src.size(), dest); + src.close(); + srcStream.close(); + dest.close(); + destStream.close(); + } catch (final FileNotFoundException e1) { + Log.w(USABILITY_TAG, e1); + return; + } catch (final IOException e2) { + Log.w(USABILITY_TAG, e2); + return; + } + if (destFile == null || !destFile.exists()) { + Log.w(USABILITY_TAG, "Dest file doesn't exist."); + return; + } + final Intent intent = new Intent(Intent.ACTION_SEND); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (LatinImeLogger.sDBG) { + Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI()); + } + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath)); + intent.putExtra(Intent.EXTRA_SUBJECT, + "[Research Logs] " + currentDateTimeString); + mIms.startActivity(intent); + } + }); + } + + public void printAll() { + mLoggingHandler.post(new Runnable() { + @Override + public void run() { + mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0); + } + }); + } + + public void clearAll() { + mLoggingHandler.post(new Runnable() { + @Override + public void run() { + if (mFile != null && mFile.exists()) { + if (LatinImeLogger.sDBG) { + Log.d(USABILITY_TAG, "Delete log file."); + } + mFile.delete(); + mWriter.close(); + } + } + }); + } + + private BufferedReader getBufferedReader() { + createLogFileIfNotExist(); + try { + return new BufferedReader(new FileReader(mFile)); + } catch (final FileNotFoundException e) { + return null; + } + } + + private PrintWriter getPrintWriter(final File dir, final String filename, + final boolean renew) throws IOException { + mFile = new File(dir, filename); + if (mFile.exists()) { + if (renew) { + mFile.delete(); + } + } + return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */); + } + } + + // TODO: Make this an external class + public static final class Stats { + public static void onNonSeparator(final char code, final int x, final int y) { + RingCharBuffer.getInstance().push(code, x, y); + LatinImeLogger.logOnInputChar(); + } + + public static void onSeparator(final int code, final int x, final int y) { + // Helper method to log a single code point separator + // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils + onSeparator(new String(new int[]{code}, 0, 1), x, y); + } + + public static void onSeparator(final String separator, final int x, final int y) { + final int length = separator.length(); + for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) { + int codePoint = Character.codePointAt(separator, i); + // TODO: accept code points + RingCharBuffer.getInstance().push((char)codePoint, x, y); + } + LatinImeLogger.logOnInputSeparator(); + } + + public static void onAutoCorrection(final String typedWord, final String correctedWord, + final String separatorString, final WordComposer wordComposer) { + final boolean isBatchMode = wordComposer.isBatchMode(); + if (!isBatchMode && TextUtils.isEmpty(typedWord)) { + return; + } + // TODO: this fails when the separator is more than 1 code point long, but + // the backend can't handle it yet. The only case when this happens is with + // smileys and other multi-character keys. + final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE + : separatorString.codePointAt(0); + if (!isBatchMode) { + LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint); + } else { + if (!TextUtils.isEmpty(correctedWord)) { + // We must make sure that InputPointer contains only the relative timestamps, + // not actual timestamps. + LatinImeLogger.logOnAutoCorrectionForGeometric( + "", correctedWord, codePoint, wordComposer.getInputPointers()); + } + } + } + + public static void onAutoCorrectionCancellation() { + LatinImeLogger.logOnAutoCorrectionCancelled(); + } + } + + public static String getDebugInfo(final SuggestedWords suggestions, final int pos) { + if (!LatinImeLogger.sDBG) { + return null; + } + final SuggestedWordInfo wordInfo = suggestions.getInfo(pos); + if (wordInfo == null) { + return null; + } + final String info = wordInfo.getDebugString(); + if (TextUtils.isEmpty(info)) { + return null; + } + return info; + } + + public static int getAcitivityTitleResId(final Context context, + final Class cls) { + final ComponentName cn = new ComponentName(context, cls); + try { + final ActivityInfo ai = context.getPackageManager().getActivityInfo(cn, 0); + if (ai != null) { + return ai.labelRes; + } + } catch (final NameNotFoundException e) { + Log.e(TAG, "Failed to get settings activity title res id.", e); + } + return 0; + } + + /** + * A utility method to get the application's PackageInfo.versionName + * @return the application's PackageInfo.versionName + */ + public static String getVersionName(final Context context) { + try { + if (context == null) { + return ""; + } + final String packageName = context.getPackageName(); + final PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); + return info.versionName; + } catch (final NameNotFoundException e) { + Log.e(TAG, "Could not find version info.", e); + } + return ""; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java b/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java new file mode 100644 index 000000000..bdad16652 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/XmlParseUtils.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.utils; + +import android.content.res.TypedArray; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +public final class XmlParseUtils { + private XmlParseUtils() { + // This utility class is not publicly instantiable. + } + + @SuppressWarnings("serial") + public static class ParseException extends XmlPullParserException { + public ParseException(final String msg, final XmlPullParser parser) { + super(msg + " at " + parser.getPositionDescription()); + } + } + + @SuppressWarnings("serial") + public static final class IllegalStartTag extends ParseException { + public IllegalStartTag(final XmlPullParser parser, final String tag, final String parent) { + super("Illegal start tag " + tag + " in " + parent, parser); + } + } + + @SuppressWarnings("serial") + public static final class IllegalEndTag extends ParseException { + public IllegalEndTag(final XmlPullParser parser, final String tag, final String parent) { + super("Illegal end tag " + tag + " in " + parent, parser); + } + } + + @SuppressWarnings("serial") + public static final class IllegalAttribute extends ParseException { + public IllegalAttribute(final XmlPullParser parser, final String tag, + final String attribute) { + super("Tag " + tag + " has illegal attribute " + attribute, parser); + } + } + + @SuppressWarnings("serial") + public static final class NonEmptyTag extends ParseException{ + public NonEmptyTag(final XmlPullParser parser, final String tag) { + super(tag + " must be empty tag", parser); + } + } + + public static void checkEndTag(final String tag, final XmlPullParser parser) + throws XmlPullParserException, IOException { + if (parser.next() == XmlPullParser.END_TAG && tag.equals(parser.getName())) + return; + throw new NonEmptyTag(parser, tag); + } + + public static void checkAttributeExists(final TypedArray attr, final int attrId, + final String attrName, final String tag, final XmlPullParser parser) + throws XmlPullParserException { + if (attr.hasValue(attrId)) { + return; + } + throw new ParseException( + "No " + attrName + " attribute found in <" + tag + "/>", parser); + } +} diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java index 6029ba963..3abeb76a0 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -61,7 +61,6 @@ import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.InputTypeUtils; import com.android.inputmethod.latin.LatinIME; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputConnection; @@ -69,6 +68,7 @@ import com.android.inputmethod.latin.RichInputConnection.Range; import com.android.inputmethod.latin.Suggest; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.define.ProductionFlag; +import com.android.inputmethod.latin.utils.InputTypeUtils; import com.android.inputmethod.research.MotionEventReader.ReplayData; import java.io.File; -- cgit v1.2.3-83-g751a