diff options
Diffstat (limited to 'java/src')
15 files changed, 283 insertions, 85 deletions
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 000000000..21535e421 --- /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 000000000..7e9e2e37b --- /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 df2e22fe8..965a2a891 100644 --- a/java/src/com/android/inputmethod/compat/IntentCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/IntentCompatUtils.java @@ -21,16 +21,15 @@ import android.content.Intent; 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 1fe23a330..d4051f74b 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.util.Xml; 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 @@ public final class KeyboardLayoutSet { 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 b31f00b62..8deadbf96 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 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { } 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 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { } + /** + * 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 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { 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 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { * @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 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { 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 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { 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 03dd1c372..0f3cd7887 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 @@ import com.android.inputmethod.latin.ResizableIntArray; * @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 @@ final class GestureTrail { 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 @@ final class GestureTrail { 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 @@ final class GestureTrail { 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 class GestureTrail { 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 @@ final class GestureTrail { 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 @@ final class GestureTrail { } 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 0ec8153f5..b526a942a 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 @@ public final class HermiteInterpolator { * @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 @@ public final class HermiteInterpolator { * 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 @@ public final class HermiteInterpolator { * * @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 41fcb83e6..56096127e 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java +++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java @@ -75,7 +75,7 @@ public final class DictionaryPackInstallBroadcastReceiver extends BroadcastRecei 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 883c9469b..f85c16be3 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.DialogInterface; 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 android.view.inputmethod.InputMethodSubtype; 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 @@ import java.util.TreeSet; * 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // 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 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * 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 class LatinIME extends InputMethodService implements KeyboardActionListen } 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 94513e635..0dd302afa 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -54,6 +54,13 @@ public final class RichInputMethodManager { 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/TargetPackageInfoGetterTask.java index 1ea4ac346..947b0c586 100644 --- a/java/src/com/android/inputmethod/latin/TargetApplicationGetter.java +++ b/java/src/com/android/inputmethod/latin/TargetPackageInfoGetterTask.java @@ -17,54 +17,54 @@ package com.android.inputmethod.latin; import android.content.Context; -import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.util.LruCache; -public final class TargetApplicationGetter extends AsyncTask<String, Void, ApplicationInfo> { +public final class TargetPackageInfoGetterTask extends + AsyncTask<String, Void, PackageInfo> { private static final int MAX_CACHE_ENTRIES = 64; // arbitrary - private static LruCache<String, ApplicationInfo> sCache = - new LruCache<String, ApplicationInfo>(MAX_CACHE_ENTRIES); + private static final LruCache<String, PackageInfo> sCache = + new LruCache<String, PackageInfo>(MAX_CACHE_ENTRIES); - public static ApplicationInfo getCachedApplicationInfo(final String packageName) { + public static PackageInfo getCachedPackageInfo(final String packageName) { if (null == packageName) return null; return sCache.get(packageName); } - public static void removeApplicationInfoCache(final String packageName) { + public static void removeCachedPackageInfo(final String packageName) { sCache.remove(packageName); } - public interface OnTargetApplicationKnownListener { - public void onTargetApplicationKnown(final ApplicationInfo info); + public interface OnTargetPackageInfoKnownListener { + public void onTargetPackageInfoKnown(final PackageInfo info); } private Context mContext; - private final OnTargetApplicationKnownListener mListener; + private final OnTargetPackageInfoKnownListener mListener; - public TargetApplicationGetter(final Context context, - final OnTargetApplicationKnownListener listener) { + public TargetPackageInfoGetterTask(final Context context, + final OnTargetPackageInfoKnownListener listener) { mContext = context; mListener = listener; } @Override - protected ApplicationInfo doInBackground(final String... packageName) { + protected PackageInfo 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; + 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 ApplicationInfo info) { - mListener.onTargetApplicationKnown(info); + 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 1b893a65d..604ebeeb6 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 @@ public final class LauncherIconVisibilityManager extends BroadcastReceiver { // 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 @@ public final class LauncherIconVisibilityManager extends BroadcastReceiver { } 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 000000000..5af194c32 --- /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 3e82139a6..fde2798e1 100644 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ b/java/src/com/android/inputmethod/research/ResearchLog.java @@ -81,6 +81,17 @@ public class ResearchLog { } /** + * 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 3be01e06b..c4409d5c8 100644 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ b/java/src/com/android/inputmethod/research/ResearchLogger.java @@ -194,6 +194,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang // 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang 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) { |