Merge "Make some constants more explicit."
diff --git a/java/proguard.flags b/java/proguard.flags
index a5a6679..701786a 100644
--- a/java/proguard.flags
+++ b/java/proguard.flags
@@ -35,6 +35,14 @@
   *;
 }
 
+-keep class com.android.inputmethod.keyboard.LatinKeyboardView {
+  # Keep getter/setter methods for ObjectAnimator
+  int getLanguageOnSpacebarAnimAlpha();
+  void setLanguageOnSpacebarAnimAlpha(int);
+  int getAltCodeKeyWhileTypingAnimAlhpa();
+  void setAltCodeKeyWhileTypingAnimAlpha(int);
+}
+
 -keep class com.android.inputmethod.keyboard.MoreKeysKeyboard$Builder$MoreKeysKeyboardParams {
   <init>(...);
 }
diff --git a/java/res/anim/alt_code_key_while_typing_fadein.xml b/java/res/anim/alt_code_key_while_typing_fadein.xml
index 3f5fd5d..f8caca3 100644
--- a/java/res/anim/alt_code_key_while_typing_fadein.xml
+++ b/java/res/anim/alt_code_key_while_typing_fadein.xml
@@ -18,8 +18,9 @@
 */
 -->
 
-<animator
+<objectAnimator
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:propertyName="altCodeKeyWhileTypingAnimAlpha"
     android:valueType="intType"
     android:duration="100"
     android:valueFrom="128"
diff --git a/java/res/anim/alt_code_key_while_typing_fadeout.xml b/java/res/anim/alt_code_key_while_typing_fadeout.xml
index ed4a6f2..bad1e74 100644
--- a/java/res/anim/alt_code_key_while_typing_fadeout.xml
+++ b/java/res/anim/alt_code_key_while_typing_fadeout.xml
@@ -18,8 +18,9 @@
 */
 -->
 
-<animator
+<objectAnimator
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:propertyName="altCodeKeyWhileTypingAnimAlpha"
     android:valueType="intType"
     android:duration="70"
     android:valueFrom="255"
diff --git a/java/res/anim/language_on_spacebar_fadeout.xml b/java/res/anim/language_on_spacebar_fadeout.xml
index f66e162..531f440 100644
--- a/java/res/anim/language_on_spacebar_fadeout.xml
+++ b/java/res/anim/language_on_spacebar_fadeout.xml
@@ -18,10 +18,11 @@
 */
 -->
 
-<animator
+<objectAnimator
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:propertyName="languageOnSpacebarAnimAlpha"
     android:valueType="intType"
     android:startOffset="1200"
     android:duration="200"
     android:valueFrom="255"
-    android:valueTo="128" />
+    android:valueTo="@integer/config_language_on_spacebar_final_alpha" />
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml
index e619ad0..cf55fbe 100644
--- a/java/res/values/attrs.xml
+++ b/java/res/values/attrs.xml
@@ -133,6 +133,7 @@
         <attr name="spacebarTextColor" format="color" />
         <attr name="spacebarTextShadowColor" format="color" />
         <!-- Fadeout animator for spacebar language label. -->
+        <attr name="languageOnSpacebarFinalAlpha" format="integer" />
         <attr name="languageOnSpacebarFadeoutAnimator" format="reference" />
         <!-- Fadeout and fadein animator for altCodeWhileTyping keys. -->
         <attr name="altCodeKeyWhileTypingFadeoutAnimator" format="reference" />
diff --git a/java/res/values/config.xml b/java/res/values/config.xml
index cccec63..1aa0dff 100644
--- a/java/res/values/config.xml
+++ b/java/res/values/config.xml
@@ -38,6 +38,7 @@
     <integer name="config_delay_update_suggestions">100</integer>
     <integer name="config_delay_update_old_suggestions">300</integer>
     <integer name="config_delay_update_shift_state">100</integer>
+    <integer name="config_language_on_spacebar_final_alpha">128</integer>
     <integer name="config_more_keys_keyboard_fadein_anim_time">0</integer>
     <integer name="config_more_keys_keyboard_fadeout_anim_time">100</integer>
     <integer name="config_keyboard_grid_width">32</integer>
diff --git a/java/res/values/styles.xml b/java/res/values/styles.xml
index 5628f27..691f98a 100644
--- a/java/res/values/styles.xml
+++ b/java/res/values/styles.xml
@@ -78,6 +78,7 @@
         <item name="longPressSpaceKeyTimeout">@integer/config_long_press_space_key_timeout</item>
         <item name="ignoreAltCodeKeyTimeout">@integer/config_ignore_alt_code_key_timeout</item>
         <item name="showMoreKeysKeyboardAtTouchedPoint">@bool/config_show_more_keys_keyboard_at_touched_point</item>
+        <item name="languageOnSpacebarFinalAlpha">@integer/config_language_on_spacebar_final_alpha</item>
         <item name="languageOnSpacebarFadeoutAnimator">@anim/language_on_spacebar_fadeout</item>
         <item name="altCodeKeyWhileTypingFadeoutAnimator">@anim/alt_code_key_while_typing_fadeout</item>
         <item name="altCodeKeyWhileTypingFadeinAnimator">@anim/alt_code_key_while_typing_fadein</item>
diff --git a/java/res/xml/rowkeys_georgian1.xml b/java/res/xml/rowkeys_georgian1.xml
index b644cdf..6b24c29 100644
--- a/java/res/xml/rowkeys_georgian1.xml
+++ b/java/res/xml/rowkeys_georgian1.xml
@@ -23,7 +23,7 @@
 >
     <switch>
         <case
-            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
         >
             <Key
                 latin:keyLabel="Q"
diff --git a/java/res/xml/rowkeys_georgian2.xml b/java/res/xml/rowkeys_georgian2.xml
index ddd375a..f50e3d6 100644
--- a/java/res/xml/rowkeys_georgian2.xml
+++ b/java/res/xml/rowkeys_georgian2.xml
@@ -23,7 +23,7 @@
 >
     <switch>
         <case
-            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
         >
             <Key
                 latin:keyLabel="A"
diff --git a/java/res/xml/rowkeys_georgian3.xml b/java/res/xml/rowkeys_georgian3.xml
index ae8443a..f908673 100644
--- a/java/res/xml/rowkeys_georgian3.xml
+++ b/java/res/xml/rowkeys_georgian3.xml
@@ -23,7 +23,7 @@
 >
     <switch>
         <case
-            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
         >
             <!-- U+10EB: "ძ" GEORGIAN LETTER JIL -->
             <Key
diff --git a/java/res/xml/rowkeys_hindi1.xml b/java/res/xml/rowkeys_hindi1.xml
index 4f7a414..fe54d9e 100644
--- a/java/res/xml/rowkeys_hindi1.xml
+++ b/java/res/xml/rowkeys_hindi1.xml
@@ -23,7 +23,7 @@
 >
     <switch>
         <case
-            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
         >
             <!-- U+0914: "औ" DEVANAGARI LETTER AU
                  U+0912/U+0902: "ऒं" DEVANAGARI LETTER SHORT O//DEVANAGARI SIGN ANUSVARA
diff --git a/java/res/xml/rowkeys_hindi2.xml b/java/res/xml/rowkeys_hindi2.xml
index 1bf6fd4..95f4881 100644
--- a/java/res/xml/rowkeys_hindi2.xml
+++ b/java/res/xml/rowkeys_hindi2.xml
@@ -23,7 +23,7 @@
 >
     <switch>
         <case
-            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
         >
             <!-- U+0913: "ओ" DEVANAGARI LETTER O
                  U+0913/U+0902: "ओं" DEVANAGARI LETTER O/DEVANAGARI SIGN ANUSVARA
diff --git a/java/res/xml/rowkeys_hindi3.xml b/java/res/xml/rowkeys_hindi3.xml
index e6b430f..7d43d57 100644
--- a/java/res/xml/rowkeys_hindi3.xml
+++ b/java/res/xml/rowkeys_hindi3.xml
@@ -23,7 +23,7 @@
 >
     <switch>
         <case
-            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLockShifted"
+            latin:keyboardSetElement="alphabetManualShifted|alphabetShiftLocked|alphabetShiftLockShifted"
         >
             <!-- U+0911: "ऑ" DEVANAGARI LETTER CANDRA O -->
             <Key
diff --git a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
index da7d01a..b66d166 100644
--- a/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/LatinKeyboardView.java
@@ -16,11 +16,8 @@
 
 package com.android.inputmethod.keyboard;
 
-import android.animation.Animator;
 import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.TypedArray;
@@ -54,7 +51,6 @@
 import com.android.inputmethod.latin.SubtypeUtils;
 import com.android.inputmethod.latin.Utils;
 import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
-import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils.LogGroup;
 
 import java.util.Locale;
 import java.util.WeakHashMap;
@@ -70,9 +66,6 @@
         SuddenJumpingTouchEventHandler.ProcessMotionEvent {
     private static final String TAG = LatinKeyboardView.class.getSimpleName();
 
-    // TODO: Kill process when the usability study mode was changed.
-    private static final boolean ENABLE_USABILITY_STUDY_LOG = LatinImeLogger.sUsabilityStudy;
-
     /** Listener for {@link KeyboardActionListener}. */
     private KeyboardActionListener mKeyboardActionListener;
 
@@ -80,12 +73,12 @@
     private Key mSpaceKey;
     private Drawable mSpaceIcon;
     // Stuff to draw language name on spacebar.
-    private ValueAnimator mLanguageOnSpacebarFadeoutAnimator;
-    private int mFinalAlphaOfLanguageOnSpacebar;
+    private final int mLanguageOnSpacebarFinalAlpha;
+    private ObjectAnimator mLanguageOnSpacebarFadeoutAnimator;
     private static final int ALPHA_OPAQUE = 255;
     private boolean mNeedsToDisplayLanguage;
     private Locale mSpacebarLocale;
-    private int mSpacebarTextAlpha = ALPHA_OPAQUE;
+    private int mLanguageOnSpacebarAnimAlpha = ALPHA_OPAQUE;
     private final float mSpacebarTextRatio;
     private float mSpacebarTextSize;
     private final int mSpacebarTextColor;
@@ -100,8 +93,8 @@
     private static final int SPACE_LED_LENGTH_PERCENT = 80;
 
     // Stuff to draw altCodeWhileTyping keys.
-    private ValueAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
-    private ValueAnimator mAltCodeKeyWhileTypingFadeinAnimator;
+    private ObjectAnimator mAltCodeKeyWhileTypingFadeoutAnimator;
+    private ObjectAnimator mAltCodeKeyWhileTypingFadeinAnimator;
     private int mAltCodeKeyWhileTypingAnimAlpha = ALPHA_OPAQUE;
 
     // More keys keyboard
@@ -231,8 +224,8 @@
             removeMessages(MSG_LONGPRESS_KEY);
         }
 
-        private static void cancelAndStartAnimators(ValueAnimator animatorToCancel,
-                ValueAnimator animatorToStart) {
+        public static void cancelAndStartAnimators(ObjectAnimator animatorToCancel,
+                ObjectAnimator animatorToStart) {
             if (animatorToCancel != null && animatorToCancel.isStarted()) {
                 animatorToCancel.cancel();
             }
@@ -366,6 +359,8 @@
         mSpacebarTextColor = a.getColor(R.styleable.LatinKeyboardView_spacebarTextColor, 0);
         mSpacebarTextShadowColor = a.getColor(
                 R.styleable.LatinKeyboardView_spacebarTextShadowColor, 0);
+        mLanguageOnSpacebarFinalAlpha = a.getInt(
+                R.styleable.LatinKeyboardView_languageOnSpacebarFinalAlpha, ALPHA_OPAQUE);
         final int languageOnSpacebarFadeoutAnimatorResId = a.getResourceId(
                 R.styleable.LatinKeyboardView_languageOnSpacebarFadeoutAnimator, 0);
         final int altCodeKeyWhileTypingFadeoutAnimatorResId = a.getResourceId(
@@ -387,55 +382,41 @@
 
         PointerTracker.setParameters(mPointerTrackerParams);
 
-        final ValueAnimator animator = loadValueAnimator(languageOnSpacebarFadeoutAnimatorResId);
-        if (animator != null) {
-            animator.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    mSpacebarTextAlpha = (Integer)animation.getAnimatedValue();
-                    invalidateKey(mSpaceKey);
-                }
-            });
-            animator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator a) {
-                    final ValueAnimator valueAnimator = (ValueAnimator)a;
-                    mFinalAlphaOfLanguageOnSpacebar = (Integer)valueAnimator.getAnimatedValue();
-                }
-            });
-            // In order to get the final value of animator.
-            animator.end();
-        }
-        mLanguageOnSpacebarFadeoutAnimator = animator;
-
-        final ValueAnimator fadeout = loadValueAnimator(altCodeKeyWhileTypingFadeoutAnimatorResId);
-        if (fadeout != null) {
-            fadeout.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    mAltCodeKeyWhileTypingAnimAlpha = (Integer)animation.getAnimatedValue();
-                    updateAltCodeKeyWhileTyping();
-                }
-            });
-        }
-        mAltCodeKeyWhileTypingFadeoutAnimator = fadeout;
-
-        final ValueAnimator fadein = loadValueAnimator(altCodeKeyWhileTypingFadeinAnimatorResId);
-        if (fadein != null) {
-            fadein.addUpdateListener(new AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    mAltCodeKeyWhileTypingAnimAlpha = (Integer)animation.getAnimatedValue();
-                    updateAltCodeKeyWhileTyping();
-                }
-            });
-        }
-        mAltCodeKeyWhileTypingFadeinAnimator = fadein;
+        mLanguageOnSpacebarFadeoutAnimator = loadObjectAnimator(
+                languageOnSpacebarFadeoutAnimatorResId, this);
+        mAltCodeKeyWhileTypingFadeoutAnimator = loadObjectAnimator(
+                altCodeKeyWhileTypingFadeoutAnimatorResId, this);
+        mAltCodeKeyWhileTypingFadeinAnimator = loadObjectAnimator(
+                altCodeKeyWhileTypingFadeinAnimatorResId, this);
     }
 
-    private ValueAnimator loadValueAnimator(int resId) {
+    private ObjectAnimator loadObjectAnimator(int resId, Object target) {
         if (resId == 0) return null;
-        return (ValueAnimator)AnimatorInflater.loadAnimator(getContext(), resId);
+        final ObjectAnimator animator = (ObjectAnimator)AnimatorInflater.loadAnimator(
+                getContext(), resId);
+        if (animator != null) {
+            animator.setTarget(target);
+        }
+        return animator;
+    }
+
+    // Getter/setter methods for {@link ObjectAnimator}.
+    public int getLanguageOnSpacebarAnimAlpha() {
+        return mLanguageOnSpacebarAnimAlpha;
+    }
+
+    public void setLanguageOnSpacebarAnimAlpha(int alpha) {
+        mLanguageOnSpacebarAnimAlpha = alpha;
+        invalidateKey(mSpaceKey);
+    }
+
+    public int getAltCodeKeyWhileTypingAnimAlpha() {
+        return mAltCodeKeyWhileTypingAnimAlpha;
+    }
+
+    public void setAltCodeKeyWhileTypingAnimAlpha(int alpha) {
+        mAltCodeKeyWhileTypingAnimAlpha = alpha;
+        updateAltCodeKeyWhileTyping();
     }
 
     public void setKeyboardActionListener(KeyboardActionListener listener) {
@@ -672,6 +653,8 @@
         final int index = me.getActionIndex();
         final int id = me.getPointerId(index);
         final int x, y;
+        final float size = me.getSize(index);
+        final float pressure = me.getPressure(index);
         if (mMoreKeysPanel != null && id == mMoreKeysPanelPointerTrackerId) {
             x = mMoreKeysPanel.translateX((int)me.getX(index));
             y = mMoreKeysPanel.translateY((int)me.getY(index));
@@ -679,32 +662,11 @@
             x = (int)me.getX(index);
             y = (int)me.getY(index);
         }
-        if (ENABLE_USABILITY_STUDY_LOG) {
-            final String eventTag;
-            switch (action) {
-                case MotionEvent.ACTION_UP:
-                    eventTag = "[Up]";
-                    break;
-                case MotionEvent.ACTION_DOWN:
-                    eventTag = "[Down]";
-                    break;
-                case MotionEvent.ACTION_POINTER_UP:
-                    eventTag = "[PointerUp]";
-                    break;
-                case MotionEvent.ACTION_POINTER_DOWN:
-                    eventTag = "[PointerDown]";
-                    break;
-                case MotionEvent.ACTION_MOVE: // Skip this as being logged below
-                    eventTag = "";
-                    break;
-                default:
-                    eventTag = "[Action" + action + "]";
-                    break;
-            }
-            if (!TextUtils.isEmpty(eventTag)) {
-                UsabilityStudyLogUtils.getInstance().write(LogGroup.MOTION_EVENT,
-                        eventTag + eventTime + "," + id + "," + x + "," + y + ","
-                        + me.getSize(index) + "," + me.getPressure(index));
+        if (LatinImeLogger.sUsabilityStudy) {
+            if (action != MotionEvent.ACTION_MOVE) {
+                // Skip ACTION_MOVE events as they are logged below
+                UsabilityStudyLogUtils.getInstance().writeMotionEvent(action, eventTime, id, x,
+                        y, size, pressure);
             }
         }
 
@@ -764,11 +726,9 @@
                     py = (int)me.getY(i);
                 }
                 tracker.onMoveEvent(px, py, eventTime);
-                if (ENABLE_USABILITY_STUDY_LOG) {
-                    UsabilityStudyLogUtils.getInstance().write(
-                            LogGroup.MOTION_EVENT,
-                            "[Move]" + eventTime + "," + me.getPointerId(i) + "," + px + "," + py
-                                    + "," + me.getSize(i) + "," + me.getPressure(i));
+                if (LatinImeLogger.sUsabilityStudy) {
+                    UsabilityStudyLogUtils.getInstance().writeMotionEvent(action, eventTime, id,
+                            px, py, size, pressure);
                 }
             }
         } else {
@@ -861,19 +821,21 @@
 
     public void startDisplayLanguageOnSpacebar(boolean subtypeChanged,
             boolean needsToDisplayLanguage) {
-        final ValueAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
-        if (animator != null) {
-           animator.cancel();
-        }
+        final ObjectAnimator animator = mLanguageOnSpacebarFadeoutAnimator;
         mNeedsToDisplayLanguage = needsToDisplayLanguage;
         if (animator == null) {
             mNeedsToDisplayLanguage = false;
         } else {
             if (subtypeChanged && needsToDisplayLanguage) {
-                mSpacebarTextAlpha = ALPHA_OPAQUE;
+                setLanguageOnSpacebarAnimAlpha(ALPHA_OPAQUE);
+                if (animator.isStarted()) {
+                    animator.cancel();
+                }
                 animator.start();
             } else {
-                mSpacebarTextAlpha = mFinalAlphaOfLanguageOnSpacebar;
+                if (!animator.isStarted()) {
+                    mLanguageOnSpacebarAnimAlpha = mLanguageOnSpacebarFinalAlpha;
+                }
             }
         }
         invalidateKey(mSpaceKey);
@@ -967,10 +929,10 @@
             final float textHeight = -paint.ascent() + descent;
             final float baseline = height / 2 + textHeight / 2;
             paint.setColor(mSpacebarTextShadowColor);
-            paint.setAlpha(mSpacebarTextAlpha);
+            paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
             canvas.drawText(language, width / 2, baseline - descent - 1, paint);
             paint.setColor(mSpacebarTextColor);
-            paint.setAlpha(mSpacebarTextAlpha);
+            paint.setAlpha(mLanguageOnSpacebarAnimAlpha);
             canvas.drawText(language, width / 2, baseline - descent, paint);
         }
 
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index e67f0ea..48fb798 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -68,6 +68,7 @@
 import com.android.inputmethod.keyboard.KeyboardSwitcher;
 import com.android.inputmethod.keyboard.KeyboardView;
 import com.android.inputmethod.keyboard.LatinKeyboardView;
+import com.android.inputmethod.latin.Utils.UsabilityStudyLogUtils;
 import com.android.inputmethod.latin.suggestions.SuggestionsView;
 
 import java.io.FileDescriptor;
@@ -1266,6 +1267,11 @@
             mDeleteCount = 0;
         }
         mLastKeyTime = when;
+
+        if (LatinImeLogger.sUsabilityStudy) {
+            UsabilityStudyLogUtils.getInstance().writeKeyEvent(primaryCode, x, y);
+        }
+
         final KeyboardSwitcher switcher = mKeyboardSwitcher;
         // The space state depends only on the last character pressed and its own previous
         // state. Here, we revert the space state to neutral if the key is actually modifying
diff --git a/java/src/com/android/inputmethod/latin/Utils.java b/java/src/com/android/inputmethod/latin/Utils.java
index e2ce083..a3589da 100644
--- a/java/src/com/android/inputmethod/latin/Utils.java
+++ b/java/src/com/android/inputmethod/latin/Utils.java
@@ -31,7 +31,9 @@
 import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.util.Log;
+import android.view.MotionEvent;
 
+import com.android.inputmethod.keyboard.Keyboard;
 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
 
 import java.io.BufferedReader;
@@ -138,9 +140,6 @@
         // TODO: accept code points
         public void push(char c, int x, int y) {
             if (!mEnabled) return;
-            if (mUsabilityStudy) {
-                UsabilityStudyLogUtils.getInstance().writeChar(c, x, y);
-            }
             mCharBuf[mEnd] = c;
             mXBuf[mEnd] = x;
             mYBuf[mEnd] = y;
@@ -279,30 +278,57 @@
             }
         }
 
-        public static void writeBackSpace(int x, int y) {
-            UsabilityStudyLogUtils.getInstance().write(
-                    LogGroup.KEY, "<backspace>\t" + x + "\t" + y);
+        public void writeMotionEvent(final int action, final long eventTime, final int id,
+                final int x, final int y, final float size, final float pressure) {
+            final String eventTag;
+            switch (action) {
+                case MotionEvent.ACTION_CANCEL: eventTag = "[Cancel]"; break;
+                case MotionEvent.ACTION_UP: eventTag = "[Up]"; break;
+                case MotionEvent.ACTION_DOWN: eventTag = "[Down]"; break;
+                case MotionEvent.ACTION_POINTER_UP: eventTag = "[PointerUp]"; break;
+                case MotionEvent.ACTION_POINTER_DOWN: eventTag = "[PointerDown]"; break;
+                case MotionEvent.ACTION_MOVE: eventTag = "[Move]"; break;
+                case MotionEvent.ACTION_OUTSIDE: eventTag = "[Outside]"; break;
+                default: eventTag = "[Action" + action + "]"; break;
+            }
+            if (!TextUtils.isEmpty(eventTag)) {
+                StringBuilder sb = new StringBuilder();
+                sb.append(eventTag);
+                sb.append('\t'); sb.append(eventTime);
+                sb.append('\t'); sb.append(id);
+                sb.append('\t'); sb.append(x);
+                sb.append('\t'); sb.append(y);
+                sb.append('\t'); sb.append(size);
+                sb.append('\t'); sb.append(pressure);
+                write(LogGroup.MOTION_EVENT, sb.toString());
+            }
         }
 
-        public void writeChar(char c, int x, int y) {
-            String inputChar = String.valueOf(c);
-            switch (c) {
-                case '\n':
-                    inputChar = "<enter>";
-                    break;
-                case '\t':
-                    inputChar = "<tab>";
-                    break;
-                case ' ':
-                    inputChar = "<space>";
-                    break;
-            }
-            UsabilityStudyLogUtils.getInstance().write(LogGroup.KEY,
-                    inputChar + "\t" + x + "\t" + y);
+        public void writeKeyEvent(int code, int x, int y) {
+            final StringBuilder sb = new StringBuilder();
+            sb.append(Keyboard.printableCode(code));
+            sb.append('\t'); sb.append(x);
+            sb.append('\t'); sb.append(y);
+            write(LogGroup.KEY, sb.toString());
+
+            // TODO: replace with a cleaner flush+retrieve mechanism
             LatinImeLogger.onPrintAllUsabilityStudyLogs();
         }
 
-        public void write(final LogGroup logGroup, final String log) {
+        public void writeCorrection(String subgroup, String before, String after, int position) {
+            final StringBuilder sb = new StringBuilder();
+            sb.append(subgroup);
+            sb.append('\t'); sb.append(before);
+            sb.append('\t'); sb.append(after);
+            sb.append('\t'); sb.append(position);
+            write(LogGroup.CORRECTION, sb.toString());
+        }
+
+        public void writeStateChange(String subgroup, String details) {
+            write(LogGroup.STATE_CHANGE, subgroup + "\t" + details);
+        }
+
+        private void write(final LogGroup logGroup, final String log) {
             mLoggingHandler.post(new Runnable() {
                 @Override
                 public void run() {
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index d196721..e88ab68 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -21,7 +21,6 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.LinkedList;
-import java.util.List;
 
 /**
  * A dictionary that can fusion heads and tails of words for more compression.
@@ -400,16 +399,11 @@
      * is ignored.
      * This comparator imposes orderings that are inconsistent with equals.
      */
-    static private class CharGroupComparator implements java.util.Comparator {
-        public int compare(Object o1, Object o2) {
-            final CharGroup c1 = (CharGroup)o1;
-            final CharGroup c2 = (CharGroup)o2;
+    static private class CharGroupComparator implements java.util.Comparator<CharGroup> {
+        public int compare(CharGroup c1, CharGroup c2) {
             if (c1.mChars[0] == c2.mChars[0]) return 0;
             return c1.mChars[0] < c2.mChars[0] ? -1 : 1;
         }
-        public boolean equals(Object o) {
-            return o instanceof CharGroupComparator;
-        }
     }
     final static private CharGroupComparator CHARGROUP_COMPARATOR = new CharGroupComparator();
 
@@ -417,7 +411,7 @@
      * Finds the insertion index of a character within a node.
      */
     private static int findInsertionIndex(final Node node, int character) {
-        final List data = node.mData;
+        final ArrayList<CharGroup> data = node.mData;
         final CharGroup reference = new CharGroup(new int[] { character }, null, null, 0,
                 false /* isShortcutOnly */);
         int result = Collections.binarySearch(data, reference, CHARGROUP_COMPARATOR);
diff --git a/native/src/unigram_dictionary.cpp b/native/src/unigram_dictionary.cpp
index a0b7238..563541d 100644
--- a/native/src/unigram_dictionary.cpp
+++ b/native/src/unigram_dictionary.cpp
@@ -30,9 +30,9 @@
 namespace latinime {
 
 const UnigramDictionary::digraph_t UnigramDictionary::GERMAN_UMLAUT_DIGRAPHS[] =
-        { { 'a', 'e' },
-        { 'o', 'e' },
-        { 'u', 'e' } };
+        { { 'a', 'e', 0x00E4 }, // U+00E4 : LATIN SMALL LETTER A WITH DIAERESIS
+        { 'o', 'e', 0x00F6 }, // U+00F6 : LATIN SMALL LETTER O WITH DIAERESIS
+        { 'u', 'e', 0x00FC } }; // U+00FC : LATIN SMALL LETTER U WITH DIAERESIS
 
 // TODO: check the header
 UnigramDictionary::UnigramDictionary(const uint8_t* const streamStart, int typedLetterMultiplier,
@@ -64,7 +64,8 @@
     queue->push(frequency, word, length);
 }
 
-bool UnigramDictionary::isDigraph(const int *codes, const int i, const int codesSize,
+// Return the replacement code point for a digraph, or 0 if none.
+int UnigramDictionary::getDigraphReplacement(const int *codes, const int i, const int codesSize,
         const digraph_t* const digraphs, const unsigned int digraphsSize) const {
 
     // There can't be a digraph if we don't have at least 2 characters to examine
@@ -77,10 +78,14 @@
         if (thisChar == digraphs[lastDigraphIndex].first) break;
     }
     // No match: return early
-    if (lastDigraphIndex < 0) return false;
+    if (lastDigraphIndex < 0) return 0;
 
     // It's an interesting digraph if the second char matches too.
-    return digraphs[lastDigraphIndex].second == codes[(i + 1) * MAX_PROXIMITY_CHARS];
+    if (digraphs[lastDigraphIndex].second == codes[(i + 1) * MAX_PROXIMITY_CHARS]) {
+        return digraphs[lastDigraphIndex].replacement;
+    } else {
+        return 0;
+    }
 }
 
 // Mostly the same arguments as the non-recursive version, except:
@@ -102,16 +107,23 @@
         for (int i = 0; i < codesRemain; ++i) {
             xCoordinatesBuffer[startIndex + i] = xcoordinates[codesBufferSize - codesRemain + i];
             yCoordinatesBuffer[startIndex + i] = ycoordinates[codesBufferSize - codesRemain + i];
-            if (isDigraph(codesSrc, i, codesRemain, digraphs, digraphsSize)) {
+            const int replacementCodePoint =
+                    getDigraphReplacement(codesSrc, i, codesRemain, digraphs, digraphsSize);
+            if (0 != replacementCodePoint) {
                 // Found a digraph. We will try both spellings. eg. the word is "pruefen"
 
-                // Copy the word up to the first char of the digraph, then continue processing
-                // on the remaining part of the word, skipping the second char of the digraph.
-                // In our example, copy "pru" and continue running on "fen"
+                // Copy the word up to the first char of the digraph, including proximity chars,
+                // and overwrite the primary code with the replacement code point. Then, continue
+                // processing on the remaining part of the word, skipping the second char of the
+                // digraph.
+                // In our example, copy "pru", replace "u" with the version with the diaeresis and
+                // continue running on "fen".
                 // Make i the index of the second char of the digraph for simplicity. Forgetting
                 // to do that results in an infinite recursion so take care!
                 ++i;
                 memcpy(codesDest, codesSrc, i * BYTES_IN_ONE_CHAR);
+                codesDest[(i - 1) * (BYTES_IN_ONE_CHAR / sizeof(codesDest[0]))] =
+                        replacementCodePoint;
                 getWordWithDigraphSuggestionsRec(proximityInfo, xcoordinates, ycoordinates,
                         codesBuffer, xCoordinatesBuffer, yCoordinatesBuffer, codesBufferSize, flags,
                         codesSrc + (i + 1) * MAX_PROXIMITY_CHARS, codesRemain - i - 1,
diff --git a/native/src/unigram_dictionary.h b/native/src/unigram_dictionary.h
index 5b8b045..b85913f 100644
--- a/native/src/unigram_dictionary.h
+++ b/native/src/unigram_dictionary.h
@@ -29,7 +29,7 @@
 
 class TerminalAttributes;
 class UnigramDictionary {
-    typedef struct { int first; int second; } digraph_t;
+    typedef struct { int first; int second; int replacement; } digraph_t;
 
  public:
     // Mask and flags for children address type selection.
@@ -88,7 +88,7 @@
     void getWordSuggestions(ProximityInfo *proximityInfo, const int *xcoordinates,
             const int *ycoordinates, const int *codes, const int inputLength,
             const int flags, Correction *correction, WordsPriorityQueuePool *queuePool);
-    bool isDigraph(const int *codes, const int i, const int codesSize,
+    int getDigraphReplacement(const int *codes, const int i, const int codesSize,
             const digraph_t* const digraphs, const unsigned int digraphsSize) const;
     void getWordWithDigraphSuggestionsRec(ProximityInfo *proximityInfo,
         const int *xcoordinates, const int* ycoordinates, const int *codesBuffer,
diff --git a/tools/makedict/Android.mk b/tools/makedict/Android.mk
index facbbf7..dcfad19 100644
--- a/tools/makedict/Android.mk
+++ b/tools/makedict/Android.mk
@@ -12,16 +12,19 @@
 # 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.
-#
-#LOCAL_PATH := $(call my-dir)
-#include $(CLEAR_VARS)
-#
-#LOCAL_SRC_FILES := $(call all-java-files-under,src)
-#LOCAL_SRC_FILES += $(call all-java-files-under,tests)
-#LOCAL_JAR_MANIFEST := etc/manifest.txt
-#LOCAL_MODULE_TAGS := eng
-#LOCAL_MODULE := makedict
-#LOCAL_JAVA_LIBRARIES := junit
-#
-#include $(BUILD_HOST_JAVA_LIBRARY)
-#include $(LOCAL_PATH)/etc/Android.mk
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+MAKEDICT_CORE_SOURCE_DIRECTORY := ../../java/src/com/android/inputmethod/latin/makedict
+
+LOCAL_SRC_FILES := $(call all-java-files-under,$(MAKEDICT_CORE_SOURCE_DIRECTORY))
+LOCAL_SRC_FILES += $(call all-java-files-under,src)
+LOCAL_SRC_FILES += $(call all-java-files-under,tests)
+LOCAL_JAR_MANIFEST := etc/manifest.txt
+LOCAL_MODULE_TAGS := eng
+LOCAL_MODULE := makedict
+LOCAL_JAVA_LIBRARIES := junit
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+include $(LOCAL_PATH)/etc/Android.mk