Merge "Show results in the native code." into jb-mr1-dev
diff --git a/java/res/values-fr/donottranslate.xml b/java/res/values-fr/donottranslate.xml
index 8cf2516..5288bd7 100644
--- a/java/res/values-fr/donottranslate.xml
+++ b/java/res/values-fr/donottranslate.xml
@@ -25,5 +25,7 @@
     <!-- Symbols that should promote magic spaces into real space -->
     <string name="phantom_space_promoting_symbols">;:!?([*&amp;@{&lt;&gt;+=|</string>
     <!-- Symbols that do NOT separate words -->
-    <string name="symbols_excluded_from_word_separators">\'</string>
+    <!-- Note that this is identical to the default value, but since the above ones are different
+         and those variables only make sense together, this is kept here for readability. -->
+    <string name="symbols_excluded_from_word_separators">\'-</string>
 </resources>
diff --git a/java/res/values-it/donottranslate.xml b/java/res/values-it/donottranslate.xml
deleted file mode 100644
index 58e9436..0000000
--- a/java/res/values-it/donottranslate.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2009, 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.
-*/
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Symbols that do NOT separate words -->
-    <string name="symbols_excluded_from_word_separators"></string>
-</resources>
diff --git a/java/res/values-ko/strings.xml b/java/res/values-ko/strings.xml
index c89d878..9049034 100644
--- a/java/res/values-ko/strings.xml
+++ b/java/res/values-ko/strings.xml
@@ -62,7 +62,7 @@
     <string name="gesture_input_summary" msgid="7019742443455085809">"한번에 문자를 그려서 단어 입력"</string>
     <string name="gesture_preview_trail" msgid="3802333369335722221">"동작 흔적 표시"</string>
     <string name="gesture_floating_preview_text" msgid="6859416520117939680">"동작 단어 표시"</string>
-    <string name="gesture_floating_preview_text_summary" msgid="3333754126434989709">"동작과 함께 이동식 단어 미리보기 표시"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="3333754126434989709">"동작에 따라 단어 미리보기 표시"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: 저장됨"</string>
     <string name="label_go_key" msgid="1635148082137219148">"이동"</string>
     <string name="label_next_key" msgid="362972844525672568">"다음"</string>
diff --git a/java/res/values-ms/strings.xml b/java/res/values-ms/strings.xml
index 85bb374..4f0f19b 100644
--- a/java/res/values-ms/strings.xml
+++ b/java/res/values-ms/strings.xml
@@ -60,12 +60,9 @@
     <string name="bigram_prediction_summary" msgid="3253961591626441019">"Berdasarkan perkataan sebelumnya"</string>
     <string name="gesture_input" msgid="3310827802759290774">"Input gerak isyarat"</string>
     <string name="gesture_input_summary" msgid="7019742443455085809">"Masukkan perkataan dengan menyurih huruf perkataan itu."</string>
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (6859416520117939680) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (3333754126434989709) -->
-    <skip />
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Tunjukkan jejak gerak isyarat"</string>
+    <string name="gesture_floating_preview_text" msgid="6859416520117939680">"Tunjukkan perkataan gerak isyarat"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="3333754126434989709">"Tunjukkan perkataan pratonton terapung dengan gerak isyarat"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Disimpan"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pergi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Seterusnya"</string>
diff --git a/java/res/values-pt/strings.xml b/java/res/values-pt/strings.xml
index d4e8c09..b483067 100644
--- a/java/res/values-pt/strings.xml
+++ b/java/res/values-pt/strings.xml
@@ -60,12 +60,9 @@
     <string name="bigram_prediction_summary" msgid="3253961591626441019">"Com base na palavra anterior"</string>
     <string name="gesture_input" msgid="3310827802759290774">"Entrada por gesto"</string>
     <string name="gesture_input_summary" msgid="7019742443455085809">"Introduza uma palavra traçando suas letras"</string>
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (6859416520117939680) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (3333754126434989709) -->
-    <skip />
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Mostrar percurso do gesto"</string>
+    <string name="gesture_floating_preview_text" msgid="6859416520117939680">"Mostrar palavra do gesto"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="3333754126434989709">"Mostrar visualização flutuante da palavra com gesto"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g> : Salvo"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Ir"</string>
     <string name="label_next_key" msgid="362972844525672568">"Avançar"</string>
diff --git a/java/res/values-sl/strings.xml b/java/res/values-sl/strings.xml
index 3c522eb..d21e585 100644
--- a/java/res/values-sl/strings.xml
+++ b/java/res/values-sl/strings.xml
@@ -60,12 +60,9 @@
     <string name="bigram_prediction_summary" msgid="3253961591626441019">"Na podlagi prejšnje besede"</string>
     <string name="gesture_input" msgid="3310827802759290774">"Vnos s potezo"</string>
     <string name="gesture_input_summary" msgid="7019742443455085809">"Vnašanje besede z drsenjem po zaslonu od črke do črke"</string>
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (6859416520117939680) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (3333754126434989709) -->
-    <skip />
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"Prikaži pot poteze"</string>
+    <string name="gesture_floating_preview_text" msgid="6859416520117939680">"Pokaži besedo za potezo"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="3333754126434989709">"Pokaži plavajoči predogled besede za potezo"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>: shranjeno"</string>
     <string name="label_go_key" msgid="1635148082137219148">"Pojdi"</string>
     <string name="label_next_key" msgid="362972844525672568">"Naprej"</string>
diff --git a/java/res/values-zh-rCN/strings.xml b/java/res/values-zh-rCN/strings.xml
index 0014db3..c608694 100644
--- a/java/res/values-zh-rCN/strings.xml
+++ b/java/res/values-zh-rCN/strings.xml
@@ -60,12 +60,9 @@
     <string name="bigram_prediction_summary" msgid="3253961591626441019">"根据上一个字词提供建议"</string>
     <string name="gesture_input" msgid="3310827802759290774">"手势输入"</string>
     <string name="gesture_input_summary" msgid="7019742443455085809">"连笔书写输入字词"</string>
-    <!-- no translation found for gesture_preview_trail (3802333369335722221) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text (6859416520117939680) -->
-    <skip />
-    <!-- no translation found for gesture_floating_preview_text_summary (3333754126434989709) -->
-    <skip />
+    <string name="gesture_preview_trail" msgid="3802333369335722221">"显示手指操作轨迹"</string>
+    <string name="gesture_floating_preview_text" msgid="6859416520117939680">"显示手指操作文字"</string>
+    <string name="gesture_floating_preview_text_summary" msgid="3333754126434989709">"进行手指操作时显示浮动预览文字"</string>
     <string name="added_word" msgid="8993883354622484372">"<xliff:g id="WORD">%s</xliff:g>:已保存"</string>
     <string name="label_go_key" msgid="1635148082137219148">"开始"</string>
     <string name="label_next_key" msgid="362972844525672568">"下一步"</string>
diff --git a/java/src/com/android/inputmethod/keyboard/KeyDetector.java b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
index a3b7776..97d88af 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyDetector.java
@@ -37,8 +37,9 @@
     }
 
     public void setKeyboard(Keyboard keyboard, float correctionX, float correctionY) {
-        if (keyboard == null)
+        if (keyboard == null) {
             throw new NullPointerException();
+        }
         mCorrectionX = (int)correctionX;
         mCorrectionY = (int)correctionY;
         mKeyboard = keyboard;
@@ -58,8 +59,6 @@
     }
 
     public Keyboard getKeyboard() {
-        if (mKeyboard == null)
-            throw new IllegalStateException("keyboard isn't set");
         return mKeyboard;
     }
 
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
index 4d95209..dc84763 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java
@@ -349,7 +349,7 @@
         return mKeyboardView;
     }
 
-    public View onCreateInputView() {
+    public View onCreateInputView(boolean isHardwareAcceleratedDrawingEnabled) {
         if (mKeyboardView != null) {
             mKeyboardView.closing();
         }
@@ -372,6 +372,10 @@
         }
 
         mKeyboardView = (MainKeyboardView) mCurrentInputView.findViewById(R.id.keyboard_view);
+        if (isHardwareAcceleratedDrawingEnabled) {
+            mKeyboardView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            // TODO: Should use LAYER_TYPE_SOFTWARE when hardware acceleration is off?
+        }
         mKeyboardView.setKeyboardActionListener(mLatinIME);
         if (mForceNonDistinctMultitouch) {
             mKeyboardView.setDistinctMultitouch(false);
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
index 6b1320d..69e4d98 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java
@@ -123,6 +123,8 @@
     /** The working rectangle variable */
     private final Rect mWorkingRect = new Rect();
     /** The keyboard bitmap buffer for faster updates */
+    /** The clip region to draw keys */
+    private final Region mClipRegion = new Region();
     private Bitmap mOffscreenBuffer;
     /** The canvas for the above mutable keyboard bitmap */
     private Canvas mOffscreenCanvas;
@@ -457,10 +459,15 @@
     @Override
     public void onDraw(Canvas canvas) {
         super.onDraw(canvas);
+        if (canvas.isHardwareAccelerated()) {
+            onDrawKeyboard(canvas);
+            return;
+        }
         if (mBufferNeedsUpdate || mOffscreenBuffer == null) {
             mBufferNeedsUpdate = false;
             if (maybeAllocateOffscreenBuffer()) {
                 mInvalidateAllKeys = true;
+                // TODO: Stop using the offscreen canvas even when in software rendering
                 if (mOffscreenCanvas != null) {
                     mOffscreenCanvas.setBitmap(mOffscreenBuffer);
                 } else {
@@ -502,35 +509,57 @@
         final Paint paint = mPaint;
         final KeyDrawParams params = mKeyDrawParams;
 
-        if (mInvalidateAllKeys || mInvalidatedKeys.isEmpty()) {
-            mWorkingRect.set(0, 0, width, height);
-            canvas.clipRect(mWorkingRect, Region.Op.REPLACE);
-            canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+        // Calculate clip region and set.
+        final boolean drawAllKeys = mInvalidateAllKeys || mInvalidatedKeys.isEmpty();
+        final boolean isHardwareAccelerated = canvas.isHardwareAccelerated();
+        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
+        if (drawAllKeys || isHardwareAccelerated) {
+            mClipRegion.set(0, 0, width, height);
+        } else {
+            mClipRegion.setEmpty();
+            for (final Key key : mInvalidatedKeys) {
+                if (mKeyboard.hasKey(key)) {
+                    final int x = key.mX + getPaddingLeft();
+                    final int y = key.mY + getPaddingTop();
+                    mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
+                    mClipRegion.union(mWorkingRect);
+                }
+            }
+        }
+        if (!isHardwareAccelerated) {
+            canvas.clipRegion(mClipRegion, Region.Op.REPLACE);
+        }
+
+        // Draw keyboard background.
+        canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
+        final Drawable background = getBackground();
+        if (background != null) {
+            background.draw(canvas);
+        }
+
+        // TODO: Confirm if it's really required to draw all keys when hardware acceleration is on.
+        if (drawAllKeys || isHardwareAccelerated) {
             // Draw all keys.
             for (final Key key : mKeyboard.mKeys) {
                 onDrawKey(key, canvas, paint, params);
             }
-            if (mNeedsToDimEntireKeyboard) {
-                drawDimRectangle(canvas, mWorkingRect, mBackgroundDimAlpha, paint);
-            }
         } else {
             // Draw invalidated keys.
             for (final Key key : mInvalidatedKeys) {
-                if (!mKeyboard.hasKey(key)) {
-                    continue;
-                }
-                final int x = key.mX + getPaddingLeft();
-                final int y = key.mY + getPaddingTop();
-                mWorkingRect.set(x, y, x + key.mWidth, y + key.mHeight);
-                canvas.clipRect(mWorkingRect, Region.Op.REPLACE);
-                canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR);
-                onDrawKey(key, canvas, paint, params);
-                if (mNeedsToDimEntireKeyboard) {
-                    drawDimRectangle(canvas, mWorkingRect, mBackgroundDimAlpha, paint);
+                if (mKeyboard.hasKey(key)) {
+                    onDrawKey(key, canvas, paint, params);
                 }
             }
         }
 
+        // Overlay a dark rectangle to dim.
+        if (mNeedsToDimEntireKeyboard) {
+            paint.setColor(Color.BLACK);
+            paint.setAlpha(mBackgroundDimAlpha);
+            // Note: clipRegion() above is in effect if it was called.
+            canvas.drawRect(0, 0, width, height, paint);
+        }
+
         // ResearchLogging indicator.
         // TODO: Reimplement using a keyboard background image specific to the ResearchLogger,
         // and remove this call.
@@ -863,13 +892,6 @@
         canvas.translate(-x, -y);
     }
 
-    // Overlay a dark rectangle to dim.
-    private static void drawDimRectangle(Canvas canvas, Rect rect, int alpha, Paint paint) {
-        paint.setColor(Color.BLACK);
-        paint.setAlpha(alpha);
-        canvas.drawRect(rect, paint);
-    }
-
     public Paint newDefaultLabelPaint() {
         final Paint paint = new Paint();
         paint.setAntiAlias(true);
diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
index cd4e300..a183546 100644
--- a/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
+++ b/java/src/com/android/inputmethod/keyboard/MoreKeysDetector.java
@@ -39,7 +39,11 @@
 
         Key nearestKey = null;
         int nearestDist = (y < 0) ? mSlideAllowanceSquareTop : mSlideAllowanceSquare;
-        for (final Key key : getKeyboard().mKeys) {
+        final Keyboard keyboard = getKeyboard();
+        if (keyboard == null) {
+            throw new NullPointerException("Keyboard isn't set");
+        }
+        for (final Key key : keyboard.mKeys) {
             final int dist = key.squaredDistanceToEdge(touchX, touchY);
             if (dist < nearestDist) {
                 nearestKey = key;
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index 5d7995d..d101aaf 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -654,9 +654,12 @@
                 --index;
                 mLookedUpString[index] = node.mCode;
                 node = node.mParent;
-            } while (node != null);
+            } while (node != null && index > 0);
 
-            if (freq >= 0) {
+            // If node is null, we have a word longer than MAX_WORD_LENGTH in the dictionary.
+            // It's a little unclear how this can happen, but just in case it does it's safer
+            // to ignore the word in this case.
+            if (freq >= 0 && node == null) {
                 suggestions.add(new SuggestedWordInfo(new String(mLookedUpString, index,
                         BinaryDictionary.MAX_WORD_LENGTH - index),
                         freq, SuggestedWordInfo.KIND_CORRECTION, mDictType));
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 8dc1081..f8a6fc8 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -553,7 +553,7 @@
 
     @Override
     public View onCreateInputView() {
-        return mKeyboardSwitcher.onCreateInputView();
+        return mKeyboardSwitcher.onCreateInputView(mIsHardwareAcceleratedDrawingEnabled);
     }
 
     @Override
@@ -1619,11 +1619,7 @@
         boolean didAutoCorrect = false;
         // Handle separator
         if (mWordComposer.isComposingWord()) {
-            // In certain languages where single quote is a separator, it's better
-            // not to auto correct, but accept the typed word. For instance,
-            // in Italian dov' should not be expanded to dove' because the elision
-            // requires the last vowel to be removed.
-            if (mCurrentSettings.mCorrectionEnabled && primaryCode != Keyboard.CODE_SINGLE_QUOTE) {
+            if (mCurrentSettings.mCorrectionEnabled) {
                 commitCurrentAutoCorrection(primaryCode);
                 didAutoCorrect = true;
             } else {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 8b53c94..5864db2 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -61,8 +61,8 @@
      * This represents an "attribute", that is either a bigram or a shortcut.
      */
     public static class WeightedString {
-        final String mWord;
-        int mFrequency;
+        public final String mWord;
+        public int mFrequency;
         public WeightedString(String word, int frequency) {
             mWord = word;
             mFrequency = frequency;
diff --git a/java/src/com/android/inputmethod/latin/makedict/Word.java b/java/src/com/android/inputmethod/latin/makedict/Word.java
index d078267..65fc72c 100644
--- a/java/src/com/android/inputmethod/latin/makedict/Word.java
+++ b/java/src/com/android/inputmethod/latin/makedict/Word.java
@@ -27,10 +27,10 @@
  * This is chiefly used to iterate a dictionary.
  */
 public class Word implements Comparable<Word> {
-    final String mWord;
-    final int mFrequency;
-    final ArrayList<WeightedString> mShortcutTargets;
-    final ArrayList<WeightedString> mBigrams;
+    public final String mWord;
+    public final int mFrequency;
+    public final ArrayList<WeightedString> mShortcutTargets;
+    public final ArrayList<WeightedString> mBigrams;
 
     private int mHashCode = 0;
 
diff --git a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
index 93e19b3..74390cc 100644
--- a/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
+++ b/native/jni/com_android_inputmethod_keyboard_ProximityInfo.cpp
@@ -16,6 +16,8 @@
 
 #define LOG_TAG "LatinIME: jni: ProximityInfo"
 
+#include <string>
+
 #include "com_android_inputmethod_keyboard_ProximityInfo.h"
 #include "jni.h"
 #include "jni_common.h"
@@ -42,10 +44,9 @@
     jfloat *sweetSpotCenterYs = safeGetFloatArrayElements(env, sweetSpotCenterYArray);
     jfloat *sweetSpotRadii = safeGetFloatArrayElements(env, sweetSpotRadiusArray);
     ProximityInfo *proximityInfo = new ProximityInfo(
-            localeStr, maxProximityCharsSize, displayWidth,
-            displayHeight, gridWidth, gridHeight, mostCommonkeyWidth,
-            (const int32_t*)proximityChars,
-            keyCount, (const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
+            localeStr, maxProximityCharsSize, displayWidth, displayHeight, gridWidth, gridHeight,
+            mostCommonkeyWidth, (const int32_t*)proximityChars, keyCount,
+            (const int32_t*)keyXCoordinates, (const int32_t*)keyYCoordinates,
             (const int32_t*)keyWidths, (const int32_t*)keyHeights, (const int32_t*)keyCharCodes,
             (const float*)sweetSpotCenterXs, (const float*)sweetSpotCenterYs,
             (const float*)sweetSpotRadii);
diff --git a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
index 2d3d963..776f5f7 100644
--- a/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
+++ b/native/jni/com_android_inputmethod_latin_BinaryDictionary.cpp
@@ -25,8 +25,9 @@
 #include "jni_common.h"
 
 #ifdef USE_MMAP_FOR_DICTIONARY
-#include <sys/mman.h>
+#include <cerrno>
 #include <fcntl.h>
+#include <sys/mman.h>
 #else // USE_MMAP_FOR_DICTIONARY
 #include <cstdlib>
 #endif // USE_MMAP_FOR_DICTIONARY
diff --git a/native/jni/jni_common.cpp b/native/jni/jni_common.cpp
index cb2351d..105a4dc 100644
--- a/native/jni/jni_common.cpp
+++ b/native/jni/jni_common.cpp
@@ -22,11 +22,8 @@
 #include "defines.h"
 #include "jni.h"
 #include "jni_common.h"
-#include "proximity_info.h"
 
 #include <cassert>
-#include <cerrno>
-#include <cstdio>
 
 using namespace latinime;
 
diff --git a/tools/dicttool/Android.mk b/tools/dicttool/Android.mk
index 9e8dbe0..e9c11ac 100644
--- a/tools/dicttool/Android.mk
+++ b/tools/dicttool/Android.mk
@@ -16,9 +16,16 @@
 LOCAL_PATH := $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under,src)
+MAKEDICT_CORE_SOURCE_DIRECTORY := ../../java/src/com/android/inputmethod/latin/makedict
+
+LOCAL_MAIN_SRC_FILES := $(call all-java-files-under,$(MAKEDICT_CORE_SOURCE_DIRECTORY))
+LOCAL_TOOL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_SRC_FILES := $(LOCAL_TOOL_SRC_FILES) \
+        $(filter-out $(addprefix %/, $(notdir $(LOCAL_TOOL_SRC_FILES))), $(LOCAL_MAIN_SRC_FILES)) \
+        $(call all-java-files-under,tests)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_MODULE := dicttool
+LOCAL_JAVA_LIBRARIES := junit
 LOCAL_MODULE_TAGS := eng
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
index 307f596..a76ec50 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Compress.java
@@ -55,11 +55,10 @@
             return "compress <filename>: Compresses a file using gzip compression";
         }
 
-        public int getArity() {
-            return 1;
-        }
-
         public void run() throws IOException {
+            if (mArgs.length < 1) {
+                throw new RuntimeException("Not enough arguments for command " + COMMAND);
+            }
             final String inFilename = mArgs[0];
             final String outFilename = inFilename + SUFFIX;
             final FileInputStream input = new FileInputStream(new File(inFilename));
@@ -79,11 +78,10 @@
             return "uncompress <filename>: Uncompresses a file compressed with gzip compression";
         }
 
-        public int getArity() {
-            return 1;
-        }
-
         public void run() throws IOException {
+            if (mArgs.length < 1) {
+                throw new RuntimeException("Not enough arguments for command " + COMMAND);
+            }
             final String inFilename = mArgs[0];
             final String outFilename = inFilename + SUFFIX;
             final FileInputStream input = new FileInputStream(new File(inFilename));
diff --git a/tools/makedict/src/com/android/inputmethod/latin/makedict/DictionaryMaker.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
similarity index 96%
rename from tools/makedict/src/com/android/inputmethod/latin/makedict/DictionaryMaker.java
rename to tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
index 5e39215..9ebd3bb 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/makedict/DictionaryMaker.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/DictionaryMaker.java
@@ -14,7 +14,12 @@
  * the License.
  */
 
-package com.android.inputmethod.latin.makedict;
+package com.android.inputmethod.latin.dicttool;
+
+import com.android.inputmethod.latin.makedict.BinaryDictInputOutput;
+import com.android.inputmethod.latin.makedict.FusionDictionary;
+import com.android.inputmethod.latin.makedict.MakedictLog;
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -102,7 +107,11 @@
         }
 
         private void displayHelp() {
-            MakedictLog.i("Usage: makedict "
+            MakedictLog.i(getHelp());
+        }
+
+        public static String getHelp() {
+            return "Usage: makedict "
                     + "[-s <unigrams.xml> [-b <bigrams.xml>] [-c <shortcuts.xml>] "
                     + "| -s <binary input>] [-d <binary output format version 2>] "
                     + "[-d1 <binary output format version 1>] [-x <xml output>] [-2]\n"
@@ -114,7 +123,7 @@
                     + "  are supported. All three can be output at the same time, but the same\n"
                     + "  output format cannot be specified several times. The behavior is\n"
                     + "  unspecified if the same file is specified for input and output, or for\n"
-                    + "  several outputs.");
+                    + "  several outputs.";
         }
 
         public Arguments(String[] argsArray) throws IOException {
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
index b78be79..c14ce7b 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Dicttool.java
@@ -16,7 +16,6 @@
 
 package com.android.inputmethod.latin.dicttool;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 
@@ -27,7 +26,6 @@
         public void setArgs(String[] args) throws IllegalArgumentException {
             mArgs = args;
         }
-        abstract public int getArity();
         abstract public String getHelp();
         abstract public void run() throws Exception;
     }
@@ -37,6 +35,7 @@
         sCommands.put("info", Info.class);
         sCommands.put("compress", Compress.Compressor.class);
         sCommands.put("uncompress", Compress.Uncompressor.class);
+        sCommands.put("makedict", Makedict.class);
     }
 
     private static Command getCommandInstance(final String commandName) {
@@ -62,59 +61,33 @@
         return sCommands.containsKey(commandName);
     }
 
-    private String mPreviousCommand = null; // local to the getNextCommand function
-    private Command getNextCommand(final ArrayList<String> arguments) {
-        final String firstArgument = arguments.get(0);
-        final String commandName;
-        if (isCommand(firstArgument)) {
-            commandName = firstArgument;
-            arguments.remove(0);
-        } else if (isCommand(mPreviousCommand)) {
-            commandName = mPreviousCommand;
-        } else {
-            throw new RuntimeException("Unknown command : " + firstArgument);
+    private Command getCommand(final String[] arguments) {
+        final String commandName = arguments[0];
+        if (!isCommand(commandName)) {
+            throw new RuntimeException("Unknown command : " + commandName);
         }
         final Command command = getCommandInstance(commandName);
-        final int arity = command.getArity();
-        if (arguments.size() < arity) {
-            throw new RuntimeException("Not enough arguments to command " + commandName);
-        }
-        final String[] argsArray = new String[arity];
-        arguments.subList(0, arity).toArray(argsArray);
-        for (int i = 0; i < arity; ++i) {
-            // For some reason, ArrayList#removeRange is protected
-            arguments.remove(0);
-        }
+        final String[] argsArray = Arrays.copyOfRange(arguments, 1, arguments.length);
         command.setArgs(argsArray);
-        mPreviousCommand = commandName;
         return command;
     }
 
-    private void execute(final ArrayList<String> arguments) {
-        ArrayList<Command> commandsToExecute = new ArrayList<Command>();
-        while (!arguments.isEmpty()) {
-            commandsToExecute.add(getNextCommand(arguments));
-        }
-        for (final Command command : commandsToExecute) {
-            try {
-                command.run();
-            } catch (Exception e) {
-                System.out.println("Exception while processing command "
-                        + command.getClass().getSimpleName() + " : " + e);
-                return;
-            }
+    private void execute(final String[] arguments) {
+        final Command command = getCommand(arguments);
+        try {
+            command.run();
+        } catch (Exception e) {
+            System.out.println("Exception while processing command "
+                    + command.getClass().getSimpleName() + " : " + e);
+            return;
         }
     }
 
-    public static void main(final String[] args) {
-        if (0 == args.length) {
+    public static void main(final String[] arguments) {
+        if (0 == arguments.length) {
             help();
             return;
         }
-        if (!isCommand(args[0])) throw new RuntimeException("Unknown command : " + args[0]);
-
-        final ArrayList<String> arguments = new ArrayList<String>(args.length);
-        arguments.addAll(Arrays.asList(args));
         new Dicttool().execute(arguments);
     }
 }
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java
index cb032dd..e592617 100644
--- a/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Info.java
@@ -17,6 +17,8 @@
 package com.android.inputmethod.latin.dicttool;
 
 public class Info extends Dicttool.Command {
+    public static final String COMMAND = "info";
+
     public Info() {
     }
 
@@ -24,12 +26,11 @@
         return "info <filename>: prints various information about a dictionary file";
     }
 
-    public int getArity() {
-        return 1;
-    }
-
     public void run() {
         // TODO: implement this
+        if (mArgs.length < 1) {
+            throw new RuntimeException("Not enough arguments for command " + COMMAND);
+        }
         System.out.println("Not implemented yet");
     }
 }
diff --git a/tools/dicttool/src/android/inputmethod/latin/dicttool/Makedict.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/Makedict.java
new file mode 100644
index 0000000..c004cfb
--- /dev/null
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/Makedict.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.inputmethod.latin.dicttool;
+
+import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import javax.xml.parsers.ParserConfigurationException;
+import org.xml.sax.SAXException;
+
+public class Makedict extends Dicttool.Command {
+    public static final String COMMAND = "makedict";
+
+    public Makedict() {
+    }
+
+    public String getHelp() {
+        return DictionaryMaker.Arguments.getHelp();
+    }
+
+    public void run() throws FileNotFoundException, IOException, ParserConfigurationException,
+            SAXException, UnsupportedFormatException {
+        DictionaryMaker.main(mArgs);
+    }
+}
diff --git a/tools/makedict/src/com/android/inputmethod/latin/makedict/MakedictLog.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/MakedictLog.java
similarity index 100%
rename from tools/makedict/src/com/android/inputmethod/latin/makedict/MakedictLog.java
rename to tools/dicttool/src/android/inputmethod/latin/dicttool/MakedictLog.java
diff --git a/tools/makedict/src/com/android/inputmethod/latin/makedict/XmlDictInputOutput.java b/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
similarity index 98%
rename from tools/makedict/src/com/android/inputmethod/latin/makedict/XmlDictInputOutput.java
rename to tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
index 52f124d..8e2e735 100644
--- a/tools/makedict/src/com/android/inputmethod/latin/makedict/XmlDictInputOutput.java
+++ b/tools/dicttool/src/android/inputmethod/latin/dicttool/XmlDictInputOutput.java
@@ -14,11 +14,13 @@
  * the License.
  */
 
-package com.android.inputmethod.latin.makedict;
+package com.android.inputmethod.latin.dicttool;
 
+import com.android.inputmethod.latin.makedict.FusionDictionary;
 import com.android.inputmethod.latin.makedict.FusionDictionary.DictionaryOptions;
 import com.android.inputmethod.latin.makedict.FusionDictionary.Node;
 import com.android.inputmethod.latin.makedict.FusionDictionary.WeightedString;
+import com.android.inputmethod.latin.makedict.Word;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java b/tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java
similarity index 100%
rename from tools/makedict/tests/com/android/inputmethod/latin/BinaryDictInputOutputTest.java
rename to tools/dicttool/tests/com/android/inputmethod/latin/makedict/BinaryDictInputOutputTest.java
diff --git a/tools/dicttool/tests/etc/test-dicttool.sh b/tools/dicttool/tests/etc/test-dicttool.sh
new file mode 100755
index 0000000..8834611
--- /dev/null
+++ b/tools/dicttool/tests/etc/test-dicttool.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# Copyright 2012, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+java -classpath ${ANDROID_HOST_OUT}/framework/junit.jar:${ANDROID_HOST_OUT}/../common/obj/JAVA_LIBRARIES/dicttool_intermediates/classes junit.textui.TestRunner com.android.inputmethod.latin.makedict.BinaryDictInputOutputTest
diff --git a/tools/makedict/Android.mk b/tools/makedict/Android.mk
index 7b5dee2..cac3a83 100644
--- a/tools/makedict/Android.mk
+++ b/tools/makedict/Android.mk
@@ -20,9 +20,7 @@
 
 LOCAL_MAIN_SRC_FILES := $(call all-java-files-under,$(MAKEDICT_CORE_SOURCE_DIRECTORY))
 LOCAL_TOOL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_SRC_FILES := $(LOCAL_TOOL_SRC_FILES) \
-        $(filter-out $(addprefix %, $(LOCAL_TOOL_SRC_FILES)), $(LOCAL_MAIN_SRC_FILES))
-LOCAL_SRC_FILES += $(call all-java-files-under,tests)
+LOCAL_SRC_FILES := $(LOCAL_TOOL_SRC_FILES)
 LOCAL_JAR_MANIFEST := etc/manifest.txt
 LOCAL_MODULE := makedict
 LOCAL_JAVA_LIBRARIES := junit