Merge "Make key label from supplementary code point"
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
index 8864b76..2fe0819 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java
@@ -58,8 +58,6 @@
 
     public final InputMethodSubtype mSubtype;
     public final Locale mLocale;
-    // TODO: Remove this member. It is used only for logging purpose.
-    public final int mOrientation;
     public final int mWidth;
     public final int mHeight;
     public final int mMode;
@@ -77,7 +75,6 @@
     public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) {
         mSubtype = params.mSubtype;
         mLocale = SubtypeLocaleUtils.getSubtypeLocale(mSubtype);
-        mOrientation = params.mOrientation;
         mWidth = params.mKeyboardWidth;
         mHeight = params.mKeyboardHeight;
         mMode = params.mMode;
@@ -101,7 +98,6 @@
 
     private static int computeHashCode(final KeyboardId id) {
         return Arrays.hashCode(new Object[] {
-                id.mOrientation,
                 id.mElementId,
                 id.mMode,
                 id.mWidth,
@@ -123,8 +119,7 @@
     private boolean equals(final KeyboardId other) {
         if (other == this)
             return true;
-        return other.mOrientation == mOrientation
-                && other.mElementId == mElementId
+        return other.mElementId == mElementId
                 && other.mMode == mMode
                 && other.mWidth == mWidth
                 && other.mHeight == mHeight
@@ -185,13 +180,10 @@
 
     @Override
     public String toString() {
-        final String orientation = (mOrientation == Configuration.ORIENTATION_PORTRAIT)
-                ? "port" : "land";
-        return String.format(Locale.ROOT, "[%s %s:%s %s:%dx%d %s %s %s%s%s%s%s%s%s%s%s]",
+        return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s %s%s%s%s%s%s%s%s%s]",
                 elementIdToName(mElementId),
-                mLocale,
-                mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
-                orientation, mWidth, mHeight,
+                mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
+                mWidth, mHeight,
                 modeName(mMode),
                 imeAction(),
                 (navigateNext() ? "navigateNext" : ""),
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index e153107..00b096c 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -119,7 +119,6 @@
         boolean mLanguageSwitchKeyEnabled;
         InputMethodSubtype mSubtype;
         boolean mIsSpellChecker;
-        int mOrientation;
         int mKeyboardWidth;
         int mKeyboardHeight;
         // Sparse array of KeyboardLayoutSet element parameters indexed by element's id.
@@ -242,9 +241,6 @@
         }
 
         public Builder setScreenGeometry(final int widthPixels, final int heightPixels) {
-            final Params params = mParams;
-            params.mOrientation = (heightPixels > widthPixels)
-                    ? Configuration.ORIENTATION_PORTRAIT : Configuration.ORIENTATION_LANDSCAPE;
             setDefaultKeyboardSize(widthPixels, heightPixels);
             return this;
         }
@@ -317,8 +313,6 @@
         }
 
         public KeyboardLayoutSet build() {
-            if (mParams.mOrientation == Configuration.ORIENTATION_UNDEFINED)
-                throw new RuntimeException("Screen geometry is not specified");
             if (mParams.mSubtype == null)
                 throw new RuntimeException("KeyboardLayoutSet subtype is not specified");
             final String packageName = mResources.getResourcePackageName(
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index 6782317..7a50388 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -615,7 +615,8 @@
         final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap;
         mSpacebarTextSize = keyHeight * mSpacebarTextRatio;
         if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
-            ResearchLogger.mainKeyboardView_setKeyboard(keyboard);
+            final int orientation = getContext().getResources().getConfiguration().orientation;
+            ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation);
         }
 
         // This always needs to be set since the accessibility state can
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index d970bca..6339e9c 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -2530,7 +2530,8 @@
         final int maxFreq = AutoCorrectionUtils.getMaxFrequency(
                 suggest.getUnigramDictionaries(), suggestion);
         if (maxFreq == 0) return null;
-        userHistoryPredictionDictionary.addToUserHistory(prevWord, secondWord, maxFreq > 0);
+        userHistoryPredictionDictionary
+                .addToPersonalizationPredictionDictionary(prevWord, secondWord, maxFreq > 0);
         return prevWord;
     }
 
diff --git a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
index 9d041f4..6498bf9 100644
--- a/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
+++ b/java/src/com/android/inputmethod/latin/personalization/DynamicPredictionDictionaryBase.java
@@ -31,6 +31,7 @@
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.settings.Settings;
 import com.android.inputmethod.latin.utils.ByteArrayWrapper;
+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;
@@ -49,9 +50,6 @@
  * This class is a base class of a dictionary for the personalized prediction language model.
  */
 public abstract class DynamicPredictionDictionaryBase extends ExpandableDictionary {
-    public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
-        // TODO: Implement
-    }
 
     private static final String TAG = DynamicPredictionDictionaryBase.class.getSimpleName();
     public static final boolean DBG_SAVE_RESTORE = false;
@@ -75,6 +73,9 @@
     private final ReentrantLock mBigramListLock = new ReentrantLock();
     private final SharedPreferences mPrefs;
 
+    private final ArrayList<PersonalizationDictionaryUpdateSession> mSessions =
+            CollectionUtils.newArrayList();
+
     // Should always be false except when we use this class for test
     @UsedForTesting boolean isTest = false;
 
@@ -118,14 +119,15 @@
     }
 
     /**
-     * Pair will be added to the user history dictionary.
+     * Pair will be added to the personalization prediction dictionary.
      *
      * The first word may be null. That means we don't know the context, in other words,
      * it's only a unigram. The first word may also be an empty string : this means start
      * context, as in beginning of a sentence for example.
      * The second word may not be null (a NullPointerException would be thrown).
      */
-    public int addToUserHistory(final String word1, final String word2, final boolean isValid) {
+    public int addToPersonalizationPredictionDictionary(
+            final String word1, final String word2, final boolean isValid) {
         if (word2.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH ||
                 (word1 != null && word1.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) {
             return -1;
@@ -393,9 +395,14 @@
             final String word1, final String word2, final boolean isValid) {
         mBigramListLock.lock();
         try {
-            addToUserHistory(word1, word2, isValid);
+            addToPersonalizationPredictionDictionary(word1, word2, isValid);
         } finally {
             mBigramListLock.unlock();
         }
     }
+
+    public void registerUpdateSession(PersonalizationDictionaryUpdateSession session) {
+        session.setDictionary(this);
+        mSessions.add(session);
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
index 19554d6..e38a235 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java
@@ -27,7 +27,7 @@
 public class PersonalizationDictionary extends ExpandableBinaryDictionary {
     private static final String NAME = "personalization";
 
-    public static void registerUpdateListener(PersonalizationDictionaryUpdateListener listener) {
+    public static void registerUpdateListener(PersonalizationDictionaryUpdateSession listener) {
         // TODO: Implement
     }
 
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
index 9f013df..da256f8 100644
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryHelper.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
 import android.util.Log;
 
 import java.lang.ref.SoftReference;
@@ -58,6 +59,16 @@
         }
     }
 
+    public static void
+            registerPersonalizationDictionaryUpdateSession(final Context context,
+                    final PersonalizationDictionaryUpdateSession session) {
+        final PersonalizationPredictionDictionary dictionary =
+                getPersonalizationPredictionDictionary(context,
+                        context.getResources().getConfiguration().locale.toString(),
+                        PreferenceManager.getDefaultSharedPreferences(context));
+        dictionary.registerUpdateSession(session);
+    }
+
     public static PersonalizationPredictionDictionary getPersonalizationPredictionDictionary(
             final Context context, final String locale, final SharedPreferences sp) {
         synchronized (sLangPersonalizationDictCache) {
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
deleted file mode 100644
index c78e5a9..0000000
--- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateListener.java
+++ /dev/null
@@ -1,21 +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.personalization;
-
-public interface PersonalizationDictionaryUpdateListener {
-    // TODO: Implement
-}
diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
new file mode 100644
index 0000000..d62aec1
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionaryUpdateSession.java
@@ -0,0 +1,66 @@
+/*
+ * 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.personalization;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * This class is a session where a data provider can communicate with a personalization
+ * dictionary.
+ */
+public abstract class PersonalizationDictionaryUpdateSession {
+    /**
+     * This class is a parameter for a new unigram or bigram word which will be added
+     * to the personalization dictionary.
+     */
+    public static class PersonalizationLanguageModelParam {
+        public final String mWord0;
+        public final String mWord1;
+        public final boolean mIsValid;
+        public final int mFrequency;
+        public PersonalizationLanguageModelParam(String word0, String word1, boolean isValid,
+                int frequency) {
+            mWord0 = word0;
+            mWord1 = word1;
+            mIsValid = isValid;
+            mFrequency = frequency;
+        }
+    }
+
+    // TODO: Use a dynamic binary dictionary instead
+    public WeakReference<DynamicPredictionDictionaryBase> mDictionary;
+
+    public abstract void onDictionaryReady();
+
+    public void setDictionary(DynamicPredictionDictionaryBase dictionary) {
+        mDictionary = new WeakReference<DynamicPredictionDictionaryBase>(dictionary);
+    }
+
+    public void addToPersonalizationDictionary(
+            final ArrayList<PersonalizationLanguageModelParam> lmParams) {
+        final DynamicPredictionDictionaryBase dictionary = mDictionary == null
+                ? null : mDictionary.get();
+        if (dictionary == null) {
+            return;
+        }
+        for (final PersonalizationLanguageModelParam lmParam : lmParams) {
+            dictionary.addToPersonalizationPredictionDictionary(
+                    lmParam.mWord0, lmParam.mWord1, lmParam.mIsValid);
+        }
+    }
+}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 0bd4939..3a34082 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -1414,7 +1414,8 @@
                     "navigatePrevious", "clobberSettingsKey", "passwordInput", "shortcutKeyEnabled",
                     "hasShortcutKey", "languageSwitchKeyEnabled", "isMultiLine", "tw", "th",
                     "keys");
-    public static void mainKeyboardView_setKeyboard(final Keyboard keyboard) {
+    public static void mainKeyboardView_setKeyboard(final Keyboard keyboard,
+            final int orientation) {
         final KeyboardId kid = keyboard.mId;
         final boolean isPasswordView = kid.passwordInput();
         final ResearchLogger researchLogger = getInstance();
@@ -1422,7 +1423,7 @@
         researchLogger.enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD,
                 KeyboardId.elementIdToName(kid.mElementId),
                 kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
-                kid.mOrientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(),
+                orientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(),
                 kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey,
                 isPasswordView, kid.mShortcutKeyEnabled, kid.mHasShortcutKey,
                 kid.mLanguageSwitchKeyEnabled, kid.isMultiLine(), keyboard.mOccupiedWidth,
diff --git a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
index 17cbdde..9a180e6 100644
--- a/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
+++ b/native/jni/src/suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "suggest/policyimpl/dictionary/patricia_trie_policy.h"
+#include "suggest/policyimpl/dictionary/dynamic_patricia_trie_policy.h"
 
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node.h"
@@ -62,8 +62,45 @@
         const BinaryDictionaryInfo *const binaryDictionaryInfo,
         const int nodePos, const int maxCodePointCount, int *const outCodePoints,
         int *const outUnigramProbability) const {
-    // TODO: Implement.
-    return 0;
+    // This method traverses parent nodes from the terminal by following parent pointers; thus,
+    // node code points are stored in the buffer in the reverse order.
+    int reverseCodePoints[maxCodePointCount];
+    int mergedNodeCodePoints[maxCodePointCount];
+    int codePointCount = 0;
+
+    DynamicPatriciaTrieNodeReader nodeReader(binaryDictionaryInfo);
+    // First, read terminal node and get its probability.
+    nodeReader.fetchNodeInfoFromBufferAndGetNodeCodePoints(nodePos, maxCodePointCount,
+            mergedNodeCodePoints);
+    // Store terminal node probability.
+    *outUnigramProbability = nodeReader.getProbability();
+    // Store terminal node code points to buffer in the reverse order.
+    for (int i = nodeReader.getCodePointCount() - 1; i >= 0; --i) {
+        reverseCodePoints[codePointCount++] = mergedNodeCodePoints[i];
+    }
+    // Then, follow parent pos toward the root node.
+    while (nodeReader.getParentPos() != getRootPosition()) {
+        // codePointCount must be incremented at least once in each iteration to ensure preventing
+        // infinite loop.
+        if (nodeReader.isDeleted() || codePointCount > maxCodePointCount
+                || nodeReader.getCodePointCount() <= 0) {
+            // The nodePos is not a valid terminal node position in the dictionary.
+            *outUnigramProbability = NOT_A_PROBABILITY;
+            return 0;
+        }
+        // Read parent node.
+        nodeReader.fetchNodeInfoFromBufferAndGetNodeCodePoints(nodeReader.getParentPos(),
+                maxCodePointCount, mergedNodeCodePoints);
+        // Store node code points to buffer in the reverse order.
+        for (int i = nodeReader.getCodePointCount() - 1; i >= 0; --i) {
+            reverseCodePoints[codePointCount++] = mergedNodeCodePoints[i];
+        }
+    }
+    // Reverse the stored code points to output them.
+    for (int i = 0; i < codePointCount; ++i) {
+        outCodePoints[i] = reverseCodePoints[codePointCount - i - 1];
+    }
+    return codePointCount;
 }
 
 int DynamicPatriciaTriePolicy::getTerminalNodePositionOfWord(