Merge "Import translations. DO NOT MERGE"
diff --git a/java/res/xml/rows_number_normal.xml b/java/res/xml/rows_number_normal.xml
index c59e262..b77544b 100644
--- a/java/res/xml/rows_number_normal.xml
+++ b/java/res/xml/rows_number_normal.xml
@@ -33,6 +33,8 @@
             latin:keyStyle="numKeyStyle" />
         <Key
             latin:keyLabel="-"
+            latin:moreKeys="+"
+            latin:keyLabelFlags="hasPopupHint"
             latin:keyStyle="numFunctionalKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
diff --git a/java/res/xml/rows_phone.xml b/java/res/xml/rows_phone.xml
index 630b24e..9299c2a 100644
--- a/java/res/xml/rows_phone.xml
+++ b/java/res/xml/rows_phone.xml
@@ -34,6 +34,8 @@
             latin:keyStyle="num3KeyStyle" />
         <Key
             latin:keyLabel="-"
+            latin:moreKeys="+"
+            latin:keyLabelFlags="hasPopupHint"
             latin:keyStyle="numFunctionalKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
diff --git a/java/res/xml/rows_phone_symbols.xml b/java/res/xml/rows_phone_symbols.xml
index 7841c56..c13018e 100644
--- a/java/res/xml/rows_phone_symbols.xml
+++ b/java/res/xml/rows_phone_symbols.xml
@@ -37,6 +37,8 @@
             latin:keyStyle="numKeyStyle" />
         <Key
             latin:keyLabel="-"
+            latin:moreKeys="+"
+            latin:keyLabelFlags="hasPopupHint"
             latin:keyStyle="numFunctionalKeyStyle"
             latin:keyWidth="fillRight" />
     </Row>
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
index c34d19d..5ed910c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictIOUtils.java
@@ -22,9 +22,13 @@
 import com.android.inputmethod.latin.makedict.FormatSpec.FileHeader;
 import com.android.inputmethod.latin.makedict.FormatSpec.FormatOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.CharGroup;
+import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
 
 import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Stack;
 
@@ -251,12 +255,50 @@
         buffer.put((byte)newFlags);
     }
 
-    private static void putSInt24(final FusionDictionaryBufferInterface buffer,
+    /**
+     * @return the size written, in bytes. Always 3 bytes.
+     */
+    private static int writeSInt24ToBuffer(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));
+        return 3;
+    }
+
+    /**
+     * @return the size written, in bytes. Always 3 bytes.
+     */
+    private static int writeSInt24ToStream(final OutputStream destination, final int value)
+            throws IOException {
+        final int absValue = Math.abs(value);
+        destination.write((byte)(((value < 0 ? 0x80 : 0) | (absValue >> 16)) & 0xFF));
+        destination.write((byte)((absValue >> 8) & 0xFF));
+        destination.write((byte)(absValue & 0xFF));
+        return 3;
+    }
+
+    /**
+     * @return the size written, in bytes. 1, 2, or 3 bytes.
+     */
+    private static int writeVariableAddress(final OutputStream destination, final int value)
+            throws IOException {
+        switch (BinaryDictInputOutput.getByteSize(value)) {
+        case 1:
+            destination.write((byte)value);
+            break;
+        case 2:
+            destination.write((byte)(0xFF & (value >> 8)));
+            destination.write((byte)(0xFF & value));
+            break;
+        case 3:
+            destination.write((byte)(0xFF & (value >> 16)));
+            destination.write((byte)(0xFF & (value >> 8)));
+            destination.write((byte)(0xFF & value));
+            break;
+        }
+        return BinaryDictInputOutput.getByteSize(value);
     }
 
     /**
@@ -277,7 +319,7 @@
         }
         final int flags = buffer.readUnsignedByte();
         final int parentOffset = newParentAddress - groupOriginAddress;
-        putSInt24(buffer, parentOffset);
+        writeSInt24ToBuffer(buffer, parentOffset);
         buffer.position(originalPosition);
     }
 
@@ -294,6 +336,35 @@
     }
 
     /**
+     * Write a string to a stream.
+     *
+     * @param destination the stream to write.
+     * @param word the string to be written.
+     * @return the size written, in bytes.
+     * @throws IOException
+     */
+    private static int writeString(final OutputStream destination, final String word)
+            throws IOException {
+        int size = 0;
+        final int length = word.length();
+        for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) {
+            final int codePoint = word.codePointAt(i);
+            if (CharEncoding.getCharSize(codePoint) == 1) {
+                destination.write((byte)codePoint);
+                size++;
+            } else {
+                destination.write((byte)(0xFF & (codePoint >> 16)));
+                destination.write((byte)(0xFF & (codePoint >> 8)));
+                destination.write((byte)(0xFF & codePoint));
+                size += 3;
+            }
+        }
+        destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR);
+        size++;
+        return size;
+    }
+
+    /**
      * Update a children address in a CharGroup that is addressed by groupOriginAddress.
      *
      * @param buffer the buffer to write.
@@ -312,7 +383,91 @@
         if ((FormatSpec.FLAG_IS_TERMINAL) != 0) buffer.readUnsignedByte();
         final int childrenOffset = newChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS
                 ? FormatSpec.NO_CHILDREN_ADDRESS : newChildrenAddress - buffer.position();
-        putSInt24(buffer, childrenOffset);
+        writeSInt24ToBuffer(buffer, childrenOffset);
         buffer.position(originalPosition);
     }
+
+    /**
+     * Write a char group to an output stream.
+     * A char group is an in-memory representation of a node in trie.
+     * A char group info is an on-disk representation of a node.
+     *
+     * @param destination the stream to write.
+     * @param info the char group info to be written.
+     * @return the size written, in bytes.
+     */
+    public static int writeCharGroup(final OutputStream destination, final CharGroupInfo info)
+            throws IOException {
+        int size = 1;
+        destination.write((byte)info.mFlags);
+        final int parentOffset = info.mParentAddress == FormatSpec.NO_PARENT_ADDRESS ?
+                FormatSpec.NO_PARENT_ADDRESS : info.mParentAddress - info.mOriginalAddress;
+        size += writeSInt24ToStream(destination, parentOffset);
+
+        for (int i = 0; i < info.mCharacters.length; ++i) {
+            if (CharEncoding.getCharSize(info.mCharacters[i]) == 1) {
+                destination.write((byte)info.mCharacters[i]);
+                size++;
+            } else {
+                size += writeSInt24ToStream(destination, info.mCharacters[i]);
+            }
+        }
+        if (info.mCharacters.length > 1) {
+            destination.write((byte)FormatSpec.GROUP_CHARACTERS_TERMINATOR);
+            size++;
+        }
+
+        if ((info.mFlags & FormatSpec.FLAG_IS_TERMINAL) != 0) {
+            destination.write((byte)info.mFrequency);
+            size++;
+        }
+
+        final int childrenOffset = info.mChildrenAddress == FormatSpec.NO_CHILDREN_ADDRESS ?
+                0 : info.mChildrenAddress - info.mOriginalAddress;
+        writeSInt24ToStream(destination, childrenOffset);
+
+        if (info.mShortcutTargets != null && info.mShortcutTargets.size() > 0) {
+            final int shortcutListSize =
+                    BinaryDictInputOutput.getShortcutListSize(info.mShortcutTargets);
+            destination.write((byte)(shortcutListSize >> 8));
+            destination.write((byte)(shortcutListSize & 0xFF));
+            size += 2;
+            final Iterator<WeightedString> shortcutIterator = info.mShortcutTargets.iterator();
+            while (shortcutIterator.hasNext()) {
+                final WeightedString target = shortcutIterator.next();
+                destination.write((byte)BinaryDictInputOutput.makeShortcutFlags(
+                        shortcutIterator.hasNext(), target.mFrequency));
+                size++;
+                size += writeString(destination, target.mWord);
+            }
+        }
+
+        if (info.mBigrams != null) {
+            // TODO: Consolidate this code with the code that computes the size of the bigram list
+            //        in BinaryDictionaryInputOutput#computeActualNodeSize
+            for (int i = 0; i < info.mBigrams.size(); ++i) {
+                final int bigramOffset = info.mBigrams.get(i).mAddress - info.mOriginalAddress;
+                final int bigramFrequency = info.mBigrams.get(i).mFrequency;
+                int bigramFlags = (i < info.mBigrams.size() - 1)
+                        ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0;
+                bigramFlags |= (bigramOffset < 0) ? FormatSpec.FLAG_ATTRIBUTE_OFFSET_NEGATIVE : 0;
+                switch (BinaryDictInputOutput.getByteSize(bigramOffset)) {
+                case 1:
+                    bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_ONEBYTE;
+                    break;
+                case 2:
+                    bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_TWOBYTES;
+                    break;
+                case 3:
+                    bigramFlags |= FormatSpec.FLAG_ATTRIBUTE_ADDRESS_TYPE_THREEBYTES;
+                    break;
+                }
+                bigramFlags |= bigramFrequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY;
+                destination.write((byte)bigramFlags);
+                size++;
+                size += writeVariableAddress(destination, Math.abs(bigramOffset));
+            }
+        }
+        return size;
+    }
 }
diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
index 3e6965a..ba29523 100644
--- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
+++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java
@@ -153,7 +153,7 @@
          * @param character the character code.
          * @return the size in binary encoded-form, either 1 or 3 bytes.
          */
-        private static int getCharSize(final int character) {
+        static int getCharSize(final int character) {
             // See char encoding in FusionDictionary.java
             if (fitsOnOneByte(character)) return 1;
             if (FormatSpec.INVALID_CHARACTER == character) return 1;
@@ -337,7 +337,7 @@
      * This is known in advance and does not change according to position in the file
      * like address lists do.
      */
-    private static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
+    static int getShortcutListSize(final ArrayList<WeightedString> shortcutList) {
         if (null == shortcutList) return 0;
         int size = FormatSpec.GROUP_SHORTCUT_LIST_SIZE_SIZE;
         for (final WeightedString shortcut : shortcutList) {
@@ -438,7 +438,7 @@
      * @param address the address
      * @return the byte size.
      */
-    private static int getByteSize(final int address) {
+    static int getByteSize(final int address) {
         assert(address <= UINT24_MAX);
         if (!hasChildrenAddress(address)) {
             return 0;
@@ -858,7 +858,7 @@
      * @param frequency the frequency of the attribute, 0..15
      * @return the flags
      */
-    private static final int makeShortcutFlags(final boolean more, final int frequency) {
+    static final int makeShortcutFlags(final boolean more, final int frequency) {
         return (more ? FormatSpec.FLAG_ATTRIBUTE_HAS_NEXT : 0)
                 + (frequency & FormatSpec.FLAG_ATTRIBUTE_FREQUENCY);
     }
@@ -896,6 +896,7 @@
      */
     private static int writePlacedNode(final FusionDictionary dict, byte[] buffer,
             final Node node, final FormatOptions formatOptions) {
+        // TODO: Make the code in common with BinaryDictIOUtils#writeCharGroup
         int index = node.mCachedAddress;
 
         final int groupCount = node.mData.size();
diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
index e9bf0fa..4ad82ab 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java
@@ -22,7 +22,6 @@
 
 import com.android.inputmethod.keyboard.Key;
 import com.android.inputmethod.keyboard.Keyboard;
-import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -175,11 +174,11 @@
         }
 
         public Builder layout(final SuggestedWords suggestions, final int fromPos,
-                final int maxWidth, final int minWidth, final int maxRow) {
-            final Keyboard keyboard = KeyboardSwitcher.getInstance().getKeyboard();
+                final int maxWidth, final int minWidth, final int maxRow,
+                final Keyboard parentKeyboard) {
             final int xmlId = R.xml.kbd_suggestions_pane_template;
-            load(xmlId, keyboard.mId);
-            mParams.mVerticalGap = mParams.mTopPadding = keyboard.mVerticalGap / 2;
+            load(xmlId, parentKeyboard.mId);
+            mParams.mVerticalGap = mParams.mTopPadding = parentKeyboard.mVerticalGap / 2;
 
             final int count = mParams.layout(suggestions, fromPos, maxWidth, minWidth, maxRow,
                     mPaneView);
diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
index e926fa2..94283f2 100644
--- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
+++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java
@@ -52,7 +52,9 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.keyboard.KeyboardActionListener;
+import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.MoreKeysPanel;
 import com.android.inputmethod.keyboard.PointerTracker;
@@ -751,36 +753,37 @@
     }
 
     private boolean showMoreSuggestions() {
-        final SuggestionStripViewParams params = mParams;
-        if (params.mMoreSuggestionsAvailable) {
-            final int stripWidth = getWidth();
-            final View container = mMoreSuggestionsContainer;
-            final int maxWidth = stripWidth - container.getPaddingLeft()
-                    - container.getPaddingRight();
-            final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
-            builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
-                    (int)(maxWidth * params.mMinMoreSuggestionsWidth),
-                    params.getMaxMoreSuggestionsRow());
-            mMoreSuggestionsView.setKeyboard(builder.build());
-            container.measure(
-                    ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-
-            final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
-            final int pointX = stripWidth / 2;
-            final int pointY = -params.mMoreSuggestionsBottomGap;
-            moreKeysPanel.showMoreKeysPanel(
-                    this, mMoreSuggestionsController, pointX, pointY,
-                    mMoreSuggestionsWindow, mMoreSuggestionsListener);
-            mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
-            mOriginX = mLastX;
-            mOriginY = mLastY;
-            mKeyboardView.dimEntireKeyboard(true);
-            for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
-                mWords.get(i).setPressed(false);
-            }
-            return true;
+        final Keyboard parentKeyboard = KeyboardSwitcher.getInstance().getKeyboard();
+        if (parentKeyboard == null) {
+            return false;
         }
-        return false;
+        final SuggestionStripViewParams params = mParams;
+        if (!params.mMoreSuggestionsAvailable) {
+            return false;
+        }
+        final int stripWidth = getWidth();
+        final View container = mMoreSuggestionsContainer;
+        final int maxWidth = stripWidth - container.getPaddingLeft() - container.getPaddingRight();
+        final MoreSuggestions.Builder builder = mMoreSuggestionsBuilder;
+        builder.layout(mSuggestedWords, params.mSuggestionsCountInStrip, maxWidth,
+                (int)(maxWidth * params.mMinMoreSuggestionsWidth),
+                params.getMaxMoreSuggestionsRow(), parentKeyboard);
+        mMoreSuggestionsView.setKeyboard(builder.build());
+        container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+        final MoreKeysPanel moreKeysPanel = mMoreSuggestionsView;
+        final int pointX = stripWidth / 2;
+        final int pointY = -params.mMoreSuggestionsBottomGap;
+        moreKeysPanel.showMoreKeysPanel(this, mMoreSuggestionsController, pointX, pointY,
+                mMoreSuggestionsWindow, mMoreSuggestionsListener);
+        mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING;
+        mOriginX = mLastX;
+        mOriginY = mLastY;
+        mKeyboardView.dimEntireKeyboard(true);
+        for (int i = 0; i < params.mSuggestionsCountInStrip; i++) {
+            mWords.get(i).setPressed(false);
+        }
+        return true;
     }
 
     // Working variables for onLongClick and dispatchTouchEvent.