diff options
Diffstat (limited to 'java/src')
8 files changed, 337 insertions, 434 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index cf89567f8..a9856e121 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -781,9 +781,6 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { public void cancelAllMessages() { mDrawingHandler.cancelAllMessages(); - if (mPreviewPlacerView != null) { - mPreviewPlacerView.cancelAllMessages(); - } } private TextView getKeyPreviewText(final int pointerId) { @@ -829,7 +826,8 @@ public class KeyboardView extends View implements PointerTracker.DrawingProxy { } final int[] viewOrigin = new int[2]; getLocationInWindow(viewOrigin); - mPreviewPlacerView.setOrigin(viewOrigin[0], viewOrigin[1]); + mPreviewPlacerView.setKeyboardViewGeometry( + viewOrigin[0], viewOrigin[1], getWidth(), getHeight()); final View rootView = getRootView(); if (rootView == null) { Log.w(TAG, "Cannot find root view"); diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index d6c567ef7..ec8f65994 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -560,10 +560,13 @@ public class PointerTracker implements PointerTrackerQueue.Element { return (sPointerTrackerQueue == null) ? 1 : sPointerTrackerQueue.size(); } - private void mayStartBatchInput() { + private void mayStartBatchInput(final Key key) { if (sInGesture || !mGestureStrokeWithPreviewPoints.isStartOfAGesture()) { return; } + if (key == null || !Character.isLetter(key.mCode)) { + return; + } if (DEBUG_LISTENER) { Log.d(TAG, "onStartBatchInput"); } @@ -573,18 +576,20 @@ public class PointerTracker implements PointerTrackerQueue.Element { mDrawingProxy.showGesturePreviewTrail(this, isOldestTracker); } - private void updateBatchInput(final long eventTime) { - synchronized (sAggregratedPointers) { - mGestureStrokeWithPreviewPoints.appendIncrementalBatchPoints(sAggregratedPointers); - final int size = sAggregratedPointers.getPointerSize(); - if (size > sLastRecognitionPointSize - && GestureStroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) { - sLastRecognitionPointSize = size; - sLastRecognitionTime = eventTime; - if (DEBUG_LISTENER) { - Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size); + private void mayUpdateBatchInput(final long eventTime, final Key key) { + if (key != null) { + synchronized (sAggregratedPointers) { + mGestureStrokeWithPreviewPoints.appendIncrementalBatchPoints(sAggregratedPointers); + final int size = sAggregratedPointers.getPointerSize(); + if (size > sLastRecognitionPointSize + && GestureStroke.hasRecognitionTimePast(eventTime, sLastRecognitionTime)) { + sLastRecognitionPointSize = size; + sLastRecognitionTime = eventTime; + if (DEBUG_LISTENER) { + Log.d(TAG, "onUpdateBatchInput: batchPoints=" + size); + } + mListener.onUpdateBatchInput(sAggregratedPointers); } - mListener.onUpdateBatchInput(sAggregratedPointers); } } final boolean isOldestTracker = sPointerTrackerQueue.getOldestElement() == this; @@ -742,9 +747,9 @@ public class PointerTracker implements PointerTrackerQueue.Element { final int gestureTime = (int)(eventTime - sGestureFirstDownTime); if (mIsDetectingGesture) { mGestureStrokeWithPreviewPoints.addPoint(x, y, gestureTime, isMajorEvent); - mayStartBatchInput(); - if (sInGesture && key != null) { - updateBatchInput(eventTime); + mayStartBatchInput(key); + if (sInGesture) { + mayUpdateBatchInput(eventTime, key); } } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java index 15170e040..72be7fc59 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PreviewPlacerView.java @@ -40,6 +40,10 @@ import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.StaticInnerHandlerWrapper; public class PreviewPlacerView extends RelativeLayout { + // The height of extra area above the keyboard to draw gesture trails. + // Proportional to the keyboard height. + private static final float EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO = 0.25f; + private final int mGestureFloatingPreviewTextColor; private final int mGestureFloatingPreviewTextOffset; private final int mGestureFloatingPreviewColor; @@ -47,14 +51,17 @@ public class PreviewPlacerView extends RelativeLayout { private final float mGestureFloatingPreviewVerticalPadding; private final float mGestureFloatingPreviewRoundRadius; - private int mXOrigin; - private int mYOrigin; + private int mKeyboardViewOriginX; + private int mKeyboardViewOriginY; private final SparseArray<GesturePreviewTrail> mGesturePreviewTrails = CollectionUtils.newSparseArray(); private final Params mGesturePreviewTrailParams; private final Paint mGesturePaint; private boolean mDrawsGesturePreviewTrail; + private int mOffscreenWidth; + private int mOffscreenHeight; + private int mOffscreenOffsetY; private Bitmap mOffscreenBuffer; private final Canvas mOffscreenCanvas = new Canvas(); private final Rect mOffscreenDirtyRect = new Rect(); @@ -101,29 +108,17 @@ public class PreviewPlacerView extends RelativeLayout { } } - private void cancelDismissGestureFloatingPreviewText() { - removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT); - } - public void dismissGestureFloatingPreviewText() { - cancelDismissGestureFloatingPreviewText(); + removeMessages(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT); sendMessageDelayed(obtainMessage(MSG_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT), mGestureFloatingPreviewTextLingerTimeout); } - private void cancelUpdateGestureTrailPreview() { - removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL); - } - public void postUpdateGestureTrailPreview() { - cancelUpdateGestureTrailPreview(); + removeMessages(MSG_UPDATE_GESTURE_PREVIEW_TRAIL); sendMessageDelayed(obtainMessage(MSG_UPDATE_GESTURE_PREVIEW_TRAIL), mGesturePreviewTrailParams.mUpdateInterval); } - - public void cancelAllMessages() { - cancelUpdateGestureTrailPreview(); - } } public PreviewPlacerView(final Context context, final AttributeSet attrs) { @@ -177,9 +172,12 @@ public class PreviewPlacerView extends RelativeLayout { setLayerType(LAYER_TYPE_HARDWARE, layerPaint); } - public void setOrigin(final int x, final int y) { - mXOrigin = x; - mYOrigin = y; + public void setKeyboardViewGeometry(final int x, final int y, final int w, final int h) { + mKeyboardViewOriginX = x; + mKeyboardViewOriginY = y; + mOffscreenOffsetY = (int)(h * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); + mOffscreenWidth = w; + mOffscreenHeight = mOffscreenOffsetY + h; } public void setGesturePreviewMode(final boolean drawsGesturePreviewTrail, @@ -216,45 +214,42 @@ public class PreviewPlacerView extends RelativeLayout { @Override protected void onDetachedFromWindow() { + freeOffscreenBuffer(); + } + + private void freeOffscreenBuffer() { if (mOffscreenBuffer != null) { mOffscreenBuffer.recycle(); mOffscreenBuffer = null; } } + private void mayAllocateOffscreenBuffer() { + if (mOffscreenBuffer != null && mOffscreenBuffer.getWidth() == mOffscreenWidth + && mOffscreenBuffer.getHeight() == mOffscreenHeight) { + return; + } + freeOffscreenBuffer(); + mOffscreenBuffer = Bitmap.createBitmap( + mOffscreenWidth, mOffscreenHeight, Bitmap.Config.ARGB_8888); + mOffscreenCanvas.setBitmap(mOffscreenBuffer); + } + @Override public void onDraw(final Canvas canvas) { super.onDraw(canvas); - canvas.translate(mXOrigin, mYOrigin); if (mDrawsGesturePreviewTrail) { - if (mOffscreenBuffer == null) { - mOffscreenBuffer = Bitmap.createBitmap( - getWidth(), getHeight(), Bitmap.Config.ARGB_8888); - mOffscreenCanvas.setBitmap(mOffscreenBuffer); - } - if (!mOffscreenDirtyRect.isEmpty()) { - // Clear previous dirty rectangle. - mGesturePaint.setColor(Color.TRANSPARENT); - mGesturePaint.setStyle(Paint.Style.FILL); - mOffscreenCanvas.drawRect(mOffscreenDirtyRect, mGesturePaint); - mOffscreenDirtyRect.setEmpty(); - } - boolean needsUpdatingGesturePreviewTrail = false; - synchronized (mGesturePreviewTrails) { - // Trails count == fingers count that have ever been active. - final int trailsCount = mGesturePreviewTrails.size(); - for (int index = 0; index < trailsCount; index++) { - final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index); - needsUpdatingGesturePreviewTrail |= - trail.drawGestureTrail(mOffscreenCanvas, mGesturePaint, - mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams); - // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail. - mOffscreenDirtyRect.union(mGesturePreviewTrailBoundsRect); - } - } + mayAllocateOffscreenBuffer(); + // Draw gesture trails to offscreen buffer. + final boolean needsUpdatingGesturePreviewTrail = drawGestureTrails( + mOffscreenCanvas, mGesturePaint, mOffscreenDirtyRect); + // Transfer offscreen buffer to screen. if (!mOffscreenDirtyRect.isEmpty()) { + final int offsetY = mKeyboardViewOriginY - mOffscreenOffsetY; + canvas.translate(mKeyboardViewOriginX, offsetY); canvas.drawBitmap(mOffscreenBuffer, mOffscreenDirtyRect, mOffscreenDirtyRect, mGesturePaint); + canvas.translate(-mKeyboardViewOriginX, -offsetY); // Note: Defer clearing the dirty rectangle here because we will get cleared // rectangle on the canvas. } @@ -263,9 +258,49 @@ public class PreviewPlacerView extends RelativeLayout { } } if (mDrawsGestureFloatingPreviewText) { + canvas.translate(mKeyboardViewOriginX, mKeyboardViewOriginY); drawGestureFloatingPreviewText(canvas, mGestureFloatingPreviewText); + canvas.translate(-mKeyboardViewOriginX, -mKeyboardViewOriginY); + } + } + + private boolean drawGestureTrails(final Canvas offscreenCanvas, final Paint paint, + final Rect dirtyRect) { + // Clear previous dirty rectangle. + if (!dirtyRect.isEmpty()) { + paint.setColor(Color.TRANSPARENT); + paint.setStyle(Paint.Style.FILL); + offscreenCanvas.drawRect(dirtyRect, paint); + } + dirtyRect.setEmpty(); + + // Draw gesture trails to offscreen buffer. + offscreenCanvas.translate(0, mOffscreenOffsetY); + boolean needsUpdatingGesturePreviewTrail = false; + synchronized (mGesturePreviewTrails) { + // Trails count == fingers count that have ever been active. + final int trailsCount = mGesturePreviewTrails.size(); + for (int index = 0; index < trailsCount; index++) { + final GesturePreviewTrail trail = mGesturePreviewTrails.valueAt(index); + needsUpdatingGesturePreviewTrail |= + trail.drawGestureTrail(offscreenCanvas, paint, + mGesturePreviewTrailBoundsRect, mGesturePreviewTrailParams); + // {@link #mGesturePreviewTrailBoundsRect} has bounding box of the trail. + dirtyRect.union(mGesturePreviewTrailBoundsRect); + } } - canvas.translate(-mXOrigin, -mYOrigin); + offscreenCanvas.translate(0, -mOffscreenOffsetY); + + // Clip dirty rectangle with offscreen buffer width/height. + dirtyRect.offset(0, mOffscreenOffsetY); + clipRect(dirtyRect, 0, 0, mOffscreenWidth, mOffscreenHeight); + return needsUpdatingGesturePreviewTrail; + } + + private static void clipRect(final Rect out, final int left, final int top, final int right, + final int bottom) { + out.set(Math.max(out.left, left), Math.max(out.top, top), Math.min(out.right, right), + Math.min(out.bottom, bottom)); } public void setGestureFloatingPreviewText(final String gestureFloatingPreviewText) { @@ -278,10 +313,6 @@ public class PreviewPlacerView extends RelativeLayout { mDrawingHandler.dismissGestureFloatingPreviewText(); } - public void cancelAllMessages() { - mDrawingHandler.cancelAllMessages(); - } - private void drawGestureFloatingPreviewText(final Canvas canvas, final String gestureFloatingPreviewText) { if (TextUtils.isEmpty(gestureFloatingPreviewText)) { diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java index 05255a6b3..519165dd0 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java @@ -115,11 +115,10 @@ public class UserHistoryDictIOUtils { 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) { @@ -132,16 +131,18 @@ public class UserHistoryDictIOUtils { */ /* packages for test */ static FusionDictionary constructFusionDictionary( final BigramDictionaryInterface dict, final UserHistoryDictionaryBigramList bigrams) { - final FusionDictionary fusionDict = new FusionDictionary(new Node(), - new FusionDictionary.DictionaryOptions( - new HashMap<String,String>(), false, false)); - + new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false, + false)); + int profTotal = 0; for (final String word1 : bigrams.keySet()) { final HashMap<String, Byte> 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)); @@ -149,17 +150,22 @@ public class UserHistoryDictIOUtils { 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; } @@ -171,7 +177,6 @@ public class UserHistoryDictIOUtils { final Map<Integer, String> unigrams = CollectionUtils.newTreeMap(); final Map<Integer, Integer> frequencies = CollectionUtils.newTreeMap(); final Map<Integer, ArrayList<PendingAttribute>> bigrams = CollectionUtils.newTreeMap(); - try { BinaryDictIOUtils.readUnigramsAndBigramsBinary(buffer, unigrams, frequencies, bigrams); @@ -189,14 +194,11 @@ public class UserHistoryDictIOUtils { /* package for test */ static void addWordsFromWordMap(final Map<Integer, String> unigrams, final Map<Integer, Integer> frequencies, final Map<Integer, ArrayList<PendingAttribute>> bigrams, final OnAddWordListener to) { - for (Map.Entry<Integer, String> entry : unigrams.entrySet()) { final String word1 = entry.getValue(); final int unigramFrequency = frequencies.get(entry.getKey()); to.setUnigram(word1, null, unigramFrequency); - final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey()); - if (attrList != null) { for (final PendingAttribute attr : attrList) { to.setBigram(word1, unigrams.get(attr.mAddress), diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java index e03af649c..202aa642f 100644 --- a/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/UserHistoryDictionary.java @@ -16,24 +16,25 @@ package com.android.inputmethod.latin; -import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.database.sqlite.SQLiteQueryBuilder; import android.os.AsyncTask; -import android.provider.BaseColumns; import android.util.Log; 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 java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; import java.lang.ref.SoftReference; import java.util.ArrayList; -import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; @@ -43,45 +44,27 @@ import java.util.concurrent.locks.ReentrantLock; */ public class UserHistoryDictionary extends ExpandableDictionary { private static final String TAG = UserHistoryDictionary.class.getSimpleName(); + private static final String NAME = UserHistoryDictionary.class.getSimpleName(); public static final boolean DBG_SAVE_RESTORE = false; public static final boolean DBG_STRESS_TEST = false; public static final boolean DBG_ALWAYS_WRITE = false; public static final boolean PROFILE_SAVE_RESTORE = LatinImeLogger.sDBG; + private static final FormatOptions VERSION3 = new FormatOptions(3, + true /* supportsDynamicUpdate */); + /** Any pair being typed or picked */ private static final int FREQUENCY_FOR_TYPED = 2; /** Maximum number of pairs. Pruning will start when databases goes above this number. */ - public static final int sMaxHistoryBigrams = 10000; + public static final int MAX_HISTORY_BIGRAMS = 10000; /** * When it hits maximum bigram pair, it will delete until you are left with * only (sMaxHistoryBigrams - sDeleteHistoryBigrams) pairs. * Do not keep this number small to avoid deleting too often. */ - public static final int sDeleteHistoryBigrams = 1000; - - /** - * Database version should increase if the database structure changes - */ - private static final int DATABASE_VERSION = 1; - - private static final String DATABASE_NAME = "userbigram_dict.db"; - - /** Name of the words table in the database */ - private static final String MAIN_TABLE_NAME = "main"; - // TODO: Consume less space by using a unique id for locale instead of the whole - // 2-5 character string. - private static final String MAIN_COLUMN_ID = BaseColumns._ID; - private static final String MAIN_COLUMN_WORD1 = "word1"; - private static final String MAIN_COLUMN_WORD2 = "word2"; - private static final String MAIN_COLUMN_LOCALE = "locale"; - - /** Name of the frequency table in the database */ - private static final String FREQ_TABLE_NAME = "frequency"; - private static final String FREQ_COLUMN_ID = BaseColumns._ID; - private static final String FREQ_COLUMN_PAIR_ID = "pair_id"; - private static final String COLUMN_FORGETTING_CURVE_VALUE = "freq"; + public static final int DELETE_HISTORY_BIGRAMS = 1000; /** Locale for which this user history dictionary is storing words */ private final String mLocale; @@ -91,29 +74,13 @@ public class UserHistoryDictionary extends ExpandableDictionary { private final ReentrantLock mBigramListLock = new ReentrantLock(); private final SharedPreferences mPrefs; - private final static HashMap<String, String> sDictProjectionMap; - private final static ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> - sLangDictCache = CollectionUtils.newConcurrentHashMap(); - - static { - sDictProjectionMap = CollectionUtils.newHashMap(); - sDictProjectionMap.put(MAIN_COLUMN_ID, MAIN_COLUMN_ID); - sDictProjectionMap.put(MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD1); - sDictProjectionMap.put(MAIN_COLUMN_WORD2, MAIN_COLUMN_WORD2); - sDictProjectionMap.put(MAIN_COLUMN_LOCALE, MAIN_COLUMN_LOCALE); - - sDictProjectionMap.put(FREQ_COLUMN_ID, FREQ_COLUMN_ID); - sDictProjectionMap.put(FREQ_COLUMN_PAIR_ID, FREQ_COLUMN_PAIR_ID); - sDictProjectionMap.put(COLUMN_FORGETTING_CURVE_VALUE, COLUMN_FORGETTING_CURVE_VALUE); - } - - private static DatabaseHelper sOpenHelper = null; + // Should always be false except when we use this class for test + /* package for test */ boolean isTest = false; - public String getLocale() { - return mLocale; - } + private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> + sLangDictCache = CollectionUtils.newConcurrentHashMap(); - public synchronized static UserHistoryDictionary getInstance( + public static synchronized UserHistoryDictionary getInstance( final Context context, final String locale, final SharedPreferences sp) { if (sLangDictCache.containsKey(locale)) { final SoftReference<UserHistoryDictionary> ref = sLangDictCache.get(locale); @@ -136,9 +103,6 @@ public class UserHistoryDictionary extends ExpandableDictionary { super(context, Dictionary.TYPE_USER_HISTORY); mLocale = locale; mPrefs = sp; - if (sOpenHelper == null) { - sOpenHelper = new DatabaseHelper(getContext()); - } if (mLocale != null && mLocale.length() > 1) { loadDictionary(); } @@ -190,6 +154,7 @@ public class UserHistoryDictionary extends ExpandableDictionary { try { super.addWord( word2, null /* the "shortcut" parameter is null */, FREQUENCY_FOR_TYPED); + mBigramList.addBigram(null, word2, (byte)FREQUENCY_FOR_TYPED); // Do not insert a word as a bigram of itself if (word2.equals(word1)) { return 0; @@ -227,11 +192,8 @@ public class UserHistoryDictionary extends ExpandableDictionary { * Schedules a background thread to write any pending words to the database. */ private void flushPendingWrites() { - if (mBigramListLock.isLocked()) { - return; - } // Create a background thread to write the pending entries - new UpdateDbTask(sOpenHelper, mBigramList, mLocale, this, mPrefs).execute(); + new UpdateBinaryTask(mBigramList, mLocale, this, mPrefs, getContext()).execute(); } @Override @@ -245,6 +207,8 @@ public class UserHistoryDictionary extends ExpandableDictionary { } } + private int profTotal; + private void loadDictionaryAsyncLocked() { if (DBG_STRESS_TEST) { try { @@ -257,343 +221,181 @@ public class UserHistoryDictionary extends ExpandableDictionary { final long last = SettingsValues.getLastUserHistoryWriteTime(mPrefs, mLocale); final boolean initializing = last == 0; final long now = System.currentTimeMillis(); - // Load the words that correspond to the current input locale - final Cursor cursor = query(MAIN_COLUMN_LOCALE + "=?", new String[] { mLocale }); - if (null == cursor) return; - try { - // TODO: Call SQLiteDataBase.beginTransaction / SQLiteDataBase.endTransaction - if (cursor.moveToFirst()) { - final int word1Index = cursor.getColumnIndex(MAIN_COLUMN_WORD1); - final int word2Index = cursor.getColumnIndex(MAIN_COLUMN_WORD2); - final int fcIndex = cursor.getColumnIndex(COLUMN_FORGETTING_CURVE_VALUE); - while (!cursor.isAfterLast()) { - final String word1 = cursor.getString(word1Index); - final String word2 = cursor.getString(word2Index); - final int fc = cursor.getInt(fcIndex); + profTotal = 0; + final String fileName = NAME + "." + mLocale + ".dict"; + final ExpandableDictionary dictionary = this; + final OnAddWordListener listener = new OnAddWordListener() { + @Override + public void setUnigram(String word, String shortcutTarget, int frequency) { + profTotal++; + if (DBG_SAVE_RESTORE) { + Log.d(TAG, "load unigram: " + word + "," + frequency); + } + dictionary.addWord(word, shortcutTarget, frequency); + mBigramList.addBigram(null, word, (byte)frequency); + } + + @Override + public void setBigram(String word1, String word2, int frequency) { + if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH + && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) { + profTotal++; if (DBG_SAVE_RESTORE) { - Log.d(TAG, "--- Load user history: " + word1 + ", " + word2 + "," - + mLocale + "," + this); - } - // Safeguard against adding really long words. Stack may overflow due - // to recursive lookup - if (null == word1) { - super.addWord(word2, null /* shortcut */, fc); - } else if (word1.length() < BinaryDictionary.MAX_WORD_LENGTH - && word2.length() < BinaryDictionary.MAX_WORD_LENGTH) { - super.setBigramAndGetFrequency( - word1, word2, initializing ? new ForgettingCurveParams(true) - : new ForgettingCurveParams(fc, now, last)); + Log.d(TAG, "load bigram: " + word1 + "," + word2 + "," + frequency); } - mBigramList.addBigram(word1, word2, (byte)fc); - cursor.moveToNext(); + dictionary.setBigramAndGetFrequency( + word1, word2, initializing ? new ForgettingCurveParams(true) + : new ForgettingCurveParams(frequency, now, last)); } + mBigramList.addBigram(word1, word2, (byte)frequency); } + }; + + // Load the dictionary from binary file + FileInputStream inStream = null; + try { + final File file = new File(getContext().getFilesDir(), fileName); + final byte[] buffer = new byte[(int)file.length()]; + inStream = new FileInputStream(file); + inStream.read(buffer); + UserHistoryDictIOUtils.readDictionaryBinary( + new UserHistoryDictIOUtils.ByteArrayWrapper(buffer), listener); + } catch (FileNotFoundException e) { + Log.e(TAG, "when loading: file not found" + e); + } catch (IOException e) { + Log.e(TAG, "IOException when open bytebuffer: " + e); } finally { - cursor.close(); + if (inStream != null) { + try { + inStream.close(); + } catch (IOException e) { + // do nothing + } + } if (PROFILE_SAVE_RESTORE) { final long diff = System.currentTimeMillis() - now; - Log.w(TAG, "PROF: Load User HistoryDictionary: " - + mLocale + ", " + diff + "ms."); + Log.d(TAG, "PROF: Load UserHistoryDictionary: " + + mLocale + ", " + diff + "ms. load " + profTotal + "entries."); } } } /** - * Query the database + * Async task to write pending words to the binarydicts. */ - private static Cursor query(String selection, String[] selectionArgs) { - SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); - - // main INNER JOIN frequency ON (main._id=freq.pair_id) - qb.setTables(MAIN_TABLE_NAME + " INNER JOIN " + FREQ_TABLE_NAME + " ON (" - + MAIN_TABLE_NAME + "." + MAIN_COLUMN_ID + "=" + FREQ_TABLE_NAME + "." - + FREQ_COLUMN_PAIR_ID +")"); - - qb.setProjectionMap(sDictProjectionMap); - - // Get the database and run the query - try { - SQLiteDatabase db = sOpenHelper.getReadableDatabase(); - Cursor c = qb.query(db, - new String[] { - MAIN_COLUMN_WORD1, MAIN_COLUMN_WORD2, COLUMN_FORGETTING_CURVE_VALUE }, - selection, selectionArgs, null, null, null); - return c; - } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) { - // Can't open the database : presumably we can't access storage. That may happen - // when the device is wedged; do a best effort to still start the keyboard. - return null; - } - } - - /** - * This class helps open, create, and upgrade the database file. - */ - private static class DatabaseHelper extends SQLiteOpenHelper { - - DatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL("PRAGMA foreign_keys = ON;"); - db.execSQL("CREATE TABLE " + MAIN_TABLE_NAME + " (" - + MAIN_COLUMN_ID + " INTEGER PRIMARY KEY," - + MAIN_COLUMN_WORD1 + " TEXT," - + MAIN_COLUMN_WORD2 + " TEXT," - + MAIN_COLUMN_LOCALE + " TEXT" - + ");"); - db.execSQL("CREATE TABLE " + FREQ_TABLE_NAME + " (" - + FREQ_COLUMN_ID + " INTEGER PRIMARY KEY," - + FREQ_COLUMN_PAIR_ID + " INTEGER," - + COLUMN_FORGETTING_CURVE_VALUE + " INTEGER," - + "FOREIGN KEY(" + FREQ_COLUMN_PAIR_ID + ") REFERENCES " + MAIN_TABLE_NAME - + "(" + MAIN_COLUMN_ID + ")" + " ON DELETE CASCADE" - + ");"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - Log.w(TAG, "Upgrading database from version " + oldVersion + " to " - + newVersion + ", which will destroy all old data"); - db.execSQL("DROP TABLE IF EXISTS " + MAIN_TABLE_NAME); - db.execSQL("DROP TABLE IF EXISTS " + FREQ_TABLE_NAME); - onCreate(db); - } - } - - /** - * Async task to write pending words to the database so that it stays in sync with - * the in-memory trie. - */ - private static class UpdateDbTask extends AsyncTask<Void, Void, Void> { + private static class UpdateBinaryTask extends AsyncTask<Void, Void, Void> + implements BigramDictionaryInterface { private final UserHistoryDictionaryBigramList mBigramList; - private final DatabaseHelper mDbHelper; + private final boolean mAddLevel0Bigrams; private final String mLocale; private final UserHistoryDictionary mUserHistoryDictionary; private final SharedPreferences mPrefs; + private final Context mContext; - public UpdateDbTask( - DatabaseHelper openHelper, UserHistoryDictionaryBigramList pendingWrites, - String locale, UserHistoryDictionary dict, SharedPreferences prefs) { + public UpdateBinaryTask(UserHistoryDictionaryBigramList pendingWrites, String locale, + UserHistoryDictionary dict, SharedPreferences prefs, Context context) { mBigramList = pendingWrites; mLocale = locale; - mDbHelper = openHelper; mUserHistoryDictionary = dict; mPrefs = prefs; - } - - /** Prune any old data if the database is getting too big. */ - private static void checkPruneData(SQLiteDatabase db) { - db.execSQL("PRAGMA foreign_keys = ON;"); - Cursor c = db.query(FREQ_TABLE_NAME, new String[] { FREQ_COLUMN_PAIR_ID }, - null, null, null, null, null); - try { - int totalRowCount = c.getCount(); - // prune out old data if we have too much data - if (totalRowCount > sMaxHistoryBigrams) { - int numDeleteRows = (totalRowCount - sMaxHistoryBigrams) - + sDeleteHistoryBigrams; - int pairIdColumnId = c.getColumnIndex(FREQ_COLUMN_PAIR_ID); - c.moveToFirst(); - int count = 0; - while (count < numDeleteRows && !c.isAfterLast()) { - String pairId = c.getString(pairIdColumnId); - // Deleting from MAIN table will delete the frequencies - // due to FOREIGN KEY .. ON DELETE CASCADE - db.delete(MAIN_TABLE_NAME, MAIN_COLUMN_ID + "=?", - new String[] { pairId }); - c.moveToNext(); - count++; - } - } - } finally { - c.close(); - } + mContext = context; + mAddLevel0Bigrams = mBigramList.size() <= MAX_HISTORY_BIGRAMS; } @Override protected Void doInBackground(Void... v) { - SQLiteDatabase db = null; - if (mUserHistoryDictionary.mBigramListLock.tryLock()) { + if (mUserHistoryDictionary.isTest) { + // If isTest == true, wait until the lock is released. + mUserHistoryDictionary.mBigramListLock.lock(); try { - try { - db = mDbHelper.getWritableDatabase(); - } catch (android.database.sqlite.SQLiteCantOpenDatabaseException e) { - // If we can't open the db, don't do anything. Exit through the next test - // for non-nullity of the db variable. - } - if (null == db) { - // Not much we can do. Just exit. - return null; - } - db.beginTransaction(); - return doLoadTaskLocked(db); + doWriteTaskLocked(); } finally { - if (db != null) { - db.endTransaction(); - } mUserHistoryDictionary.mBigramListLock.unlock(); } + } else if (mUserHistoryDictionary.mBigramListLock.tryLock()) { + doWriteTaskLocked(); } return null; } - private Void doLoadTaskLocked(SQLiteDatabase db) { + private void doWriteTaskLocked() { if (DBG_STRESS_TEST) { try { Log.w(TAG, "Start stress in closing: " + mLocale); Thread.sleep(15000); Log.w(TAG, "End stress in closing"); } catch (InterruptedException e) { + Log.e(TAG, "In stress test: " + e); } } + final long now = PROFILE_SAVE_RESTORE ? System.currentTimeMillis() : 0; - int profTotal = 0; - int profInsert = 0; - int profDelete = 0; - db.execSQL("PRAGMA foreign_keys = ON;"); - final boolean addLevel0Bigram = mBigramList.size() <= sMaxHistoryBigrams; - - // Write all the entries to the db - for (String word1 : mBigramList.keySet()) { - final HashMap<String, Byte> word1Bigrams = mBigramList.getBigrams(word1); - for (String word2 : word1Bigrams.keySet()) { - if (PROFILE_SAVE_RESTORE) { - ++profTotal; - } - // Get new frequency. Do not insert unigrams/bigrams which freq is "-1". - final int freq; // -1, or 0~255 - if (word1 == null) { // unigram - freq = FREQUENCY_FOR_TYPED; - final byte prevFc = word1Bigrams.get(word2); - if (prevFc == FREQUENCY_FOR_TYPED) { - // No need to update since we found no changes for this entry. - // Just skip to the next entry. - if (DBG_SAVE_RESTORE) { - Log.d(TAG, "Skip update user history: " + word1 + "," + word2 - + "," + prevFc); - } - if (!DBG_ALWAYS_WRITE) { - continue; - } - } - } else { // bigram - final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2); - if (nw != null) { - final ForgettingCurveParams fcp = nw.getFcParams(); - final byte prevFc = word1Bigrams.get(word2); - final byte fc = (byte)fcp.getFc(); - final boolean isValid = fcp.isValid(); - if (prevFc > 0 && prevFc == fc) { - // No need to update since we found no changes for this entry. - // Just skip to the next entry. - if (DBG_SAVE_RESTORE) { - Log.d(TAG, "Skip update user history: " + word1 + "," - + word2 + "," + prevFc); - } - if (!DBG_ALWAYS_WRITE) { - continue; - } else { - freq = fc; - } - } else if (UserHistoryForgettingCurveUtils. - needsToSave(fc, isValid, addLevel0Bigram)) { - freq = fc; - } else { - // Delete this entry - freq = -1; - } - } else { - // Delete this entry - freq = -1; - } - } - // TODO: this process of making a text search for each pair each time - // is terribly inefficient. Optimize this. - // Find pair id - Cursor c = null; + final String fileName = NAME + "." + mLocale + ".dict"; + final File file = new File(mContext.getFilesDir(), fileName); + FileOutputStream out = null; + + try { + out = new FileOutputStream(file); + UserHistoryDictIOUtils.writeDictionaryBinary(out, this, mBigramList, VERSION3); + out.flush(); + out.close(); + } catch (IOException e) { + Log.e(TAG, "IO Exception while writing file: " + e); + } finally { + if (out != null) { try { - if (null != word1) { - c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID }, - MAIN_COLUMN_WORD1 + "=? AND " + MAIN_COLUMN_WORD2 + "=? AND " - + MAIN_COLUMN_LOCALE + "=?", - new String[] { word1, word2, mLocale }, null, null, - null); - } else { - c = db.query(MAIN_TABLE_NAME, new String[] { MAIN_COLUMN_ID }, - MAIN_COLUMN_WORD1 + " IS NULL AND " + MAIN_COLUMN_WORD2 - + "=? AND " + MAIN_COLUMN_LOCALE + "=?", - new String[] { word2, mLocale }, null, null, null); - } - - final int pairId; - if (c.moveToFirst()) { - if (PROFILE_SAVE_RESTORE) { - ++profDelete; - } - // Delete existing pair - pairId = c.getInt(c.getColumnIndex(MAIN_COLUMN_ID)); - db.delete(FREQ_TABLE_NAME, FREQ_COLUMN_PAIR_ID + "=?", - new String[] { Integer.toString(pairId) }); - } else { - // Create new pair - Long pairIdLong = db.insert(MAIN_TABLE_NAME, null, - getContentValues(word1, word2, mLocale)); - pairId = pairIdLong.intValue(); - } - // Eliminate freq == 0 because that word is profanity. - if (freq > 0) { - if (PROFILE_SAVE_RESTORE) { - ++profInsert; - } - if (DBG_SAVE_RESTORE) { - Log.d(TAG, "--- Save user history: " + word1 + ", " + word2 - + mLocale + "," + this); - } - // Insert new frequency - db.insert(FREQ_TABLE_NAME, null, - getFrequencyContentValues(pairId, freq)); - // Update an existing bigram entry in mBigramList too in order to - // synchronize the SQL DB and mBigramList. - mBigramList.updateBigram(word1, word2, (byte)freq); - } - } finally { - if (c != null) { - c.close(); - } + out.close(); + } catch (IOException e) { + // ignore } } } - checkPruneData(db); - // Save the timestamp after we finish writing the SQL DB. + // Save the timestamp after we finish writing the binary dictionary. SettingsValues.setLastUserHistoryWriteTime(mPrefs, mLocale); if (PROFILE_SAVE_RESTORE) { final long diff = System.currentTimeMillis() - now; - Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", "+ diff - + "ms. Total: " + profTotal + ". Insert: " + profInsert + ". Delete: " - + profDelete); + Log.w(TAG, "PROF: Write User HistoryDictionary: " + mLocale + ", " + diff + "ms."); } - db.setTransactionSuccessful(); - return null; } - private static ContentValues getContentValues(String word1, String word2, String locale) { - ContentValues values = new ContentValues(3); - values.put(MAIN_COLUMN_WORD1, word1); - values.put(MAIN_COLUMN_WORD2, word2); - values.put(MAIN_COLUMN_LOCALE, locale); - return values; + @Override + public int getFrequency(String word1, String word2) { + final int freq; + if (word1 == null) { // unigram + freq = FREQUENCY_FOR_TYPED; + final byte prevFc = mBigramList.getBigrams(word1).get(word2); + } else { // bigram + final NextWord nw = mUserHistoryDictionary.getBigramWord(word1, word2); + if (nw != null) { + final ForgettingCurveParams fcp = nw.getFcParams(); + final byte prevFc = mBigramList.getBigrams(word1).get(word2); + final byte fc = fcp.getFc(); + final boolean isValid = fcp.isValid(); + if (prevFc > 0 && prevFc == fc) { + freq = ((int)fc) & 0xFF; + } else if (UserHistoryForgettingCurveUtils. + needsToSave(fc, isValid, mAddLevel0Bigrams)) { + freq = ((int)fc) & 0xFF; + } else { + // Delete this entry + freq = -1; + } + } else { + // Delete this entry + freq = -1; + } + } + return freq; } + } - private static ContentValues getFrequencyContentValues(int pairId, int frequency) { - ContentValues values = new ContentValues(2); - values.put(FREQ_COLUMN_PAIR_ID, pairId); - values.put(COLUMN_FORGETTING_CURVE_VALUE, frequency); - return values; + void forceAddWordForTest(final String word1, final String word2, final boolean isValid) { + mBigramListLock.lock(); + try { + addToUserHistory(word1, word2, isValid); + } finally { + mBigramListLock.unlock(); } } - } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java index 19da5124a..a1606db60 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java @@ -77,7 +77,10 @@ public class BinaryDictIOUtils { p.mAddress += BinaryDictInputOutput.getGroupCountSize(p.mNumOfCharGroup); p.mPosition = 0; } - + if (p.mNumOfCharGroup == 0) { + stack.pop(); + continue; + } CharGroupInfo info = BinaryDictInputOutput.readCharGroup(buffer, p.mAddress - headerSize, formatOptions); for (int i = 0; i < info.mCharacters.length; ++i) { @@ -85,7 +88,10 @@ public class BinaryDictIOUtils { } p.mPosition++; - if (info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) { // found word + final boolean isMovedGroup = BinaryDictInputOutput.isMovedGroup(info.mFlags, + formatOptions); + if (!isMovedGroup + && info.mFrequency != FusionDictionary.CharGroup.NOT_A_TERMINAL) {// found word words.put(info.mOriginalAddress, new String(pushedChars, 0, index)); frequencies.put(info.mOriginalAddress, info.mFrequency); if (info.mBigrams != null) bigrams.put(info.mOriginalAddress, info.mBigrams); @@ -109,7 +115,7 @@ public class BinaryDictIOUtils { p.mAddress = buffer.position(); } - if (BinaryDictInputOutput.hasChildrenAddress(info.mChildrenAddress)) { + if (!isMovedGroup && BinaryDictInputOutput.hasChildrenAddress(info.mChildrenAddress)) { Position childrenPos = new Position(info.mChildrenAddress + headerSize, index); stack.push(childrenPos); } @@ -168,6 +174,10 @@ public class BinaryDictIOUtils { final int charGroupPos = buffer.position(); final CharGroupInfo currentInfo = BinaryDictInputOutput.readCharGroup(buffer, buffer.position(), header.mFormatOptions); + if (BinaryDictInputOutput.isMovedGroup(currentInfo.mFlags, + header.mFormatOptions)) { + continue; + } boolean same = true; for (int p = 0, j = word.offsetByCodePoints(0, wordPos); p < currentInfo.mCharacters.length; @@ -239,4 +249,34 @@ public class BinaryDictIOUtils { buffer.position(wordPosition); buffer.put((byte)newFlags); } + + private static void putSInt24(final FusionDictionaryBufferInterface buffer, + final int value) { + final int absValue = Math.abs(value); + buffer.put((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF)); + buffer.put((byte)((absValue >> 8) & 0xFF)); + buffer.put((byte)(absValue & 0xFF)); + } + + /** + * Update a parent address in a CharGroup that is addressed by groupOriginAddress. + * + * @param buffer the buffer to write. + * @param groupOriginAddress the address of the group. + * @param newParentAddress the absolute address of the parent. + * @param formatOptions file format options. + */ + public static void updateParentAddress(final FusionDictionaryBufferInterface buffer, + final int groupOriginAddress, final int newParentAddress, + final FormatOptions formatOptions) { + final int originalPosition = buffer.position(); + buffer.position(groupOriginAddress); + if (!formatOptions.mSupportsDynamicUpdate) { + throw new RuntimeException("this file format does not support parent addresses"); + } + final int flags = buffer.readUnsignedByte(); + final int parentOffset = newParentAddress - groupOriginAddress; + putSInt24(buffer, parentOffset); + buffer.position(originalPosition); + } } diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java index f9339de08..9fc694218 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java @@ -53,6 +53,7 @@ public class BinaryDictInputOutput { // If the number of passes exceeds this number, makedict bails with an exception on // suspicion that a bug might be causing an infinite loop. private static final int MAX_PASSES = 24; + private static final int MAX_JUMPS = 12; public interface FusionDictionaryBufferInterface { public int readUnsignedByte(); @@ -395,6 +396,13 @@ public class BinaryDictInputOutput { } /** + * Helper method to check whether the group is moved. + */ + public static boolean isMovedGroup(final int flags, final FormatOptions options) { + return options.mSupportsDynamicUpdate && ((flags & FormatSpec.FLAG_IS_MOVED) == 1); + } + + /** * Helper method to check whether the dictionary can be updated dynamically. */ public static boolean supportsDynamicUpdate(final FormatOptions options) { @@ -1374,8 +1382,18 @@ public class BinaryDictInputOutput { int index = FormatSpec.MAX_WORD_LENGTH - 1; // the length of the path from the root to the leaf is limited by MAX_WORD_LENGTH for (int count = 0; count < FormatSpec.MAX_WORD_LENGTH; ++count) { - buffer.position(currentAddress + headerSize); - final CharGroupInfo currentInfo = readCharGroup(buffer, currentAddress, options); + CharGroupInfo currentInfo; + int loopCounter = 0; + do { + buffer.position(currentAddress + headerSize); + currentInfo = readCharGroup(buffer, currentAddress, options); + if (isMovedGroup(currentInfo.mFlags, options)) { + currentAddress = currentInfo.mParentAddress + currentInfo.mOriginalAddress; + } + if (DBG && loopCounter++ > MAX_JUMPS) { + MakedictLog.d("Too many jumps - probably a bug"); + } + } while (isMovedGroup(currentInfo.mFlags, options)); for (int i = 0; i < currentInfo.mCharacters.length; ++i) { sGetWordBuffer[index--] = currentInfo.mCharacters[currentInfo.mCharacters.length - i - 1]; @@ -1457,6 +1475,7 @@ public class BinaryDictInputOutput { int groupOffset = nodeHeadPosition + getGroupCountSize(count); for (int i = count; i > 0; --i) { // Scan the array of CharGroup. CharGroupInfo info = readCharGroup(buffer, groupOffset, options); + if (isMovedGroup(info.mFlags, options)) continue; ArrayList<WeightedString> shortcutTargets = info.mShortcutTargets; ArrayList<WeightedString> bigrams = null; if (null != info.mBigrams) { diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index cab0661f6..35311f0c2 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -52,13 +52,18 @@ public final class FormatSpec { */ /* Node(CharGroup) layout is as follows: - * | addressType xx : mask with MASK_GROUP_ADDRESS_TYPE - * 2 bits, 00 = no children : FLAG_GROUP_ADDRESS_TYPE_NOADDRESS - * f | 01 = 1 byte : FLAG_GROUP_ADDRESS_TYPE_ONEBYTE - * l | 10 = 2 bytes : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES - * a | 11 = 3 bytes : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES - * g | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS - * s | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL + * | IF !SUPPORTS_DYNAMIC_UPDATE + * | addressType xx : mask with MASK_GROUP_ADDRESS_TYPE + * | 2 bits, 00 = no children : FLAG_GROUP_ADDRESS_TYPE_NOADDRESS + * f | 01 = 1 byte : FLAG_GROUP_ADDRESS_TYPE_ONEBYTE + * l | 10 = 2 bytes : FLAG_GROUP_ADDRESS_TYPE_TWOBYTES + * a | 11 = 3 bytes : FLAG_GROUP_ADDRESS_TYPE_THREEBYTES + * g | ELSE + * s | is moved ? 2 bits, 11 = no + * | 01 = yes + * | the new address is stored in the same place as the parent address + * | has several chars ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_MULTIPLE_CHARS + * | has a terminal ? 1 bit, 1 = yes, 0 = no : FLAG_IS_TERMINAL * | has shortcut targets ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_SHORTCUT_TARGETS * | has bigrams ? 1 bit, 1 = yes, 0 = no : FLAG_HAS_BIGRAMS * | is not a word ? 1 bit, 1 = yes, 0 = no : FLAG_IS_NOT_A_WORD @@ -178,6 +183,7 @@ public final class FormatSpec { static final int FLAG_HAS_BIGRAMS = 0x04; static final int FLAG_IS_NOT_A_WORD = 0x02; static final int FLAG_IS_BLACKLISTED = 0x01; + static final int FLAG_IS_MOVED = 0x40; static final int FLAG_ATTRIBUTE_HAS_NEXT = 0x80; static final int FLAG_ATTRIBUTE_OFFSET_NEGATIVE = 0x40; |