diff options
Diffstat (limited to 'java/src')
13 files changed, 246 insertions, 93 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java index 9779c683c..f12373503 100644 --- a/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java +++ b/java/src/com/android/inputmethod/keyboard/EmojiPalettesView.java @@ -162,9 +162,11 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange addShownCategoryId(CATEGORY_ID_OBJECTS); addShownCategoryId(CATEGORY_ID_NATURE); addShownCategoryId(CATEGORY_ID_PLACES); - mCurrentCategoryId = CATEGORY_ID_PEOPLE; + mCurrentCategoryId = + Settings.readLastShownEmojiCategoryId(mPrefs, CATEGORY_ID_PEOPLE); } else { - mCurrentCategoryId = CATEGORY_ID_SYMBOLS; + mCurrentCategoryId = + Settings.readLastShownEmojiCategoryId(mPrefs, CATEGORY_ID_SYMBOLS); } addShownCategoryId(CATEGORY_ID_SYMBOLS); addShownCategoryId(CATEGORY_ID_EMOTICONS); @@ -222,6 +224,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange public void setCurrentCategoryId(int categoryId) { mCurrentCategoryId = categoryId; + Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId); } public void setCurrentCategoryPageId(int id) { @@ -233,7 +236,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange } public void saveLastTypedCategoryPage() { - Settings.writeEmojiCategoryLastTypedId( + Settings.writeLastTypedEmojiCategoryPageId( mPrefs, mCurrentCategoryId, mCurrentCategoryPageId); } @@ -254,7 +257,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange // Returns the view pager's page position for the categoryId public int getPageIdFromCategoryId(int categoryId) { final int lastSavedCategoryPageId = - Settings.readEmojiCategoryLastTypedId(mPrefs, categoryId); + Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId); int sum = 0; for (int i = 0; i < mShownCategories.size(); ++i) { final CategoryProperties props = mShownCategories.get(i); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 97609837e..b7521b998 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -155,7 +155,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } public void saveKeyboardState() { - if (getKeyboard() != null || isShowingEmojiKeyboard()) { + if (getKeyboard() != null || isShowingEmojiPalettes()) { mState.onSaveKeyboardState(); } } @@ -316,19 +316,23 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mState.onCodeInput(code, mLatinIME.getCurrentAutoCapsState()); } - public boolean isShowingEmojiKeyboard() { - return mEmojiPalettesView != null && mEmojiPalettesView.getVisibility() == View.VISIBLE; + private boolean isShowingMainKeyboard() { + return null != mKeyboardView && mKeyboardView.isShown(); + } + + public boolean isShowingEmojiPalettes() { + return mEmojiPalettesView != null && mEmojiPalettesView.isShown(); } public boolean isShowingMoreKeysPanel() { - if (isShowingEmojiKeyboard()) { + if (isShowingEmojiPalettes()) { return false; } return mKeyboardView.isShowingMoreKeysPanel(); } public View getVisibleKeyboardView() { - if (isShowingEmojiKeyboard()) { + if (isShowingEmojiPalettes()) { return mEmojiPalettesView; } return mKeyboardView; @@ -348,6 +352,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } } + public boolean isShowingMainKeyboardOrEmojiPalettes() { + return isShowingMainKeyboard() || isShowingEmojiPalettes(); + } + public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) { if (mKeyboardView != null) { mKeyboardView.closing(); diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index c4f96016c..9a9653094 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -174,6 +174,7 @@ public final class Constants { public static final int CODE_SLASH = '/'; public static final int CODE_COMMERCIAL_AT = '@'; public static final int CODE_PLUS = '+'; + public static final int CODE_PERCENT = '%'; public static final int CODE_CLOSING_PARENTHESIS = ')'; public static final int CODE_CLOSING_SQUARE_BRACKET = ']'; public static final int CODE_CLOSING_CURLY_BRACKET = '}'; diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java index 642b3a4da..2e9280c77 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -16,8 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.latin.utils.StringUtils; - import android.text.TextUtils; /** @@ -85,8 +83,4 @@ public final class LastComposedWord { private boolean didCommitTypedWord() { return TextUtils.equals(mTypedWord, mCommittedWord); } - - public static int getSeparatorLength(final String separatorString) { - return StringUtils.codePointCount(separatorString); - } } diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index b668a7770..e8ebf8860 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -99,6 +99,7 @@ import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LatinImeLoggerUtils; import com.android.inputmethod.latin.utils.RecapitalizeStatus; import com.android.inputmethod.latin.utils.StaticInnerHandlerWrapper; +import com.android.inputmethod.latin.utils.StringUtils; import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; import com.android.inputmethod.latin.utils.TextRange; import com.android.inputmethod.latin.utils.UserHistoryForgettingCurveUtils; @@ -1261,9 +1262,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final boolean needsInputViewShown) { // TODO: Modify this if we support suggestions with hard keyboard if (onEvaluateInputViewShown() && mSuggestionStripView != null) { - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - final boolean inputViewShown = (mainKeyboardView != null) - ? mainKeyboardView.isShown() : false; + final boolean inputViewShown = mKeyboardSwitcher.isShowingMainKeyboardOrEmojiPalettes(); final boolean shouldShowSuggestions = shown && (needsInputViewShown ? inputViewShown : true); if (isFullscreenMode()) { @@ -1329,7 +1328,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (visibleKeyboardView.isShown()) { // Note that the height of Emoji layout is the same as the height of the main keyboard // and the suggestion strip - if (mKeyboardSwitcher.isShowingEmojiKeyboard() + if (mKeyboardSwitcher.isShowingEmojiPalettes() || mSuggestionStripView.getVisibility() == View.VISIBLE) { visibleTopY -= suggestionsHeight; } @@ -1503,6 +1502,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen || codePoint == Constants.CODE_CLOSING_CURLY_BRACKET || codePoint == Constants.CODE_CLOSING_ANGLE_BRACKET || codePoint == Constants.CODE_PLUS + || codePoint == Constants.CODE_PERCENT || Character.getType(codePoint) == Character.OTHER_SYMBOL; } @@ -1588,8 +1588,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // relying on this behavior so we continue to support it for older apps. sendDownUpKeyEvent(KeyEvent.KEYCODE_ENTER); } else { - final String text = new String(new int[] { code }, 0, 1); - mConnection.commitText(text, text.length()); + mConnection.commitText(StringUtils.newSingleCodePointString(code), 1); } } @@ -1624,8 +1623,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case Constants.CODE_DELETE: mSpaceState = SPACE_STATE_NONE; handleBackspace(spaceState); - mDeleteCount++; - mExpectingUpdateSelection = true; LatinImeLogger.logOnDelete(x, y); break; case Constants.CODE_SHIFT: @@ -1712,7 +1709,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSpaceState = SPACE_STATE_NONE; final boolean didAutoCorrect; final SettingsValues settingsValues = mSettings.getCurrent(); - if (settingsValues.isWordSeparator(primaryCode)) { + if (settingsValues.isWordSeparator(primaryCode) + || Character.getType(primaryCode) == Character.OTHER_SYMBOL) { didAutoCorrect = handleSeparator(primaryCode, x, y, spaceState); } else { didAutoCorrect = false; @@ -2052,6 +2050,10 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private void handleBackspace(final int spaceState) { + // We revert these in this method if the deletion doesn't happen. + mDeleteCount++; + mExpectingUpdateSelection = true; + // In many cases, we may have to put the keyboard in auto-shift state again. However // we want to wait a few milliseconds before doing it to avoid the keyboard flashing // during key repeat. @@ -2141,8 +2143,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // This should never happen. Log.e(TAG, "Backspace when we don't know the selection position"); } - final int lengthToDelete = Character.isSupplementaryCodePoint( - mConnection.getCodePointBeforeCursor()) ? 2 : 1; + final int codePointBeforeCursor = mConnection.getCodePointBeforeCursor(); + if (codePointBeforeCursor == Constants.NOT_A_CODE) { + // Nothing to delete before the cursor. We have to revert the deletion states + // that were updated at the beginning of this method. + mDeleteCount--; + mExpectingUpdateSelection = false; + return; + } + final int lengthToDelete = + Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1; if (mAppWorkAroundsUtils.isBeforeJellyBean() || currentSettings.mInputAttributes.isTypeNull()) { // There are two possible reasons to send a key event: either the field has @@ -2161,12 +2171,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen true /* shouldUncommitLogUnit */); } if (mDeleteCount > DELETE_ACCELERATE_AT) { - final int lengthToDeleteAgain = Character.isSupplementaryCodePoint( - mConnection.getCodePointBeforeCursor()) ? 2 : 1; - mConnection.deleteSurroundingText(lengthToDeleteAgain, 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain, - true /* shouldUncommitLogUnit */); + final int codePointBeforeCursorToDeleteAgain = + mConnection.getCodePointBeforeCursor(); + if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) { + final int lengthToDeleteAgain = Character.isSupplementaryCodePoint( + codePointBeforeCursorToDeleteAgain) ? 2 : 1; + mConnection.deleteSurroundingText(lengthToDeleteAgain, 0); + if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { + ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain, + true /* shouldUncommitLogUnit */); + } } } } @@ -2336,11 +2350,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mWordComposer.isComposingWord()) { // May have changed since we stored wasComposing if (currentSettings.mCorrectionEnabled) { final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR - : new String(new int[] { primaryCode }, 0, 1); + : StringUtils.newSingleCodePointString(primaryCode); commitCurrentAutoCorrection(separator); didAutoCorrect = true; } else { - commitTyped(new String(new int[]{primaryCode}, 0, 1)); + commitTyped(StringUtils.newSingleCodePointString(primaryCode)); } } @@ -2979,8 +2993,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final String originallyTypedWord = mLastComposedWord.mTypedWord; final String committedWord = mLastComposedWord.mCommittedWord; final int cancelLength = committedWord.length(); - final int separatorLength = LastComposedWord.getSeparatorLength( - mLastComposedWord.mSeparatorString); + // We want java chars, not codepoints for the following. + final int separatorLength = mLastComposedWord.mSeparatorString.length(); // TODO: should we check our saved separator against the actual contents of the text view? final int deleteLength = cancelLength + separatorLength; if (DEBUG) { diff --git a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java index 9f7f502ea..fda97dafc 100644 --- a/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/AbstractDictDecoder.java @@ -60,7 +60,8 @@ public abstract class AbstractDictDecoder implements DictDecoder { 0 != (optionsFlags & FormatSpec.GERMAN_UMLAUT_PROCESSING_FLAG), 0 != (optionsFlags & FormatSpec.FRENCH_LIGATURE_PROCESSING_FLAG)), new FormatOptions(version, - 0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE))); + 0 != (optionsFlags & FormatSpec.SUPPORTS_DYNAMIC_UPDATE), + 0 != (optionsFlags & FormatSpec.CONTAINS_TIMESTAMP_FLAG))); return header; } diff --git a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java index 336277196..28da9ffdd 100644 --- a/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/DynamicBinaryDictIOUtils.java @@ -22,6 +22,7 @@ import com.android.inputmethod.latin.makedict.BinaryDictDecoderUtils.DictBuffer; import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader; import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions; import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString; +import com.android.inputmethod.latin.utils.CollectionUtils; import java.io.IOException; import java.io.OutputStream; @@ -217,6 +218,25 @@ public final class DynamicBinaryDictIOUtils { } /** + * Converts a list of WeightedString to a list of PendingAttribute. + */ + public static ArrayList<PendingAttribute> resolveBigramPositions(final DictUpdater dictUpdater, + final ArrayList<WeightedString> bigramStrings) + throws IOException, UnsupportedFormatException { + if (bigramStrings == null) return CollectionUtils.newArrayList(); + final ArrayList<PendingAttribute> bigrams = CollectionUtils.newArrayList(); + for (final WeightedString bigram : bigramStrings) { + final int pos = dictUpdater.getTerminalPosition(bigram.mWord); + if (pos == FormatSpec.NOT_VALID_WORD) { + // TODO: figure out what is the correct thing to do here. + } else { + bigrams.add(new PendingAttribute(bigram.mFrequency, pos)); + } + } + return bigrams; + } + + /** * Insert a word into a binary dictionary. * * @param dictUpdater the dict updater. @@ -238,18 +258,9 @@ public final class DynamicBinaryDictIOUtils { final ArrayList<WeightedString> shortcuts, final boolean isNotAWord, final boolean isBlackListEntry) throws IOException, UnsupportedFormatException { - final ArrayList<PendingAttribute> bigrams = new ArrayList<PendingAttribute>(); + final ArrayList<PendingAttribute> bigrams = resolveBigramPositions(dictUpdater, + bigramStrings); final DictBuffer dictBuffer = dictUpdater.getDictBuffer(); - if (bigramStrings != null) { - for (final WeightedString bigram : bigramStrings) { - int position = dictUpdater.getTerminalPosition(bigram.mWord); - if (position == FormatSpec.NOT_VALID_WORD) { - // TODO: figure out what is the correct thing to do here. - } else { - bigrams.add(new PendingAttribute(bigram.mFrequency, position)); - } - } - } final boolean isTerminal = true; final boolean hasBigrams = !bigrams.isEmpty(); diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index 5a5d7af6b..6d5827023 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -37,13 +37,15 @@ public final class FormatSpec { * sion * * o | - * p | not used 4 bits - * t | has bigrams ? 1 bit, 1 = yes, 0 = no : CONTAINS_BIGRAMS_FLAG - * i | FRENCH_LIGATURE_PROCESSING_FLAG - * o | supports dynamic updates ? 1 bit, 1 = yes, 0 = no : SUPPORTS_DYNAMIC_UPDATE - * n | GERMAN_UMLAUT_PROCESSING_FLAG - * f | - * lags + * p | not used 3 bits + * t | each unigram and bigram entry has a time stamp? + * i | 1 bit, 1 = yes, 0 = no : CONTAINS_TIMESTAMP_FLAG + * o | has bigrams ? 1 bit, 1 = yes, 0 = no : CONTAINS_BIGRAMS_FLAG + * n | FRENCH_LIGATURE_PROCESSING_FLAG + * f | supports dynamic updates ? 1 bit, 1 = yes, 0 = no : SUPPORTS_DYNAMIC_UPDATE + * l | GERMAN_UMLAUT_PROCESSING_FLAG + * a | + * gs * * h | * e | size of the file header, 4bytes @@ -211,6 +213,7 @@ public final class FormatSpec { static final int SUPPORTS_DYNAMIC_UPDATE = 0x2; static final int FRENCH_LIGATURE_PROCESSING_FLAG = 0x4; static final int CONTAINS_BIGRAMS_FLAG = 0x8; + static final int CONTAINS_TIMESTAMP_FLAG = 0x10; // TODO: Make this value adaptative to content data, store it in the header, and // use it in the reading code. @@ -263,6 +266,7 @@ public final class FormatSpec { // These values are used only by version 4 or later. static final String TRIE_FILE_EXTENSION = ".trie"; static final String FREQ_FILE_EXTENSION = ".freq"; + static final String UNIGRAM_TIMESTAMP_FILE_EXTENSION = ".timestamp"; // tat = Terminal Address Table static final String TERMINAL_ADDRESS_TABLE_FILE_EXTENSION = ".tat"; static final String BIGRAM_FILE_EXTENSION = ".bigram"; @@ -271,14 +275,20 @@ public final class FormatSpec { static final String CONTENT_TABLE_FILE_SUFFIX = "_index"; static final int FREQUENCY_AND_FLAGS_SIZE = 2; static final int TERMINAL_ADDRESS_TABLE_ADDRESS_SIZE = 3; + static final int UNIGRAM_TIMESTAMP_SIZE = 4; // With the English main dictionary as of October 2013, the size of bigram address table is // is 584KB with the block size being 4. // This is 91% of that of full address table. static final int BIGRAM_ADDRESS_TABLE_BLOCK_SIZE = 4; - static final int BIGRAM_CONTENT_COUNT = 1; + static final int BIGRAM_CONTENT_COUNT = 2; static final int BIGRAM_FREQ_CONTENT_INDEX = 0; + static final int BIGRAM_TIMESTAMP_CONTENT_INDEX = 1; static final String BIGRAM_FREQ_CONTENT_ID = "_freq"; + static final String BIGRAM_TIMESTAMP_CONTENT_ID = "_timestamp"; + static final int BIGRAM_TIMESTAMP_SIZE = 4; + static final int BIGRAM_COUNTER_SIZE = 1; + static final int BIGRAM_LEVEL_SIZE = 1; static final int SHORTCUT_CONTENT_COUNT = 1; static final int SHORTCUT_CONTENT_INDEX = 0; @@ -321,6 +331,7 @@ public final class FormatSpec { public final int mVersion; public final boolean mSupportsDynamicUpdate; public final boolean mHasTerminalId; + public final boolean mHasTimestamp; @UsedForTesting public FormatOptions(final int version) { this(version, false); @@ -328,6 +339,11 @@ public final class FormatSpec { @UsedForTesting public FormatOptions(final int version, final boolean supportsDynamicUpdate) { + this(version, supportsDynamicUpdate, false /* hasTimestamp */); + } + + public FormatOptions(final int version, final boolean supportsDynamicUpdate, + final boolean hasTimestamp) { mVersion = version; if (version < FIRST_VERSION_WITH_DYNAMIC_UPDATE && supportsDynamicUpdate) { throw new RuntimeException("Dynamic updates are only supported with versions " @@ -335,6 +351,7 @@ public final class FormatSpec { } mSupportsDynamicUpdate = supportsDynamicUpdate; mHasTerminalId = (version >= FIRST_VERSION_WITH_TERMINAL_ID); + mHasTimestamp = hasTimestamp; } } diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java index 53729075f..734223ec2 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictDecoder.java @@ -153,8 +153,12 @@ public class Ver4DictDecoder extends AbstractDictDecoder { final File contentFile = new File(mDictDirectory, mDictDirectory.getName() + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + FormatSpec.SHORTCUT_CONTENT_ID); + final File timestampsFile = new File(mDictDirectory, mDictDirectory.getName() + + FormatSpec.SHORTCUT_FILE_EXTENSION + FormatSpec.CONTENT_TABLE_FILE_SUFFIX + + FormatSpec.SHORTCUT_CONTENT_ID); mShortcutAddressTable = SparseTable.readFromFiles(lookupIndexFile, - new File[] { contentFile }, FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE); + new File[] { contentFile, timestampsFile }, + FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE); } protected static class PtNodeReader extends AbstractDictDecoder.PtNodeReader { diff --git a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java index f9dcacf77..5d5ab0462 100644 --- a/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java +++ b/java/src/com/android/inputmethod/latin/makedict/Ver4DictEncoder.java @@ -45,6 +45,7 @@ public class Ver4DictEncoder implements DictEncoder { private int mHeaderSize; private OutputStream mTrieOutStream; private OutputStream mFreqOutStream; + private OutputStream mUnigramTimestampOutStream; private OutputStream mTerminalAddressTableOutStream; private File mDictDir; private String mBaseFilename; @@ -69,16 +70,16 @@ public class Ver4DictEncoder implements DictEncoder { private final File[] mContentFiles; protected final OutputStream[] mContentOutStreams; - public SparseTableContentWriter(final String name, final int contentCount, - final int initialCapacity, final int blockSize, final File baseDir, - final String[] contentFilenames, final String[] contentIds) { + public SparseTableContentWriter(final String name, final int initialCapacity, + final int blockSize, final File baseDir, final String[] contentFilenames, + final String[] contentIds) { if (contentFilenames.length != contentIds.length) { throw new RuntimeException("The length of contentFilenames and the length of" + " contentIds are different " + contentFilenames.length + ", " + contentIds.length); } - mContentCount = contentCount; - mSparseTable = new SparseTable(initialCapacity, blockSize, contentCount); + mContentCount = contentFilenames.length; + mSparseTable = new SparseTable(initialCapacity, blockSize, mContentCount); mLookupTableFile = new File(baseDir, name + FormatSpec.LOOKUP_TABLE_FILE_SUFFIX); mAddressTableFiles = new File[mContentCount]; mContentFiles = new File[mContentCount]; @@ -113,16 +114,40 @@ public class Ver4DictEncoder implements DictEncoder { } private static class BigramContentWriter extends SparseTableContentWriter { + private final boolean mWriteTimestamp; public BigramContentWriter(final String name, final int initialCapacity, - final File baseDir) { - super(name + FormatSpec.BIGRAM_FILE_EXTENSION, FormatSpec.BIGRAM_CONTENT_COUNT, - initialCapacity, FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir, - new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION }, - new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID }); + final File baseDir, final boolean writeTimestamp) { + super(name + FormatSpec.BIGRAM_FILE_EXTENSION, initialCapacity, + FormatSpec.BIGRAM_ADDRESS_TABLE_BLOCK_SIZE, baseDir, + getContentFilenames(name, writeTimestamp), getContentIds(writeTimestamp)); + mWriteTimestamp = writeTimestamp; } - public void writeBigramsForOneWord(final int terminalId, + private static String[] getContentFilenames(final String name, + final boolean writeTimestamp) { + final String[] contentFilenames; + if (writeTimestamp) { + contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION, + name + FormatSpec.BIGRAM_FILE_EXTENSION }; + } else { + contentFilenames = new String[] { name + FormatSpec.BIGRAM_FILE_EXTENSION }; + } + return contentFilenames; + } + + private static String[] getContentIds(final boolean writeTimestamp) { + final String[] contentIds; + if (writeTimestamp) { + contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID, + FormatSpec.BIGRAM_TIMESTAMP_CONTENT_ID }; + } else { + contentIds = new String[] { FormatSpec.BIGRAM_FREQ_CONTENT_ID }; + } + return contentIds; + } + + public void writeBigramsForOneWord(final int terminalId, final int bigramCount, final Iterator<WeightedString> bigramIterator, final FusionDictionary dict) throws IOException { write(FormatSpec.BIGRAM_FREQ_CONTENT_INDEX, terminalId, @@ -130,8 +155,16 @@ public class Ver4DictEncoder implements DictEncoder { @Override public void write(final OutputStream outStream) throws IOException { writeBigramsForOneWordInternal(outStream, bigramIterator, dict); - } - }); + }}); + if (mWriteTimestamp) { + write(FormatSpec.BIGRAM_TIMESTAMP_CONTENT_INDEX, terminalId, + new SparseTableContentWriterInterface() { + @Override + public void write(final OutputStream outStream) throws IOException { + initBigramTimestampsCountersAndLevelsForOneWordInternal(outStream, + bigramCount); + }}); + } } private void writeBigramsForOneWordInternal(final OutputStream outStream, @@ -151,13 +184,26 @@ public class Ver4DictEncoder implements DictEncoder { FormatSpec.PTNODE_ATTRIBUTE_MAX_ADDRESS_SIZE); } } + + private void initBigramTimestampsCountersAndLevelsForOneWordInternal( + final OutputStream outStream, final int bigramCount) throws IOException { + for (int i = 0; i < bigramCount; ++i) { + // TODO: Figure out what initial values should be. + BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */, + FormatSpec.BIGRAM_TIMESTAMP_SIZE); + BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */, + FormatSpec.BIGRAM_COUNTER_SIZE); + BinaryDictEncoderUtils.writeUIntToStream(outStream, 0 /* value */, + FormatSpec.BIGRAM_LEVEL_SIZE); + } + } } private static class ShortcutContentWriter extends SparseTableContentWriter { public ShortcutContentWriter(final String name, final int initialCapacity, final File baseDir) { - super(name + FormatSpec.SHORTCUT_FILE_EXTENSION, FormatSpec.SHORTCUT_CONTENT_COUNT, - initialCapacity, FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir, + super(name + FormatSpec.SHORTCUT_FILE_EXTENSION, initialCapacity, + FormatSpec.SHORTCUT_ADDRESS_TABLE_BLOCK_SIZE, baseDir, new String[] { name + FormatSpec.SHORTCUT_FILE_EXTENSION }, new String[] { FormatSpec.SHORTCUT_CONTENT_ID }); } @@ -193,18 +239,20 @@ public class Ver4DictEncoder implements DictEncoder { mDictDir = new File(mDictPlacedDir, mBaseFilename); final File trieFile = new File(mDictDir, mBaseFilename + FormatSpec.TRIE_FILE_EXTENSION); final File freqFile = new File(mDictDir, mBaseFilename + FormatSpec.FREQ_FILE_EXTENSION); + final File timestampFile = new File(mDictDir, + mBaseFilename + FormatSpec.UNIGRAM_TIMESTAMP_FILE_EXTENSION); final File terminalAddressTableFile = new File(mDictDir, mBaseFilename + FormatSpec.TERMINAL_ADDRESS_TABLE_FILE_EXTENSION); if (!mDictDir.isDirectory()) { if (mDictDir.exists()) mDictDir.delete(); mDictDir.mkdirs(); } - if (!trieFile.exists()) trieFile.createNewFile(); - if (!freqFile.exists()) freqFile.createNewFile(); - if (!terminalAddressTableFile.exists()) terminalAddressTableFile.createNewFile(); mTrieOutStream = new FileOutputStream(trieFile); mFreqOutStream = new FileOutputStream(freqFile); mTerminalAddressTableOutStream = new FileOutputStream(terminalAddressTableFile); + if (formatOptions.mHasTimestamp) { + mUnigramTimestampOutStream = new FileOutputStream(timestampFile); + } } private void close() throws IOException { @@ -218,6 +266,9 @@ public class Ver4DictEncoder implements DictEncoder { if (mTerminalAddressTableOutStream != null) { mTerminalAddressTableOutStream.close(); } + if (mUnigramTimestampOutStream != null) { + mUnigramTimestampOutStream.close(); + } } finally { mTrieOutStream = null; mFreqOutStream = null; @@ -257,7 +308,11 @@ public class Ver4DictEncoder implements DictEncoder { if (MakedictLog.DBG) BinaryDictEncoderUtils.checkFlatPtNodeArrayList(flatNodes); writeTerminalData(flatNodes, terminalCount); - mBigramWriter = new BigramContentWriter(mBaseFilename, terminalCount, mDictDir); + if (formatOptions.mHasTimestamp) { + initUnigramTimestamps(terminalCount); + } + mBigramWriter = new BigramContentWriter(mBaseFilename, terminalCount, mDictDir, + formatOptions.mHasTimestamp); writeBigrams(flatNodes, dict); mShortcutWriter = new ShortcutContentWriter(mBaseFilename, terminalCount, mDictDir); writeShortcuts(flatNodes); @@ -348,7 +403,7 @@ public class Ver4DictEncoder implements DictEncoder { for (final PtNodeArray nodeArray : flatNodes) { for (final PtNode ptNode : nodeArray.mData) { if (ptNode.mBigrams != null) { - mBigramWriter.writeBigramsForOneWord(ptNode.mTerminalId, + mBigramWriter.writeBigramsForOneWord(ptNode.mTerminalId, ptNode.mBigrams.size(), ptNode.mBigrams.iterator(), dict); } } @@ -408,4 +463,11 @@ public class Ver4DictEncoder implements DictEncoder { mFreqOutStream.write(freqBuf); mTerminalAddressTableOutStream.write(terminalAddressTableBuf); } + + private void initUnigramTimestamps(final int terminalCount) throws IOException { + // Initial value of time stamps for each word is 0. + final byte[] unigramTimestampBuf = + new byte[terminalCount * FormatSpec.UNIGRAM_TIMESTAMP_SIZE]; + mUnigramTimestampOutStream.write(unigramTimestampBuf); + } } diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index 1a0fecc62..dc005bbdf 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -101,6 +101,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang // Emoji public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys"; public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id"; + public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id"; private Resources mRes; private SharedPreferences mPrefs; @@ -383,15 +384,25 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang return prefs.getString(PREF_EMOJI_RECENT_KEYS, ""); } - public static void writeEmojiCategoryLastTypedId( - final SharedPreferences prefs, final int category, final int id) { - final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category; - prefs.edit().putInt(key, id).apply(); + public static void writeLastTypedEmojiCategoryPageId( + final SharedPreferences prefs, final int categoryId, final int categoryPageId) { + final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId; + prefs.edit().putInt(key, categoryPageId).apply(); } - public static int readEmojiCategoryLastTypedId( - final SharedPreferences prefs, final int category) { - final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + category; + public static int readLastTypedEmojiCategoryPageId( + final SharedPreferences prefs, final int categoryId) { + final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId; return prefs.getInt(key, 0); } + + public static void writeLastShownEmojiCategoryId( + final SharedPreferences prefs, final int categoryId) { + prefs.edit().putInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, categoryId).apply(); + } + + public static int readLastShownEmojiCategoryId( + final SharedPreferences prefs, final int defValue) { + return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, defValue); + } } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index 8d2689a7d..faa5560e4 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -302,18 +302,19 @@ final class SuggestionStripLayoutHelper { final int countInStrip = mSuggestionsCountInStrip; setupWordViewsTextAndColor(suggestedWords, countInStrip); final TextView centerWordView = mWordViews.get(mCenterPositionInStrip); - final int stripWidth = placerView.getWidth(); - final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, stripWidth); + final int availableStripWidth = placerView.getWidth() + - placerView.getPaddingRight() - placerView.getPaddingLeft(); + final int centerWidth = getSuggestionWidth(mCenterPositionInStrip, availableStripWidth); if (getTextScaleX(centerWordView.getText(), centerWidth, centerWordView.getPaint()) < MIN_TEXT_XSCALE) { // Layout only the most relevant suggested word at the center of the suggestion strip // by consolidating all slots in the strip. mMoreSuggestionsAvailable = (suggestedWords.size() > 1); - layoutWord(mCenterPositionInStrip, stripWidth); + layoutWord(mCenterPositionInStrip, availableStripWidth - mPadding); stripView.addView(centerWordView); setLayoutWeight(centerWordView, 1.0f, ViewGroup.LayoutParams.MATCH_PARENT); if (SuggestionStripView.DBG) { - layoutDebugInfo(mCenterPositionInStrip, placerView, stripWidth); + layoutDebugInfo(mCenterPositionInStrip, placerView, availableStripWidth); } return; } @@ -328,7 +329,7 @@ final class SuggestionStripLayoutHelper { x += divider.getMeasuredWidth(); } - final int width = getSuggestionWidth(positionInStrip, stripWidth); + final int width = getSuggestionWidth(positionInStrip, availableStripWidth); final TextView wordView = layoutWord(positionInStrip, width); stripView.addView(wordView); setLayoutWeight(wordView, getSuggestionWeight(positionInStrip), @@ -373,9 +374,9 @@ final class SuggestionStripLayoutHelper { // Disable this suggestion if the suggestion is null or empty. wordView.setEnabled(!TextUtils.isEmpty(word)); final CharSequence text = getEllipsizedText(word, width, wordView.getPaint()); - final float scaleX = wordView.getTextScaleX(); + final float scaleX = getTextScaleX(word, width, wordView.getPaint()); wordView.setText(text); // TextView.setText() resets text scale x to 1.0. - wordView.setTextScaleX(scaleX); + wordView.setTextScaleX(Math.max(scaleX, MIN_TEXT_XSCALE)); return wordView; } @@ -545,8 +546,24 @@ final class SuggestionStripLayoutHelper { // Note that TextUtils.ellipsize() use text-x-scale as 1.0 if ellipsize is needed. To // get squeezed and ellipsized text, passes enlarged width (maxWidth / MIN_TEXT_XSCALE). - final CharSequence ellipsized = TextUtils.ellipsize( - text, paint, maxWidth / MIN_TEXT_XSCALE, TextUtils.TruncateAt.MIDDLE); + final float upscaledWidth = maxWidth / MIN_TEXT_XSCALE; + CharSequence ellipsized = TextUtils.ellipsize( + text, paint, upscaledWidth, TextUtils.TruncateAt.MIDDLE); + // For an unknown reason, ellipsized seems to return a text that does indeed fit inside the + // passed width according to paint.measureText, but not according to paint.getTextWidths. + // But when rendered, the text seems to actually take up as many pixels as returned by + // paint.getTextWidths, hence problem. + // To save this case, we compare the measured size of the new text, and if it's too much, + // try it again removing the difference. This may still give a text too long by one or + // two pixels so we take an additional 2 pixels cushion and call it a day. + // TODO: figure out why getTextWidths and measureText don't agree with each other, and + // remove the following code. + final float ellipsizedTextWidth = getTextWidth(ellipsized, paint); + if (upscaledWidth <= ellipsizedTextWidth) { + ellipsized = TextUtils.ellipsize( + text, paint, upscaledWidth - (ellipsizedTextWidth - upscaledWidth) - 2, + TextUtils.TruncateAt.MIDDLE); + } paint.setTextScaleX(MIN_TEXT_XSCALE); return ellipsized; } diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java index 121aecf0f..a36548392 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -48,6 +48,16 @@ public final class StringUtils { return text.codePointCount(0, text.length()); } + public static String newSingleCodePointString(int codePoint) { + if (Character.charCount(codePoint) == 1) { + // Optimization: avoid creating an temporary array for characters that are + // represented by a single char value + return String.valueOf((char) codePoint); + } + // For surrogate pair + return new String(Character.toChars(codePoint)); + } + public static boolean containsInArray(final String text, final String[] array) { for (final String element : array) { if (text.equals(element)) return true; |