Merge "Always record word boundaries"
diff --git a/java/res/values-es/strings.xml b/java/res/values-es/strings.xml
index c4e7268..cbb07dd 100644
--- a/java/res/values-es/strings.xml
+++ b/java/res/values-es/strings.xml
@@ -131,7 +131,7 @@
     <string name="language_selection_title" msgid="1651299598555326750">"Idiomas"</string>
     <string name="send_feedback" msgid="1780431884109392046">"Danos tu opinión"</string>
     <string name="select_language" msgid="3693815588777926848">"Idiomas de introducción"</string>
-    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toca otra vez para guardar."</string>
+    <string name="hint_add_to_dictionary" msgid="573678656946085380">"Toca otra vez para guardar"</string>
     <string name="has_dictionary" msgid="6071847973466625007">"Hay un diccionario disponible"</string>
     <string name="prefs_enable_log" msgid="6620424505072963557">"Habilitar comentarios de usuarios"</string>
     <string name="prefs_description_log" msgid="7525225584555429211">"Ayuda a mejorar este editor de método de introducción de texto enviando estadísticas de uso e informes de error."</string>
diff --git a/java/res/values-mn/strings-appname.xml b/java/res/values-mn/strings-appname.xml
new file mode 100644
index 0000000..6c27e3d
--- /dev/null
+++ b/java/res/values-mn/strings-appname.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+/*
+**
+** Copyright 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.
+*/
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="english_ime_name" msgid="5940510615957428904">"Андройд Гар (AOSP)"</string>
+    <string name="spell_checker_service_name" msgid="1254221805440242662">"Андройд Алдаа Шалгагч (AOSP)"</string>
+    <string name="english_ime_settings" msgid="5760361067176802794">"Андройд Гарын Тохиргоо (AOSP)"</string>
+    <string name="android_spell_checker_settings" msgid="6123949487832861885">"Андройд Алдаа Шалгагчийн Тохиргоо (AOSP)"</string>
+</resources>
diff --git a/java/src/com/android/inputmethod/compat/AppWorkaroundsHelper.java b/java/src/com/android/inputmethod/compat/AppWorkaroundsHelper.java
new file mode 100644
index 0000000..21535e4
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/AppWorkaroundsHelper.java
@@ -0,0 +1,29 @@
+/*
+ * 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.compat;
+
+import android.content.pm.PackageInfo;
+
+public class AppWorkaroundsHelper {
+    private AppWorkaroundsHelper() {
+        // This helper class is not publicly instantiable.
+    }
+
+    public static boolean evaluateIsBrokenByRecorrection(final PackageInfo info) {
+        return false;
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java b/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java
new file mode 100644
index 0000000..7e9e2e3
--- /dev/null
+++ b/java/src/com/android/inputmethod/compat/AppWorkaroundsUtils.java
@@ -0,0 +1,60 @@
+/*
+ * 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.compat;
+
+import android.content.pm.PackageInfo;
+import android.os.Build.VERSION_CODES;
+
+/**
+ * A class to encapsulate work-arounds specific to particular apps.
+ */
+public class AppWorkaroundsUtils {
+    private PackageInfo mPackageInfo; // May be null
+    private boolean mIsBrokenByRecorrection = false;
+
+    public void setPackageInfo(final PackageInfo packageInfo) {
+        mPackageInfo = packageInfo;
+        mIsBrokenByRecorrection = AppWorkaroundsHelper.evaluateIsBrokenByRecorrection(
+                packageInfo);
+    }
+
+    public boolean isBrokenByRecorrection() {
+        return mIsBrokenByRecorrection;
+    }
+
+    public boolean isBeforeJellyBean() {
+        if (null == mPackageInfo || null == mPackageInfo.applicationInfo) {
+            return false;
+        }
+        return mPackageInfo.applicationInfo.targetSdkVersion < VERSION_CODES.JELLY_BEAN;
+    }
+
+    @Override
+    public String toString() {
+        if (null == mPackageInfo || null == mPackageInfo.applicationInfo) {
+            return "";
+        }
+        final StringBuilder s = new StringBuilder();
+        s.append("Target application : ")
+                .append(mPackageInfo.applicationInfo.name)
+                .append("\nPackage : ")
+                .append(mPackageInfo.applicationInfo.packageName)
+                .append("\nTarget app sdk version : ")
+                .append(mPackageInfo.applicationInfo.targetSdkVersion);
+        return s.toString();
+    }
+}
diff --git a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
index df2e22f..965a2a8 100644
--- a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
+++ b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java
@@ -21,16 +21,15 @@
 public final class IntentCompatUtils {
     // Note that Intent.ACTION_USER_INITIALIZE have been introduced in API level 17
     // (Build.VERSION_CODE.JELLY_BEAN_MR1).
-    public static final String ACTION_USER_INITIALIZE =
-            (String)CompatUtils.getFieldValue(null, null,
+    private static final String ACTION_USER_INITIALIZE =
+            (String)CompatUtils.getFieldValue(null /* receiver */, null /* defaultValue */,
                     CompatUtils.getField(Intent.class, "ACTION_USER_INITIALIZE"));
 
     private IntentCompatUtils() {
         // This utility class is not publicly instantiable.
     }
 
-    public static boolean has_ACTION_USER_INITIALIZE(final Intent intent) {
-        return ACTION_USER_INITIALIZE != null && intent != null
-                && ACTION_USER_INITIALIZE.equals(intent.getAction());
+    public static boolean is_ACTION_USER_INITIALIZE(final String action) {
+        return ACTION_USER_INITIALIZE != null && ACTION_USER_INITIALIZE.equals(action);
     }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
index 1fe23a3..d4051f7 100644
--- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
+++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java
@@ -36,6 +36,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import com.android.inputmethod.annotations.UsedForTesting;
 import com.android.inputmethod.compat.EditorInfoCompatUtils;
 import com.android.inputmethod.keyboard.internal.KeyboardBuilder;
 import com.android.inputmethod.keyboard.internal.KeyboardParams;
@@ -424,6 +425,7 @@
                 SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false);
     }
 
+    @UsedForTesting
     public static KeyboardLayoutSet createKeyboardSetForTest(final Context context,
             final InputMethodSubtype subtype, final int orientation,
             final boolean testCasesHaveTouchCoordinates) {
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index b31f00b..8deadbf 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -58,7 +58,7 @@
         }
 
         private static double degreeToRadian(final int degree) {
-            return (double)degree / 180.0d * Math.PI;
+            return degree / 180.0d * Math.PI;
         }
 
         public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) {
@@ -125,8 +125,18 @@
 
     }
 
+    /**
+     * Append sampled preview points.
+     *
+     * @param eventTimes the event time array of gesture trail to be drawn.
+     * @param xCoords the x-coordinates array of gesture trail to be drawn.
+     * @param yCoords the y-coordinates array of gesture trail to be drawn.
+     * @param types the point types array of gesture trail. This is valid only when
+     * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
+     */
     public void appendPreviewStroke(final ResizableIntArray eventTimes,
-            final ResizableIntArray xCoords, final ResizableIntArray yCoords) {
+            final ResizableIntArray xCoords, final ResizableIntArray yCoords,
+            final ResizableIntArray types) {
         final int length = mPreviewEventTimes.getLength() - mLastPreviewSize;
         if (length <= 0) {
             return;
@@ -134,6 +144,9 @@
         eventTimes.append(mPreviewEventTimes, mLastPreviewSize, length);
         xCoords.append(mPreviewXCoordinates, mLastPreviewSize, length);
         yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
+        if (GestureTrail.DEBUG_SHOW_POINTS) {
+            types.fill(GestureTrail.POINT_TYPE_SAMPLED, types.getLength(), length);
+        }
         mLastPreviewSize = mPreviewEventTimes.getLength();
     }
 
@@ -148,6 +161,8 @@
      * @param eventTimes the event time array of gesture trail to be drawn.
      * @param xCoords the x-coordinates array of gesture trail to be drawn.
      * @param yCoords the y-coordinates array of gesture trail to be drawn.
+     * @param types the point types array of gesture trail. This is valid only when
+     * {@link GestureTrail#DEBUG_SHOW_POINTS} is true.
      * @return the start index of the last interpolated segment of input arrays.
      */
     public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
@@ -189,7 +204,7 @@
                 eventTimes.add(d1, (int)(dt * t) + t1);
                 xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
                 yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
-                if (GestureTrail.DBG_SHOW_POINTS) {
+                if (GestureTrail.DEBUG_SHOW_POINTS) {
                     types.add(d1, GestureTrail.POINT_TYPE_INTERPOLATED);
                 }
                 d1++;
@@ -197,7 +212,7 @@
             eventTimes.add(d1, pt[p2]);
             xCoords.add(d1, px[p2]);
             yCoords.add(d1, py[p2]);
-            if (GestureTrail.DBG_SHOW_POINTS) {
+            if (GestureTrail.DEBUG_SHOW_POINTS) {
                 types.add(d1, GestureTrail.POINT_TYPE_SAMPLED);
             }
         }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
index 03dd1c3..0f3cd78 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrail.java
@@ -36,10 +36,11 @@
  * @attr ref R.styleable#MainKeyboardView_gestureTrailWidth
  */
 final class GestureTrail {
-    public static final boolean DBG_SHOW_POINTS = false;
-    public static final int POINT_TYPE_SAMPLED = 0;
-    public static final int POINT_TYPE_INTERPOLATED = 1;
-    public static final int POINT_TYPE_COMPROMISED = 2;
+    public static final boolean DEBUG_SHOW_POINTS = false;
+    public static final int POINT_TYPE_SAMPLED = 1;
+    public static final int POINT_TYPE_INTERPOLATED = 2;
+    private static final int FADEOUT_START_DELAY_FOR_DEBUG = 2000; // millisecond
+    private static final int FADEOUT_DURATION_FOR_DEBUG = 200; // millisecond
 
     private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY;
 
@@ -48,7 +49,7 @@
     private final ResizableIntArray mYCoordinates = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mEventTimes = new ResizableIntArray(DEFAULT_CAPACITY);
     private final ResizableIntArray mPointTypes = new ResizableIntArray(
-            DBG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
+            DEBUG_SHOW_POINTS ? DEFAULT_CAPACITY : 0);
     private int mCurrentStrokeId = -1;
     // The wall time of the zero value in {@link #mEventTimes}
     private long mCurrentTimeBase;
@@ -83,10 +84,12 @@
                     R.styleable.MainKeyboardView_gestureTrailShadowRatio, 0);
             mTrailShadowEnabled = (trailShadowRatioInt > 0);
             mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT;
-            mFadeoutStartDelay = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
-            mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt(
-                    R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
+            mFadeoutStartDelay = DEBUG_SHOW_POINTS ? FADEOUT_START_DELAY_FOR_DEBUG
+                    : mainKeyboardViewAttr.getInt(
+                            R.styleable.MainKeyboardView_gestureTrailFadeoutStartDelay, 0);
+            mFadeoutDuration = DEBUG_SHOW_POINTS ? FADEOUT_DURATION_FOR_DEBUG
+                    : mainKeyboardViewAttr.getInt(
+                            R.styleable.MainKeyboardView_gestureTrailFadeoutDuration, 0);
             mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration;
             mUpdateInterval = mainKeyboardViewAttr.getInt(
                     R.styleable.MainKeyboardView_gestureTrailUpdateInterval, 0);
@@ -117,7 +120,7 @@
 
     private void addStrokeLocked(final GestureStrokeWithPreviewPoints stroke, final long downTime) {
         final int trailSize = mEventTimes.getLength();
-        stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates);
+        stroke.appendPreviewStroke(mEventTimes, mXCoordinates, mYCoordinates, mPointTypes);
         if (mEventTimes.getLength() == trailSize) {
             return;
         }
@@ -255,23 +258,15 @@
                         final int alpha = getAlpha(elapsedTime, params);
                         paint.setAlpha(alpha);
                         canvas.drawPath(path, paint);
-                        if (DBG_SHOW_POINTS) {
-                            if (pointTypes[i] == POINT_TYPE_INTERPOLATED) {
-                                paint.setColor(Color.RED);
-                            } else if (pointTypes[i] == POINT_TYPE_SAMPLED) {
-                                paint.setColor(0xFFA000FF);
-                            } else {
-                                paint.setColor(Color.GREEN);
-                            }
-                            canvas.drawCircle(p1x - 1, p1y - 1, 2, paint);
-                            paint.setColor(params.mTrailColor);
-                        }
                     }
                 }
                 p1x = p2x;
                 p1y = p2y;
                 r1 = r2;
             }
+            if (DEBUG_SHOW_POINTS) {
+                debugDrawPoints(canvas, startIndex, trailSize, paint);
+            }
         }
 
         final int newSize = trailSize - startIndex;
@@ -281,11 +276,14 @@
                 System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
                 System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
                 System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+                if (DEBUG_SHOW_POINTS) {
+                    System.arraycopy(pointTypes, startIndex, pointTypes, 0, newSize);
+                }
             }
             mEventTimes.setLength(newSize);
             mXCoordinates.setLength(newSize);
             mYCoordinates.setLength(newSize);
-            if (DBG_SHOW_POINTS) {
+            if (DEBUG_SHOW_POINTS) {
                 mPointTypes.setLength(newSize);
             }
             // The start index of the last segment of the stroke
@@ -295,4 +293,26 @@
         }
         return newSize > 0;
     }
+
+    private void debugDrawPoints(final Canvas canvas, final int startIndex, final int endIndex,
+            final Paint paint) {
+        final int[] xCoords = mXCoordinates.getPrimitiveArray();
+        final int[] yCoords = mYCoordinates.getPrimitiveArray();
+        final int[] pointTypes = mPointTypes.getPrimitiveArray();
+        // {@link Paint} that is zero width stroke and anti alias off draws exactly 1 pixel.
+        paint.setAntiAlias(false);
+        paint.setStrokeWidth(0);
+        for (int i = startIndex; i < endIndex; i++) {
+            final int pointType = pointTypes[i];
+            if (pointType == POINT_TYPE_INTERPOLATED) {
+                paint.setColor(Color.RED);
+            } else if (pointType == POINT_TYPE_SAMPLED) {
+                paint.setColor(0xFFA000FF);
+            } else {
+                paint.setColor(Color.GREEN);
+            }
+            canvas.drawPoint(getXCoordValue(xCoords[i]), yCoords[i], paint);
+        }
+        paint.setAntiAlias(true);
+    }
 }
diff --git a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
index 0ec8153..b526a94 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
@@ -16,8 +16,6 @@
 
 package com.android.inputmethod.keyboard.internal;
 
-import com.android.inputmethod.annotations.UsedForTesting;
-
 /**
  * Interpolates XY-coordinates using Cubic Hermite Curve.
  */
@@ -54,7 +52,6 @@
      * @param minPos the minimum index of left-open interval of valid data.
      * @param maxPos the maximum index of left-open interval of valid data.
      */
-    @UsedForTesting
     public void reset(final int[] xCoords, final int[] yCoords, final int minPos,
             final int maxPos) {
         mXCoords = xCoords;
@@ -79,7 +76,6 @@
      *           valid points, <code>p3</code> must be equal or greater than <code>maxPos</code> of
      *           {@link #reset(int[],int[],int,int)}.
      */
-    @UsedForTesting
     public void setInterval(final int p0, final int p1, final int p2, final int p3) {
         mP1X = mXCoords[p1];
         mP1Y = mYCoords[p1];
@@ -152,7 +148,6 @@
      *
      * @param t the interpolation parameter. The value must be in close interval <code>[0,1]</code>.
      */
-    @UsedForTesting
     public void interpolate(final float t) {
         final float omt = 1.0f - t;
         final float tm2 = 2.0f * t;
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
index 41fcb83..5609612 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -75,7 +75,7 @@
             final String packageName = packageUri.getSchemeSpecificPart();
             if (null == packageName) return;
             // TODO: do this in a more appropriate place
-            TargetApplicationGetter.removeApplicationInfoCache(packageName);
+            TargetPackageInfoGetterTask.removeCachedPackageInfo(packageName);
             final PackageInfo packageInfo;
             try {
                 packageInfo = manager.getPackageInfo(packageName, PackageManager.GET_PROVIDERS);
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 883c946..f85c16b 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -28,14 +28,13 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.inputmethodservice.InputMethodService;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
-import android.os.Build.VERSION_CODES;
 import android.os.Debug;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -64,6 +63,7 @@
 import com.android.inputmethod.accessibility.AccessibilityUtils;
 import com.android.inputmethod.accessibility.AccessibleKeyboardViewProxy;
 import com.android.inputmethod.annotations.UsedForTesting;
+import com.android.inputmethod.compat.AppWorkaroundsUtils;
 import com.android.inputmethod.compat.InputMethodServiceCompatUtils;
 import com.android.inputmethod.compat.SuggestionSpanUtils;
 import com.android.inputmethod.dictionarypack.DictionaryPackConstants;
@@ -91,7 +91,7 @@
  * Input method implementation for Qwerty'ish keyboard.
  */
 public class LatinIME extends InputMethodService implements KeyboardActionListener,
-        SuggestionStripView.Listener, TargetApplicationGetter.OnTargetApplicationKnownListener,
+        SuggestionStripView.Listener, TargetPackageInfoGetterTask.OnTargetPackageInfoKnownListener,
         Suggest.SuggestInitializationListener {
     private static final String TAG = LatinIME.class.getSimpleName();
     private static final boolean TRACE = false;
@@ -141,7 +141,7 @@
     private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY;
     @UsedForTesting Suggest mSuggest;
     private CompletionInfo[] mApplicationSpecifiedCompletions;
-    private ApplicationInfo mTargetApplicationInfo;
+    private AppWorkaroundsUtils mAppWorkAroundsUtils = new AppWorkaroundsUtils();
 
     private RichInputMethodManager mRichImm;
     @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher;
@@ -711,10 +711,11 @@
             Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead");
         }
 
-        mTargetApplicationInfo =
-                TargetApplicationGetter.getCachedApplicationInfo(editorInfo.packageName);
-        if (null == mTargetApplicationInfo) {
-            new TargetApplicationGetter(this /* context */, this /* listener */)
+        final PackageInfo packageInfo =
+                TargetPackageInfoGetterTask.getCachedPackageInfo(editorInfo.packageName);
+        mAppWorkAroundsUtils.setPackageInfo(packageInfo);
+        if (null == packageInfo) {
+            new TargetPackageInfoGetterTask(this /* context */, this /* listener */)
                     .execute(editorInfo.packageName);
         }
 
@@ -819,10 +820,10 @@
         if (TRACE) Debug.startMethodTracing("/data/trace/latinime");
     }
 
-    // Callback for the TargetApplicationGetter
+    // Callback for the TargetPackageInfoGetterTask
     @Override
-    public void onTargetApplicationKnown(final ApplicationInfo info) {
-        mTargetApplicationInfo = info;
+    public void onTargetPackageInfoKnown(final PackageInfo info) {
+        mAppWorkAroundsUtils.setPackageInfo(info);
     }
 
     @Override
@@ -1369,8 +1370,7 @@
             return;
         }
 
-        if (Constants.CODE_ENTER == code && mTargetApplicationInfo != null
-                && mTargetApplicationInfo.targetSdkVersion < VERSION_CODES.JELLY_BEAN) {
+        if (Constants.CODE_ENTER == code && mAppWorkAroundsUtils.isBeforeJellyBean()) {
             // Backward compatibility mode. Before Jelly bean, the keyboard would simulate
             // a hardware keyboard event on pressing enter or delete. This is bad for many
             // reasons (there are race conditions with commits) but some applications are
@@ -1864,8 +1864,7 @@
                     // This should never happen.
                     Log.e(TAG, "Backspace when we don't know the selection position");
                 }
-                if (mTargetApplicationInfo != null
-                        && mTargetApplicationInfo.targetSdkVersion < VERSION_CODES.JELLY_BEAN) {
+                if (mAppWorkAroundsUtils.isBeforeJellyBean()) {
                     // Backward compatibility mode. Before Jelly bean, the keyboard would simulate
                     // a hardware keyboard event on pressing enter or delete. This is bad for many
                     // reasons (there are race conditions with commits) but some applications are
@@ -2451,6 +2450,10 @@
      * do nothing.
      */
     private void restartSuggestionsOnWordTouchedByCursor() {
+        // HACK: We may want to special-case some apps that exhibit bad behavior in case of
+        // recorrection. This is a temporary, stopgap measure that will be removed later.
+        // TODO: remove this.
+        if (mAppWorkAroundsUtils.isBrokenByRecorrection()) return;
         // If the cursor is not touching a word, or if there is a selection, return right away.
         if (mLastSelectionStart != mLastSelectionEnd) return;
         // If we don't know the cursor location, return.
@@ -2783,12 +2786,8 @@
     }
 
     public void debugDumpStateAndCrashWithException(final String context) {
-        final StringBuilder s = new StringBuilder();
-        s.append("Target application : ").append(mTargetApplicationInfo.name)
-                .append("\nPackage : ").append(mTargetApplicationInfo.packageName)
-                .append("\nTarget app sdk version : ")
-                .append(mTargetApplicationInfo.targetSdkVersion)
-                .append("\nAttributes : ").append(mSettings.getCurrent().mInputAttributes)
+        final StringBuilder s = new StringBuilder(mAppWorkAroundsUtils.toString());
+        s.append("\nAttributes : ").append(mSettings.getCurrent().mInputAttributes)
                 .append("\nContext : ").append(context);
         throw new RuntimeException(s.toString());
     }
diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
index 94513e6..0dd302a 100644
--- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
+++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java
@@ -54,6 +54,13 @@
         return sInstance;
     }
 
+    // Caveat: This may cause IPC
+    public static boolean isInputMethodManagerValidForUserOfThisProcess(final Context context) {
+        // Basically called to check whether this IME has been triggered by the current user or not
+        return !((InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE)).
+                getInputMethodList().isEmpty();
+    }
+
     public static void init(final Context context) {
         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
         sInstance.initInternal(context, prefs);
diff --git a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java b/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
deleted file mode 100644
index 1ea4ac3..0000000
--- a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.os.AsyncTask;
-import android.util.LruCache;
-
-public final class TargetApplicationGetter extends AsyncTask<String, Void, ApplicationInfo> {
-    private static final int MAX_CACHE_ENTRIES = 64; // arbitrary
-    private static LruCache<String, ApplicationInfo> sCache =
-            new LruCache<String, ApplicationInfo>(MAX_CACHE_ENTRIES);
-
-    public static ApplicationInfo getCachedApplicationInfo(final String packageName) {
-        if (null == packageName) return null;
-        return sCache.get(packageName);
-    }
-
-    public static void removeApplicationInfoCache(final String packageName) {
-        sCache.remove(packageName);
-    }
-
-    public interface OnTargetApplicationKnownListener {
-        public void onTargetApplicationKnown(final ApplicationInfo info);
-    }
-
-    private Context mContext;
-    private final OnTargetApplicationKnownListener mListener;
-
-    public TargetApplicationGetter(final Context context,
-            final OnTargetApplicationKnownListener listener) {
-        mContext = context;
-        mListener = listener;
-    }
-
-    @Override
-    protected ApplicationInfo doInBackground(final String... packageName) {
-        final PackageManager pm = mContext.getPackageManager();
-        mContext = null; // Bazooka-powered anti-leak device
-        try {
-            final ApplicationInfo targetAppInfo =
-                    pm.getApplicationInfo(packageName[0], 0 /* flags */);
-            sCache.put(packageName[0], targetAppInfo);
-            return targetAppInfo;
-        } catch (android.content.pm.PackageManager.NameNotFoundException e) {
-            return null;
-        }
-    }
-
-    @Override
-    protected void onPostExecute(final ApplicationInfo info) {
-        mListener.onTargetApplicationKnown(info);
-    }
-}
diff --git a/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java
new file mode 100644
index 0000000..947b0c5
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.util.LruCache;
+
+public final class TargetPackageInfoGetterTask extends
+        AsyncTask<String, Void, PackageInfo> {
+    private static final int MAX_CACHE_ENTRIES = 64; // arbitrary
+    private static final LruCache<String, PackageInfo> sCache =
+            new LruCache<String, PackageInfo>(MAX_CACHE_ENTRIES);
+
+    public static PackageInfo getCachedPackageInfo(final String packageName) {
+        if (null == packageName) return null;
+        return sCache.get(packageName);
+    }
+
+    public static void removeCachedPackageInfo(final String packageName) {
+        sCache.remove(packageName);
+    }
+
+    public interface OnTargetPackageInfoKnownListener {
+        public void onTargetPackageInfoKnown(final PackageInfo info);
+    }
+
+    private Context mContext;
+    private final OnTargetPackageInfoKnownListener mListener;
+
+    public TargetPackageInfoGetterTask(final Context context,
+            final OnTargetPackageInfoKnownListener listener) {
+        mContext = context;
+        mListener = listener;
+    }
+
+    @Override
+    protected PackageInfo doInBackground(final String... packageName) {
+        final PackageManager pm = mContext.getPackageManager();
+        mContext = null; // Bazooka-powered anti-leak device
+        try {
+            final PackageInfo packageInfo = pm.getPackageInfo(packageName[0], 0 /* flags */);
+            sCache.put(packageName[0], packageInfo);
+            return packageInfo;
+        } catch (android.content.pm.PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    @Override
+    protected void onPostExecute(final PackageInfo info) {
+        mListener.onTargetPackageInfoKnown(info);
+    }
+}
diff --git a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
index 1b893a6..604ebee 100644
--- a/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
+++ b/java/src/com/android/inputmethod/latin/setup/LauncherIconVisibilityManager.java
@@ -68,8 +68,15 @@
         // 1) the package has been re-installed, 2) the device has been booted,
         // 3) a multiuser has been created.
         // There is no good reason to keep the process alive if this IME isn't a current IME.
-        RichInputMethodManager.init(context);
-        if (!SetupActivity.isThisImeCurrent(context)) {
+        final boolean isCurrentImeOfCurrentUser;
+        if (RichInputMethodManager.isInputMethodManagerValidForUserOfThisProcess(context)) {
+            RichInputMethodManager.init(context);
+            isCurrentImeOfCurrentUser = SetupActivity.isThisImeCurrent(context);
+        } else {
+            isCurrentImeOfCurrentUser = false;
+        }
+
+        if (!isCurrentImeOfCurrentUser) {
             final int myPid = Process.myPid();
             Log.i(TAG, "Killing my process: pid=" + myPid);
             Process.killProcess(myPid);
@@ -84,7 +91,7 @@
         } else if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
             Log.i(TAG, "Boot has been completed");
             return true;
-        } else if (IntentCompatUtils.has_ACTION_USER_INITIALIZE(intent)) {
+        } else if (IntentCompatUtils.is_ACTION_USER_INITIALIZE(action)) {
             Log.i(TAG, "User initialize");
             return true;
         }
diff --git a/java/src/com/android/inputmethod/research/FeedbackLog.java b/java/src/com/android/inputmethod/research/FeedbackLog.java
new file mode 100644
index 0000000..5af194c
--- /dev/null
+++ b/java/src/com/android/inputmethod/research/FeedbackLog.java
@@ -0,0 +1,32 @@
+/*
+ * 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.research;
+
+import android.content.Context;
+
+import java.io.File;
+
+public class FeedbackLog extends ResearchLog {
+    public FeedbackLog(final File outputFile, final Context context) {
+        super(outputFile, context);
+    }
+
+    @Override
+    public boolean isFeedbackLog() {
+        return true;
+    }
+}
diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java
index 3e82139..fde2798 100644
--- a/java/src/com/android/inputmethod/research/ResearchLog.java
+++ b/java/src/com/android/inputmethod/research/ResearchLog.java
@@ -81,6 +81,17 @@
     }
 
     /**
+     * Returns true if this is a FeedbackLog.
+     *
+     * FeedbackLogs record only the data associated with a Feedback dialog. Instead of normal
+     * logging, they contain a LogStatement with the complete feedback string and optionally a
+     * recording of the user's supplied demo of the problem.
+     */
+    public boolean isFeedbackLog() {
+        return false;
+    }
+
+    /**
      * Waits for any publication requests to finish and closes the {@link JsonWriter} used for
      * output.
      *
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 3be01e0..c4409d5 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -194,6 +194,8 @@
     // gesture, and when committing the earlier word, split the LogUnit.
     private long mSavedDownEventTime;
     private Bundle mFeedbackDialogBundle = null;
+    // Whether the feedback dialog is visible, and the user is typing into it.  Normal logging is
+    // not performed on text that the user types into the feedback dialog.
     private boolean mInFeedbackDialog = false;
     private Handler mUserRecordingTimeoutHandler;
     private static final long USER_RECORDING_TIMEOUT_MS = 30L * DateUtils.SECOND_IN_MILLIS;
@@ -655,7 +657,7 @@
         feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, SystemClock.uptimeMillis(),
                 feedbackContents, accountName, recording);
 
-        final ResearchLog feedbackLog = new ResearchLog(mResearchLogDirectory.getLogFilePath(
+        final ResearchLog feedbackLog = new FeedbackLog(mResearchLogDirectory.getLogFilePath(
                 System.currentTimeMillis(), System.nanoTime()), mLatinIME);
         final LogBuffer feedbackLogBuffer = new LogBuffer();
         feedbackLogBuffer.shiftIn(feedbackLogUnit);
@@ -718,8 +720,28 @@
         mIsPasswordView = isPasswordView;
     }
 
-    private boolean isAllowedToLog() {
-        return !mIsPasswordView && sIsLogging && !mInFeedbackDialog;
+    /**
+     * Returns true if logging is permitted.
+     *
+     * This method is called when adding a LogStatement to a LogUnit, and when adding a LogUnit to a
+     * ResearchLog.  It is checked in both places in case conditions change between these times, and
+     * as a defensive measure in case refactoring changes the logging pipeline.
+     */
+    private boolean isAllowedToLogTo(final ResearchLog researchLog) {
+        // Logging is never allowed in these circumstances
+        if (mIsPasswordView) return false;
+        if (!sIsLogging) return false;
+        if (mInFeedbackDialog) {
+            // The FeedbackDialog is up.  Normal logging should not happen (the user might be trying
+            // out things while the dialog is up, and their reporting of an issue may not be
+            // representative of what they normally type).  However, after the user has finished
+            // entering their feedback, the logger packs their comments and an encoded version of
+            // any demonstration of the issue into a special "FeedbackLog".  So if the FeedbackLog
+            // is the destination, we do want to allow logging to it.
+            return researchLog.isFeedbackLog();
+        }
+        // No other exclusions.  Logging is permitted.
+        return true;
     }
 
     public void requestIndicatorRedraw() {
@@ -752,7 +774,7 @@
         // and remove this method.
         // The check for MainKeyboardView ensures that the indicator only decorates the main
         // keyboard, not every keyboard.
-        if (IS_SHOWING_INDICATOR && (isAllowedToLog() || isReplaying())
+        if (IS_SHOWING_INDICATOR && (isAllowedToLogTo(mMainResearchLog) || isReplaying())
                 && view instanceof MainKeyboardView) {
             final int savedColor = paint.getColor();
             paint.setColor(getIndicatorColor());
@@ -787,7 +809,7 @@
     private synchronized void enqueueEvent(final LogUnit logUnit, final LogStatement logStatement,
             final Object... values) {
         assert values.length == logStatement.getKeys().length;
-        if (isAllowedToLog() && logUnit != null) {
+        if (isAllowedToLogTo(mMainResearchLog) && logUnit != null) {
             final long time = SystemClock.uptimeMillis();
             logUnit.addLogStatement(logStatement, time, values);
         }
@@ -886,7 +908,7 @@
             final ResearchLog researchLog, final boolean canIncludePrivateData) {
         final LogUnit openingLogUnit = new LogUnit();
         if (logUnits.isEmpty()) return;
-        if (!isAllowedToLog()) return;
+        if (!isAllowedToLogTo(researchLog)) return;
         // LogUnits not containing private data, such as contextual data for the log, do not require
         // logSegment boundary statements.
         if (canIncludePrivateData) {