aboutsummaryrefslogtreecommitdiffstats
path: root/java/src
diff options
context:
space:
mode:
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java24
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java5
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java24
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/EventHandler.java4
-rw-r--r--java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java14
-rw-r--r--java/src/com/android/inputmethod/keyboard/MainKeyboardView.java3
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java16
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java138
-rw-r--r--java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java166
-rw-r--r--java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java21
-rw-r--r--java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java54
-rw-r--r--java/src/com/android/inputmethod/latin/ExpandableDictionary.java11
-rw-r--r--java/src/com/android/inputmethod/latin/LatinIME.java12
-rw-r--r--java/src/com/android/inputmethod/latin/RichInputConnection.java28
-rw-r--r--java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java7
-rw-r--r--java/src/com/android/inputmethod/latin/define/ProductionFlag.java2
-rw-r--r--java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java26
-rw-r--r--java/src/com/android/inputmethod/research/ResearchLogger.java26
18 files changed, 499 insertions, 82 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
index 0c8b466a4..69615887f 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryPackConstants.java
@@ -25,16 +25,34 @@ package com.android.inputmethod.dictionarypack;
*/
public class DictionaryPackConstants {
/**
+ * The root domain for the dictionary pack, upon which authorities and actions will append
+ * their own distinctive strings.
+ */
+ private static final String DICTIONARY_DOMAIN = "com.android.inputmethod.dictionarypack";
+
+ /**
* Authority for the ContentProvider protocol.
*/
// TODO: find some way to factorize this string with the one in the resources
- public static final String AUTHORITY = "com.android.inputmethod.dictionarypack.aosp";
+ public static final String AUTHORITY = DICTIONARY_DOMAIN + ".aosp";
/**
* The action of the intent for publishing that new dictionary data is available.
*/
// TODO: make this different across different packages. A suggested course of action is
// to use the package name inside this string.
- public static final String NEW_DICTIONARY_INTENT_ACTION =
- "com.android.inputmethod.dictionarypack.newdict";
+ // NOTE: The appended string should be uppercase like all other actions, but it's not for
+ // historical reasons.
+ public static final String NEW_DICTIONARY_INTENT_ACTION = DICTIONARY_DOMAIN + ".newdict";
+
+ /**
+ * The action of the intent sent by the dictionary pack to ask for a client to make
+ * itself known. This is used when the settings activity is brought up for a client the
+ * dictionary pack does not know about.
+ */
+ public static final String UNKNOWN_DICTIONARY_PROVIDER_CLIENT = DICTIONARY_DOMAIN
+ + ".UNKNOWN_CLIENT";
+ // In the above intents, the name of the string extra that contains the name of the client
+ // we want information about.
+ public static final String DICTIONARY_PROVIDER_CLIENT_EXTRA = "client";
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
index 77b3b8e2e..f8d1c4fc9 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java
@@ -509,6 +509,11 @@ public final class DictionaryProvider extends ContentProvider {
} catch (final BadFormatException e) {
Log.w(TAG, "Not enough information to insert this dictionary " + values, e);
}
+ // We just received new information about the list of dictionary for this client.
+ // For all intents and purposes, this is new metadata, so we should publish it
+ // so that any listeners (like the Settings interface for example) can update
+ // themselves.
+ UpdateHandler.publishUpdateMetadataCompleted(getContext(), true);
break;
case DICTIONARY_V1_WHOLE_LIST:
case DICTIONARY_V1_DICT_INFO:
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
index 7e2a6bb1e..9e27c1f3f 100644
--- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
+++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java
@@ -110,6 +110,15 @@ public final class DictionarySettingsFragment extends PreferenceFragment
super.onResume();
mChangedSettings = false;
UpdateHandler.registerUpdateEventListener(this);
+ final Activity activity = getActivity();
+ if (!MetadataDbHelper.isClientKnown(activity, mClientId)) {
+ Log.i(TAG, "Unknown dictionary pack client: " + mClientId + ". Requesting info.");
+ final Intent unknownClientBroadcast =
+ new Intent(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT);
+ unknownClientBroadcast.putExtra(
+ DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA, mClientId);
+ activity.sendBroadcast(unknownClientBroadcast);
+ }
final IntentFilter filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getActivity().registerReceiver(mConnectivityChangedReceiver, filter);
@@ -130,6 +139,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
}
}
+ @Override
public void downloadedMetadata(final boolean succeeded) {
stopLoadingAnimation();
if (!succeeded) return; // If the download failed nothing changed, so no need to refresh
@@ -141,6 +151,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
}.start();
}
+ @Override
public void wordListDownloadFinished(final String wordListId, final boolean succeeded) {
final WordListPreference pref = findWordListPreference(wordListId);
if (null == pref) return;
@@ -177,6 +188,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
return null;
}
+ @Override
public void updateCycleCompleted() {}
private void refreshNetworkState() {
@@ -260,6 +272,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
} else if (!cursor.moveToFirst()) {
final ArrayList<Preference> result = new ArrayList<Preference>();
result.add(createErrorMessage(activity, R.string.no_dictionaries_available));
+ cursor.close();
return result;
} else {
final String systemLocaleString = Locale.getDefault().toString();
@@ -289,6 +302,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
prefList.put(key, pref);
}
} while (cursor.moveToNext());
+ cursor.close();
return prefList.values();
}
}
@@ -335,8 +349,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment
private void cancelRefresh() {
UpdateHandler.unregisterUpdateEventListener(this);
final Context context = getActivity();
- UpdateHandler.cancelUpdate(context,
- MetadataDbHelper.getMetadataUriAsString(context, mClientId));
+ UpdateHandler.cancelUpdate(context, mClientId);
stopLoadingAnimation();
}
@@ -359,7 +372,12 @@ public final class DictionarySettingsFragment extends PreferenceFragment
getActivity(), android.R.anim.fade_out));
preferenceView.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
- mUpdateNowMenu.setTitle(R.string.check_for_updates_now);
+ // The menu is created by the framework asynchronously after the activity,
+ // which means it's possible to have the activity running but the menu not
+ // created yet - hence the necessity for a null check here.
+ if (null != mUpdateNowMenu) {
+ mUpdateNowMenu.setTitle(R.string.check_for_updates_now);
+ }
}
});
}
diff --git a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
index 96c4a8305..d8aa33bb8 100644
--- a/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/EventHandler.java
@@ -16,13 +16,9 @@
package com.android.inputmethod.dictionarypack;
-import com.android.inputmethod.latin.LatinIME;
-import com.android.inputmethod.latin.R;
-
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.util.Log;
public final class EventHandler extends BroadcastReceiver {
private static final String TAG = EventHandler.class.getName();
diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
index b4727509c..e05a79b7b 100644
--- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
+++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java
@@ -444,7 +444,19 @@ public final class UpdateHandler {
manager.remove(fileId);
}
- private static void publishUpdateMetadataCompleted(final Context context,
+ /**
+ * Sends a broadcast informing listeners that the dictionaries were updated.
+ *
+ * This will call all local listeners through the UpdateEventListener#downloadedMetadata
+ * callback (for example, the dictionary provider interface uses this to stop the Loading
+ * animation) and send a broadcast about the metadata having been updated. For a client of
+ * the dictionary pack like Latin IME, this means it should re-query the dictionary pack
+ * for any relevant new data.
+ *
+ * @param context the context, to send the broadcast.
+ * @param downloadSuccessful whether the download of the metadata was successful or not.
+ */
+ public static void publishUpdateMetadataCompleted(final Context context,
final boolean downloadSuccessful) {
// We need to warn all listeners of what happened. But some listeners may want to
// remove themselves or re-register something in response. Hence we should take a
diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
index f0ca9c1ec..745e7dfed 100644
--- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
+++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java
@@ -229,6 +229,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack
@Override
public void handleMessage(final Message msg) {
final MainKeyboardView keyboardView = getOuterInstance();
+ if (keyboardView == null) {
+ return;
+ }
final PointerTracker tracker = (PointerTracker) msg.obj;
switch (msg.what) {
case MSG_TYPING_STATE_EXPIRED:
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
index b047fe038..e3e6d39e4 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java
@@ -44,6 +44,7 @@ final class GesturePreviewTrail {
// The wall time of the zero value in {@link #mEventTimes}
private long mCurrentTimeBase;
private int mTrailStartIndex;
+ private int mLastInterpolatedDrawIndex;
static final class Params {
public final int mTrailColor;
@@ -96,6 +97,17 @@ final class GesturePreviewTrail {
}
final int[] eventTimes = mEventTimes.getPrimitiveArray();
final int strokeId = stroke.getGestureStrokeId();
+ // Because interpolation algorithm in {@link GestureStrokeWithPreviewPoints} can't determine
+ // the interpolated points in the last segment of gesture stroke, it may need recalculation
+ // of interpolation when new segments are added to the stroke.
+ // {@link #mLastInterpolatedDrawIndex} holds the start index of the last segment. It may
+ // be updated by the interpolation
+ // {@link GestureStrokeWithPreviewPoints#interpolatePreviewStroke}
+ // or by animation {@link #drawGestureTrail(Canvas,Paint,Rect,Params)} below.
+ final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId)
+ ? mLastInterpolatedDrawIndex : trailSize;
+ mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment(
+ lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates);
if (strokeId != mCurrentStrokeId) {
final int elapsedTime = (int)(downTime - mCurrentTimeBase);
for (int i = mTrailStartIndex; i < trailSize; i++) {
@@ -216,6 +228,10 @@ final class GesturePreviewTrail {
System.arraycopy(eventTimes, startIndex, eventTimes, 0, newSize);
System.arraycopy(xCoords, startIndex, xCoords, 0, newSize);
System.arraycopy(yCoords, startIndex, yCoords, 0, newSize);
+ // The start index of the last segment of the stroke
+ // {@link mLastInterpolatedDrawIndex} should also be updated because all array
+ // elements have just been shifted for compaction.
+ mLastInterpolatedDrawIndex = Math.max(mLastInterpolatedDrawIndex - startIndex, 0);
}
mEventTimes.setLength(newSize);
mXCoordinates.setLength(newSize);
diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
index fc81410ff..3315954c1 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java
@@ -21,19 +21,32 @@ import com.android.inputmethod.latin.ResizableIntArray;
public final class GestureStrokeWithPreviewPoints extends GestureStroke {
public static final int PREVIEW_CAPACITY = 256;
+ private static final boolean ENABLE_INTERPOLATION = true;
+
private final ResizableIntArray mPreviewEventTimes = new ResizableIntArray(PREVIEW_CAPACITY);
private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY);
private int mStrokeId;
private int mLastPreviewSize;
+ private final HermiteInterpolator mInterpolator = new HermiteInterpolator();
+ private int mLastInterpolatedPreviewIndex;
- private int mMinPreviewSampleLengthSquare;
+ private int mMinPreviewSamplingDistanceSquared;
private int mLastX;
private int mLastY;
+ private double mMinPreviewSamplingDistance;
+ private double mDistanceFromLastSample;
- // TODO: Move this to resource.
- private static final float MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH = 0.1f;
+ // TODO: Move these constants to resource.
+ // The minimum linear distance between sample points for preview in keyWidth unit.
+ private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH = 0.1f;
+ // The minimum trail distance between sample points for preview in keyWidth unit when using
+ // interpolation.
+ private static final float MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION = 0.2f;
+ // The angular threshold to use interpolation in radian. PI/12 is 15 degree.
+ private static final double INTERPOLATION_ANGULAR_THRESHOLD = Math.PI / 12.0d;
+ private static final int MAX_INTERPOLATION_PARTITION = 4;
public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) {
super(pointerId, params);
@@ -44,6 +57,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
super.reset();
mStrokeId++;
mLastPreviewSize = 0;
+ mLastInterpolatedPreviewIndex = 0;
mPreviewEventTimes.setLength(0);
mPreviewXCoordinates.setLength(0);
mPreviewYCoordinates.setLength(0);
@@ -53,35 +67,49 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
return mStrokeId;
}
- public int getGestureStrokePreviewSize() {
- return mPreviewEventTimes.getLength();
- }
-
@Override
public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) {
super.setKeyboardGeometry(keyWidth, keyboardHeight);
- final float sampleLength = keyWidth * MIN_PREVIEW_SAMPLE_LENGTH_RATIO_TO_KEY_WIDTH;
- mMinPreviewSampleLengthSquare = (int)(sampleLength * sampleLength);
+ final float samplingRatioToKeyWidth = ENABLE_INTERPOLATION
+ ? MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH_WITH_INTERPOLATION
+ : MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH;
+ mMinPreviewSamplingDistance = keyWidth * samplingRatioToKeyWidth;
+ mMinPreviewSamplingDistanceSquared = (int)(
+ mMinPreviewSamplingDistance * mMinPreviewSamplingDistance);
}
- private boolean needsSampling(final int x, final int y) {
+ private boolean needsSampling(final int x, final int y, final boolean isMajorEvent) {
+ if (ENABLE_INTERPOLATION) {
+ mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY);
+ mLastX = x;
+ mLastY = y;
+ if (mDistanceFromLastSample >= mMinPreviewSamplingDistance) {
+ mDistanceFromLastSample = 0.0d;
+ return true;
+ }
+ return false;
+ }
+
final int dx = x - mLastX;
final int dy = y - mLastY;
- return dx * dx + dy * dy >= mMinPreviewSampleLengthSquare;
+ if (isMajorEvent || dx * dx + dy * dy >= mMinPreviewSamplingDistanceSquared) {
+ mLastX = x;
+ mLastY = y;
+ return true;
+ }
+ return false;
}
@Override
public boolean addPointOnKeyboard(final int x, final int y, final int time,
final boolean isMajorEvent) {
- final boolean onValidArea = super.addPointOnKeyboard(x, y, time, isMajorEvent);
- if (isMajorEvent || needsSampling(x, y)) {
+ if (needsSampling(x, y, isMajorEvent)) {
mPreviewEventTimes.add(time);
mPreviewXCoordinates.add(x);
mPreviewYCoordinates.add(y);
- mLastX = x;
- mLastY = y;
}
- return onValidArea;
+ return super.addPointOnKeyboard(x, y, time, isMajorEvent);
+
}
public void appendPreviewStroke(final ResizableIntArray eventTimes,
@@ -95,4 +123,82 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke {
yCoords.append(mPreviewYCoordinates, mLastPreviewSize, length);
mLastPreviewSize = mPreviewEventTimes.getLength();
}
+
+ /**
+ * Calculate interpolated points between the last interpolated point and the end of the trail.
+ * And return the start index of the last interpolated segment of input arrays because it
+ * may need to recalculate the interpolated points in the segment if further segments are
+ * added to this stroke.
+ *
+ * @param lastInterpolatedIndex the start index of the last interpolated segment of
+ * <code>eventTimes</code>, <code>xCoords</code>, and <code>yCoords</code>.
+ * @param eventTimes the event time array of gesture preview trail to be drawn.
+ * @param xCoords the x-coordinates array of gesture preview trail to be drawn.
+ * @param yCoords the y-coordinates array of gesture preview trail to be drawn.
+ * @return the start index of the last interpolated segment of input arrays.
+ */
+ public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex,
+ final ResizableIntArray eventTimes, final ResizableIntArray xCoords,
+ final ResizableIntArray yCoords) {
+ if (!ENABLE_INTERPOLATION) {
+ return lastInterpolatedIndex;
+ }
+ final int size = mPreviewEventTimes.getLength();
+ final int[] pt = mPreviewEventTimes.getPrimitiveArray();
+ final int[] px = mPreviewXCoordinates.getPrimitiveArray();
+ final int[] py = mPreviewYCoordinates.getPrimitiveArray();
+ mInterpolator.reset(px, py, 0, size);
+ // The last segment of gesture stroke needs to be interpolated again because the slope of
+ // the tangent at the last point isn't determined.
+ int lastInterpolatedDrawIndex = lastInterpolatedIndex;
+ int d1 = lastInterpolatedIndex;
+ for (int p2 = mLastInterpolatedPreviewIndex + 1; p2 < size; p2++) {
+ final int p1 = p2 - 1;
+ final int p0 = p1 - 1;
+ final int p3 = p2 + 1;
+ mLastInterpolatedPreviewIndex = p1;
+ lastInterpolatedDrawIndex = d1;
+ mInterpolator.setInterval(p0, p1, p2, p3);
+ final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X);
+ final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X);
+ final double dm = Math.abs(angularDiff(m2, m1));
+ final int partition = Math.min((int)Math.ceil(dm / INTERPOLATION_ANGULAR_THRESHOLD),
+ MAX_INTERPOLATION_PARTITION);
+ final int t1 = eventTimes.get(d1);
+ final int dt = pt[p2] - pt[p1];
+ d1++;
+ for (int i = 1; i < partition; i++) {
+ final float t = i / (float)partition;
+ mInterpolator.interpolate(t);
+ eventTimes.add(d1, (int)(dt * t) + t1);
+ xCoords.add(d1, (int)mInterpolator.mInterpolatedX);
+ yCoords.add(d1, (int)mInterpolator.mInterpolatedY);
+ d1++;
+ }
+ eventTimes.add(d1, pt[p2]);
+ xCoords.add(d1, px[p2]);
+ yCoords.add(d1, py[p2]);
+ }
+ return lastInterpolatedDrawIndex;
+ }
+
+ private static final double TWO_PI = Math.PI * 2.0d;
+
+ /**
+ * Calculate the angular of rotation from <code>a0</code> to <code>a1</code>.
+ *
+ * @param a1 the angular to which the rotation ends.
+ * @param a0 the angular from which the rotation starts.
+ * @return the angular rotation value from a0 to a1, normalized to [-PI, +PI].
+ */
+ private static double angularDiff(final double a1, final double a0) {
+ double deltaAngle = a1 - a0;
+ while (deltaAngle > Math.PI) {
+ deltaAngle -= TWO_PI;
+ }
+ while (deltaAngle < -Math.PI) {
+ deltaAngle += TWO_PI;
+ }
+ return deltaAngle;
+ }
}
diff --git a/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
new file mode 100644
index 000000000..0ec8153f5
--- /dev/null
+++ b/java/src/com/android/inputmethod/keyboard/internal/HermiteInterpolator.java
@@ -0,0 +1,166 @@
+/*
+ * 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.keyboard.internal;
+
+import com.android.inputmethod.annotations.UsedForTesting;
+
+/**
+ * Interpolates XY-coordinates using Cubic Hermite Curve.
+ */
+public final class HermiteInterpolator {
+ private int[] mXCoords;
+ private int[] mYCoords;
+ private int mMinPos;
+ private int mMaxPos;
+
+ // Working variable to calculate interpolated value.
+ /** The coordinates of the start point of the interval. */
+ public int mP1X, mP1Y;
+ /** The coordinates of the end point of the interval. */
+ public int mP2X, mP2Y;
+ /** The slope of the tangent at the start point. */
+ public float mSlope1X, mSlope1Y;
+ /** The slope of the tangent at the end point. */
+ public float mSlope2X, mSlope2Y;
+ /** The interpolated coordinates.
+ * The return variables of {@link #interpolate(float)} to avoid instantiations.
+ */
+ public float mInterpolatedX, mInterpolatedY;
+
+ public HermiteInterpolator() {
+ // Nothing to do with here.
+ }
+
+ /**
+ * Reset this interpolator to point XY-coordinates data.
+ * @param xCoords the array of x-coordinates. Valid data are in left-open interval
+ * <code>[minPos, maxPos)</code>.
+ * @param yCoords the array of y-coordinates. Valid data are in left-open interval
+ * <code>[minPos, maxPos)</code>.
+ * @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;
+ mYCoords = yCoords;
+ mMinPos = minPos;
+ mMaxPos = maxPos;
+ }
+
+ /**
+ * Set interpolation interval.
+ * <p>
+ * The start and end coordinates of the interval will be set in {@link #mP1X}, {@link #mP1Y},
+ * {@link #mP2X}, and {@link #mP2Y}. The slope of the tangents at start and end points will be
+ * set in {@link #mSlope1X}, {@link #mSlope1Y}, {@link #mSlope2X}, and {@link #mSlope2Y}.
+ *
+ * @param p0 the index just before interpolation interval. If <code>p1</code> points the start
+ * of valid points, <code>p0</code> must be less than <code>minPos</code> of
+ * {@link #reset(int[],int[],int,int)}.
+ * @param p1 the start index of interpolation interval.
+ * @param p2 the end index of interpolation interval.
+ * @param p3 the index just after interpolation interval. If <code>p2</code> points the end of
+ * 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];
+ mP2X = mXCoords[p2];
+ mP2Y = mYCoords[p2];
+ // A(ax,ay) is the vector p1->p2.
+ final int ax = mP2X - mP1X;
+ final int ay = mP2Y - mP1Y;
+
+ // Calculate the slope of the tangent at p1.
+ if (p0 >= mMinPos) {
+ // p1 has previous valid point p0.
+ // The slope of the tangent is half of the vector p0->p2.
+ mSlope1X = (mP2X - mXCoords[p0]) / 2.0f;
+ mSlope1Y = (mP2Y - mYCoords[p0]) / 2.0f;
+ } else if (p3 < mMaxPos) {
+ // p1 has no previous valid point, but p2 has next valid point p3.
+ // B(bx,by) is the slope vector of the tangent at p2.
+ final float bx = (mXCoords[p3] - mP1X) / 2.0f;
+ final float by = (mYCoords[p3] - mP1Y) / 2.0f;
+ final float crossProdAB = ax * by - ay * bx;
+ final float dotProdAB = ax * bx + ay * by;
+ final float normASquare = ax * ax + ay * ay;
+ final float invHalfNormASquare = 1.0f / normASquare / 2.0f;
+ // The slope of the tangent is the mirror image of vector B to vector A.
+ mSlope1X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay);
+ mSlope1Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax);
+ } else {
+ // p1 and p2 have no previous valid point. (Interval has only point p1 and p2)
+ mSlope1X = ax;
+ mSlope1Y = ay;
+ }
+
+ // Calculate the slope of the tangent at p2.
+ if (p3 < mMaxPos) {
+ // p2 has next valid point p3.
+ // The slope of the tangent is half of the vector p1->p3.
+ mSlope2X = (mXCoords[p3] - mP1X) / 2.0f;
+ mSlope2Y = (mYCoords[p3] - mP1Y) / 2.0f;
+ } else if (p0 >= mMinPos) {
+ // p2 has no next valid point, but p1 has previous valid point p0.
+ // B(bx,by) is the slope vector of the tangent at p1.
+ final float bx = (mP2X - mXCoords[p0]) / 2.0f;
+ final float by = (mP2Y - mYCoords[p0]) / 2.0f;
+ final float crossProdAB = ax * by - ay * bx;
+ final float dotProdAB = ax * bx + ay * by;
+ final float normASquare = ax * ax + ay * ay;
+ final float invHalfNormASquare = 1.0f / normASquare / 2.0f;
+ // The slope of the tangent is the mirror image of vector B to vector A.
+ mSlope2X = invHalfNormASquare * (dotProdAB * ax + crossProdAB * ay);
+ mSlope2Y = invHalfNormASquare * (dotProdAB * ay - crossProdAB * ax);
+ } else {
+ // p1 and p2 has no previous valid point. (Interval has only point p1 and p2)
+ mSlope2X = ax;
+ mSlope2Y = ay;
+ }
+ }
+
+ /**
+ * Calculate interpolation value at <code>t</code> in unit interval <code>[0,1]</code>.
+ * <p>
+ * On the unit interval [0,1], given a starting point p1 at t=0 and an ending point p2 at t=1
+ * with the slope of the tangent m1 at p1 and m2 at p2, the polynomial of cubic Hermite curve
+ * can be defined by
+ * p(t) = (1+2t)(1-t)(1-t)*p1 + t(1-t)(1-t)*m1 + (3-2t)t^2*p2 + (t-1)t^2*m2
+ * where t is an element of [0,1].
+ * <p>
+ * The interpolated XY-coordinates will be set in {@link #mInterpolatedX} and
+ * {@link #mInterpolatedY}.
+ *
+ * @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;
+ final float k1 = 1.0f + tm2;
+ final float k2 = 3.0f - tm2;
+ final float omt2 = omt * omt;
+ final float t2 = t * t;
+ mInterpolatedX = (k1 * mP1X + t * mSlope1X) * omt2 + (k2 * mP2X - omt * mSlope2X) * t2;
+ mInterpolatedY = (k1 * mP1Y + t * mSlope1Y) * omt2 + (k2 * mP2Y - omt * mSlope2Y) * t2;
+ }
+}
diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
index 4bec99c04..562e1d0b7 100644
--- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
+++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java
@@ -450,4 +450,25 @@ public final class BinaryDictionaryFileDumper {
info.toContentValues());
}
}
+
+ /**
+ * Initialize a client record with the dictionary content provider.
+ *
+ * This merely acquires the content provider and calls
+ * #reinitializeClientRecordInDictionaryContentProvider.
+ *
+ * @param context the context for resources and providers.
+ * @param clientId the client ID to use.
+ */
+ public static void initializeClientRecordHelper(final Context context,
+ final String clientId) {
+ try {
+ final ContentProviderClient client = context.getContentResolver().
+ acquireContentProviderClient(getProviderUriBuilder("").build());
+ if (null == client) return;
+ reinitializeClientRecordInDictionaryContentProvider(context, client, clientId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot contact the dictionary content provider", e);
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
index 35f3119ea..41fcb83e6 100644
--- a/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
+++ b/java/src/com/android/inputmethod/latin/DictionaryPackInstallBroadcastReceiver.java
@@ -25,14 +25,35 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.net.Uri;
+import android.util.Log;
/**
- * Takes action to reload the necessary data when a dictionary pack was added/removed.
+ * Receives broadcasts pertaining to dictionary management and takes the appropriate action.
+ *
+ * This object receives three types of broadcasts.
+ * - Package installed/added. When a dictionary provider application is added or removed, we
+ * need to query the dictionaries.
+ * - New dictionary broadcast. The dictionary provider broadcasts new dictionary availability. When
+ * this happens, we need to re-query the dictionaries.
+ * - Unknown client. If the dictionary provider is in urgent need of data about some client that
+ * it does not know, it sends this broadcast. When we receive this, we need to tell the dictionary
+ * provider about ourselves. This happens when the settings for the dictionary pack are accessed,
+ * but Latin IME never got a chance to register itself.
*/
public final class DictionaryPackInstallBroadcastReceiver extends BroadcastReceiver {
+ private static final String TAG = DictionaryPackInstallBroadcastReceiver.class.getSimpleName();
final LatinIME mService;
+ public DictionaryPackInstallBroadcastReceiver() {
+ // This empty constructor is necessary for the system to instantiate this receiver.
+ // This happens when the dictionary pack says it can't find a record for our client,
+ // which happens when the dictionary pack settings are called before the keyboard
+ // was ever started once.
+ Log.i(TAG, "Latin IME dictionary broadcast receiver instantiated from the framework.");
+ mService = null;
+ }
+
public DictionaryPackInstallBroadcastReceiver(final LatinIME service) {
mService = service;
}
@@ -44,6 +65,11 @@ public final class DictionaryPackInstallBroadcastReceiver extends BroadcastRecei
// We need to reread the dictionary if a new dictionary package is installed.
if (action.equals(Intent.ACTION_PACKAGE_ADDED)) {
+ if (null == mService) {
+ Log.e(TAG, "Called with intent " + action + " but we don't know the service: this "
+ + "should never happen");
+ return;
+ }
final Uri packageUri = intent.getData();
if (null == packageUri) return; // No package name : we can't do anything
final String packageName = packageUri.getSchemeSpecificPart();
@@ -71,6 +97,11 @@ public final class DictionaryPackInstallBroadcastReceiver extends BroadcastRecei
return;
} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)
&& !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ if (null == mService) {
+ Log.e(TAG, "Called with intent " + action + " but we don't know the service: this "
+ + "should never happen");
+ return;
+ }
// When the dictionary package is removed, we need to reread dictionary (to use the
// next-priority one, or stop using a dictionary at all if this was the only one,
// since this is the user request).
@@ -82,7 +113,28 @@ public final class DictionaryPackInstallBroadcastReceiver extends BroadcastRecei
// read dictionary from?
mService.resetSuggestMainDict();
} else if (action.equals(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION)) {
+ if (null == mService) {
+ Log.e(TAG, "Called with intent " + action + " but we don't know the service: this "
+ + "should never happen");
+ return;
+ }
mService.resetSuggestMainDict();
+ } else if (action.equals(DictionaryPackConstants.UNKNOWN_DICTIONARY_PROVIDER_CLIENT)) {
+ if (null != mService) {
+ // Careful! This is returning if the service is NOT null. This is because we
+ // should come here instantiated by the framework in reaction to a broadcast of
+ // the above action, so we should gave gone through the no-args constructor.
+ Log.e(TAG, "Called with intent " + action + " but we have a reference to the "
+ + "service: this should never happen");
+ return;
+ }
+ // The dictionary provider does not know about some client. We check that it's really
+ // us that it needs to know about, and if it's the case, we register with the provider.
+ final String wantedClientId =
+ intent.getStringExtra(DictionaryPackConstants.DICTIONARY_PROVIDER_CLIENT_EXTRA);
+ final String myClientId = context.getString(R.string.dictionary_pack_client_id);
+ if (!wantedClientId.equals(myClientId)) return; // Not for us
+ BinaryDictionaryFileDumper.initializeClientRecordHelper(context, myClientId);
}
}
}
diff --git a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
index ae2ee577f..fd81d13ca 100644
--- a/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
+++ b/java/src/com/android/inputmethod/latin/ExpandableDictionary.java
@@ -18,6 +18,7 @@ package com.android.inputmethod.latin;
import android.content.Context;
import android.text.TextUtils;
+import android.util.Log;
import com.android.inputmethod.keyboard.ProximityInfo;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
@@ -31,6 +32,7 @@ import java.util.LinkedList;
* be searched for suggestions and valid words.
*/
public class ExpandableDictionary extends Dictionary {
+ private static final String TAG = ExpandableDictionary.class.getSimpleName();
/**
* The weight to give to a word if it's length is the same as the number of typed characters.
*/
@@ -551,8 +553,13 @@ public class ExpandableDictionary extends Dictionary {
// word. We do want however to return the correct case for the right hand side.
// So we want to squash the case of the left hand side, and preserve that of the right
// hand side word.
- Node firstWord = searchWord(mRoots, word1.toLowerCase(), 0, null);
- Node secondWord = searchWord(mRoots, word2, 0, null);
+ final String word1Lower = word1.toLowerCase();
+ if (TextUtils.isEmpty(word1Lower) || TextUtils.isEmpty(word2)) {
+ Log.e(TAG, "Invalid bigram pair: " + word1 + ", " + word1Lower + ", " + word2);
+ return frequency;
+ }
+ final Node firstWord = searchWord(mRoots, word1Lower, 0, null);
+ final Node secondWord = searchWord(mRoots, word2, 0, null);
LinkedList<NextWord> bigrams = firstWord.mNGrams;
if (bigrams == null || bigrams.size() == 0) {
firstWord.mNGrams = CollectionUtils.newLinkedList();
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 7bd09811c..0fc26a80e 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -803,10 +803,6 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
@Override
public void onWindowHidden() {
- if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.latinIME_onWindowHidden(mLastSelectionStart, mLastSelectionEnd,
- getCurrentInputConnection());
- }
super.onWindowHidden();
final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView();
if (mainKeyboardView != null) {
@@ -834,8 +830,10 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
// Remove pending messages related to update suggestions
mHandler.cancelUpdateSuggestionStrip();
resetComposingState(true /* alsoResetLastComposedWord */);
+ // Notify ResearchLogger
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
- ResearchLogger.getInstance().latinIME_onFinishInputViewInternal();
+ ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput, mLastSelectionStart,
+ mLastSelectionEnd, getCurrentInputConnection());
}
}
@@ -1145,11 +1143,11 @@ public final class LatinIME extends InputMethodService implements KeyboardAction
if (!mWordComposer.isComposingWord()) return;
final String typedWord = mWordComposer.getTypedWord();
if (typedWord.length() > 0) {
- commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
- separatorString);
if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) {
ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode());
}
+ commitChosenWord(typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD,
+ separatorString);
}
}
diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java
index 8a7ade49e..16744d1f0 100644
--- a/java/src/com/android/inputmethod/latin/RichInputConnection.java
+++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java
@@ -60,11 +60,11 @@ public final class RichInputConnection {
* This contains the committed text immediately preceding the cursor and the composing
* text if any. It is refreshed when the cursor moves by calling upon the TextView.
*/
- private StringBuilder mCommittedTextBeforeComposingText = new StringBuilder();
+ private final StringBuilder mCommittedTextBeforeComposingText = new StringBuilder();
/**
* This contains the currently composing text, as LatinIME thinks the TextView is seeing it.
*/
- private StringBuilder mComposingText = new StringBuilder();
+ private final StringBuilder mComposingText = new StringBuilder();
// A hint on how many characters to cache from the TextView. A good value of this is given by
// how many characters we need to be able to almost always find the caps mode.
private static final int DEFAULT_TEXT_CACHE_SIZE = 100;
@@ -334,13 +334,15 @@ public final class RichInputConnection {
mCurrentCursorPosition = end;
final CharSequence textBeforeCursor =
getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE + (end - start), 0);
- final int indexOfStartOfComposingText =
- Math.max(textBeforeCursor.length() - (end - start), 0);
- mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText,
- textBeforeCursor.length()));
mCommittedTextBeforeComposingText.setLength(0);
- mCommittedTextBeforeComposingText.append(
- textBeforeCursor.subSequence(0, indexOfStartOfComposingText));
+ if (!TextUtils.isEmpty(textBeforeCursor)) {
+ final int indexOfStartOfComposingText =
+ Math.max(textBeforeCursor.length() - (end - start), 0);
+ mComposingText.append(textBeforeCursor.subSequence(indexOfStartOfComposingText,
+ textBeforeCursor.length()));
+ mCommittedTextBeforeComposingText.append(
+ textBeforeCursor.subSequence(0, indexOfStartOfComposingText));
+ }
if (null != mIC) {
mIC.setComposingRegion(start, end);
}
@@ -502,16 +504,6 @@ public final class RichInputConnection {
return (r == null) ? null : r.mWord;
}
- private int getCursorPosition() {
- mIC = mParent.getCurrentInputConnection();
- if (null == mIC) return INVALID_CURSOR_POSITION;
- final ExtractedText extracted = mIC.getExtractedText(new ExtractedTextRequest(), 0);
- if (extracted == null) {
- return INVALID_CURSOR_POSITION;
- }
- return extracted.startOffset + extracted.selectionStart;
- }
-
/**
* Returns the text surrounding the cursor.
*
diff --git a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
index 62f2a9750..10931555e 100644
--- a/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
+++ b/java/src/com/android/inputmethod/latin/UserHistoryDictIOUtils.java
@@ -207,7 +207,12 @@ public final class UserHistoryDictIOUtils {
final ArrayList<PendingAttribute> attrList = bigrams.get(entry.getKey());
if (attrList != null) {
for (final PendingAttribute attr : attrList) {
- to.setBigram(word1, unigrams.get(attr.mAddress),
+ final String word2 = unigrams.get(attr.mAddress);
+ if (word1 == null || word2 == null) {
+ Log.e(TAG, "Invalid bigram pair detected: " + word1 + ", " + word2);
+ continue;
+ }
+ to.setBigram(word1, word2,
BinaryDictInputOutput.reconstructBigramFrequency(unigramFrequency,
attr.mFrequency));
}
diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
index 699e47b6a..dc937fb25 100644
--- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
+++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java
@@ -28,5 +28,5 @@ public final class ProductionFlag {
// USES_DEVELOPMENT_ONLY_DIAGNOSTICS must be false for any production build.
public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG = false;
- public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = true;
+ public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false;
}
diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
index 5c805598a..e7c7e2b8a 100644
--- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
+++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java
@@ -620,34 +620,34 @@ public final class FusionDictionary implements Iterable<Word> {
* Helper method to find a word in a given branch.
*/
@SuppressWarnings("unused")
- public static CharGroup findWordInTree(Node node, final String s) {
+ public static CharGroup findWordInTree(Node node, final String string) {
int index = 0;
final StringBuilder checker = DBG ? new StringBuilder() : null;
+ final int[] codePoints = getCodePoints(string);
CharGroup currentGroup;
- final int codePointCountInS = s.codePointCount(0, s.length());
do {
- int indexOfGroup = findIndexOfChar(node, s.codePointAt(index));
+ int indexOfGroup = findIndexOfChar(node, codePoints[index]);
if (CHARACTER_NOT_FOUND == indexOfGroup) return null;
currentGroup = node.mData.get(indexOfGroup);
- if (s.length() - index < currentGroup.mChars.length) return null;
+ if (codePoints.length - index < currentGroup.mChars.length) return null;
int newIndex = index;
- while (newIndex < s.length() && newIndex - index < currentGroup.mChars.length) {
- if (currentGroup.mChars[newIndex - index] != s.codePointAt(newIndex)) return null;
+ while (newIndex < codePoints.length && newIndex - index < currentGroup.mChars.length) {
+ if (currentGroup.mChars[newIndex - index] != codePoints[newIndex]) return null;
newIndex++;
}
index = newIndex;
if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length));
- if (index < codePointCountInS) {
+ if (index < codePoints.length) {
node = currentGroup.mChildren;
}
- } while (null != node && index < codePointCountInS);
+ } while (null != node && index < codePoints.length);
- if (index < codePointCountInS) return null;
+ if (index < codePoints.length) return null;
if (!currentGroup.isTerminal()) return null;
- if (DBG && !s.equals(checker.toString())) return null;
+ if (DBG && !codePoints.equals(checker.toString())) return null;
return currentGroup;
}
@@ -847,12 +847,12 @@ public final class FusionDictionary implements Iterable<Word> {
@Override
public Word next() {
Position currentPos = mPositions.getLast();
- mCurrentString.setLength(mCurrentString.length() - currentPos.length);
+ mCurrentString.setLength(currentPos.length);
do {
if (currentPos.pos.hasNext()) {
final CharGroup currentGroup = currentPos.pos.next();
- currentPos.length = currentGroup.mChars.length;
+ currentPos.length = mCurrentString.length();
for (int i : currentGroup.mChars)
mCurrentString.append(Character.toChars(i));
if (null != currentGroup.mChildren) {
@@ -866,7 +866,7 @@ public final class FusionDictionary implements Iterable<Word> {
} else {
mPositions.removeLast();
currentPos = mPositions.getLast();
- mCurrentString.setLength(mCurrentString.length() - mPositions.getLast().length);
+ mCurrentString.setLength(mPositions.getLast().length);
}
} while (true);
}
diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java
index 1593e23f6..fbfa9c977 100644
--- a/java/src/com/android/inputmethod/research/ResearchLogger.java
+++ b/java/src/com/android/inputmethod/research/ResearchLogger.java
@@ -1122,10 +1122,6 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
}
- public void latinIME_onFinishInputViewInternal() {
- stop();
- }
-
/**
* Log a change in preferences.
*
@@ -1208,16 +1204,22 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
}
/**
- * Log a call to LatinIME.onWindowHidden().
+ * The IME is finishing; it is either being destroyed, or is about to be hidden.
*
* UserAction: The user has performed an action that has caused the IME to be closed. They may
* have focused on something other than a text field, or explicitly closed it.
*/
- private static final LogStatement LOGSTATEMENT_LATINIME_ONWINDOWHIDDEN =
- new LogStatement("LatinIMEOnWindowHidden", false, false, "isTextTruncated", "text");
- public static void latinIME_onWindowHidden(final int savedSelectionStart,
- final int savedSelectionEnd, final InputConnection ic) {
- if (ic != null) {
+ private static final LogStatement LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL =
+ new LogStatement("LatinIMEOnFinishInputViewInternal", false, false, "isTextTruncated",
+ "text");
+ public static void latinIME_onFinishInputViewInternal(final boolean finishingInput,
+ final int savedSelectionStart, final int savedSelectionEnd, final InputConnection ic) {
+ // The finishingInput flag is set in InputMethodService. It is true if called from
+ // doFinishInput(), which can be called as part of doStartInput(). This can happen at times
+ // when the IME is not closing, such as when powering up. The finishinInput flag is false
+ // if called from finishViews(), which is called from hideWindow() and onDestroy(). These
+ // are the situations in which we want to finish up the researchLog.
+ if (ic != null && !finishingInput) {
final boolean isTextTruncated;
final String text;
if (LOG_FULL_TEXTVIEW_CONTENTS) {
@@ -1261,8 +1263,8 @@ public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChang
// Assume that OUTPUT_ENTIRE_BUFFER is only true when we don't care about privacy (e.g.
// during a live user test), so the normal isPotentiallyPrivate and
// isPotentiallyRevealing flags do not apply
- researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONWINDOWHIDDEN, isTextTruncated,
- text);
+ researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL,
+ isTextTruncated, text);
researchLogger.commitCurrentLogUnit();
getInstance().stop();
}