diff options
Diffstat (limited to 'java/src')
29 files changed, 903 insertions, 1055 deletions
diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java index fb75d6dc0..618322357 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java @@ -66,6 +66,8 @@ public final class DictionarySettingsFragment extends PreferenceFragment private boolean mChangedSettings; private DictionaryListInterfaceState mDictionaryListInterfaceState = new DictionaryListInterfaceState(); + private TreeMap<String, WordListPreference> mCurrentPreferenceMap = + new TreeMap<String, WordListPreference>(); // never null private final BroadcastReceiver mConnectivityChangedReceiver = new BroadcastReceiver() { @Override @@ -278,7 +280,7 @@ public final class DictionarySettingsFragment extends PreferenceFragment return result; } else { final String systemLocaleString = Locale.getDefault().toString(); - final TreeMap<String, WordListPreference> prefList = + final TreeMap<String, WordListPreference> prefMap = new TreeMap<String, WordListPreference>(); final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN); final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN); @@ -299,16 +301,31 @@ public final class DictionarySettingsFragment extends PreferenceFragment // The key is sorted in lexicographic order, according to the match level, then // the description. final String key = matchLevelString + "." + description + "." + wordlistId; - final WordListPreference existingPref = prefList.get(key); + final WordListPreference existingPref = prefMap.get(key); if (null == existingPref || hasPriority(status, existingPref.mStatus)) { - final WordListPreference pref = new WordListPreference(activity, - mDictionaryListInterfaceState, mClientId, wordlistId, version, locale, - description, status, filesize); - prefList.put(key, pref); + final WordListPreference oldPreference = mCurrentPreferenceMap.get(key); + final WordListPreference pref; + if (null != oldPreference + && oldPreference.mVersion == version + && oldPreference.mLocale.equals(locale)) { + // If the old preference has all the new attributes, reuse it. We test + // for version and locale because although attributes other than status + // need to be the same, others have been tested through the key of the + // map. Also, status may differ so we don't want to use #equals() here. + pref = oldPreference; + pref.mStatus = status; + } else { + // Otherwise, discard it and create a new one instead. + pref = new WordListPreference(activity, mDictionaryListInterfaceState, + mClientId, wordlistId, version, locale, description, status, + filesize); + } + prefMap.put(key, pref); } } while (cursor.moveToNext()); cursor.close(); - return prefList.values(); + mCurrentPreferenceMap = prefMap; + return prefMap.values(); } } diff --git a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java index 29015d61b..451a0fb82 100644 --- a/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java +++ b/java/src/com/android/inputmethod/dictionarypack/WordListPreference.java @@ -58,6 +58,8 @@ public final class WordListPreference extends Preference { // The metadata word list id and version of this word list. public final String mWordlistId; public final int mVersion; + public final Locale mLocale; + public final String mDescription; // The status public int mStatus; // The size of the dictionary file @@ -80,6 +82,8 @@ public final class WordListPreference extends Preference { mVersion = version; mWordlistId = wordlistId; mFilesize = filesize; + mLocale = locale; + mDescription = description; setLayoutResource(R.layout.dictionary_line); diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index bc9e8cdd4..1fe23a330 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -29,17 +29,18 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.text.InputType; import android.text.TextUtils; +import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; 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; import com.android.inputmethod.keyboard.internal.KeysCache; +import com.android.inputmethod.latin.AdditionalSubtype; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.InputTypeUtils; @@ -72,6 +73,8 @@ public final class KeyboardLayoutSet { private static final String TAG_ELEMENT = "Element"; private static final String KEYBOARD_LAYOUT_SET_RESOURCE_PREFIX = "keyboard_layout_set_"; + private static final int SPELLCHECKER_DUMMY_KEYBOARD_WIDTH = 480; + private static final int SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT = 800; private final Context mContext; private final Params mParams; @@ -282,8 +285,7 @@ public final class KeyboardLayoutSet { return this; } - @UsedForTesting - public void disableTouchPositionCorrectionDataForTest() { + public void disableTouchPositionCorrectionData() { mParams.mDisableTouchPositionCorrectionDataForTest = true; } @@ -413,4 +415,47 @@ public final class KeyboardLayoutSet { } } } + + public static KeyboardLayoutSet createKeyboardSetForSpellChecker(final Context context, + final String locale, final String layout) { + final InputMethodSubtype subtype = + AdditionalSubtype.createAdditionalSubtype(locale, layout, null); + return createKeyboardSet(context, subtype, SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, + SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT, false); + } + + public static KeyboardLayoutSet createKeyboardSetForTest(final Context context, + final InputMethodSubtype subtype, final int orientation, + final boolean testCasesHaveTouchCoordinates) { + final DisplayMetrics dm = context.getResources().getDisplayMetrics(); + final int width; + final int height; + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + width = Math.max(dm.widthPixels, dm.heightPixels); + height = Math.min(dm.widthPixels, dm.heightPixels); + } else if (orientation == Configuration.ORIENTATION_PORTRAIT) { + width = Math.min(dm.widthPixels, dm.heightPixels); + height = Math.max(dm.widthPixels, dm.heightPixels); + } else { + throw new RuntimeException("Orientation should be ORIENTATION_LANDSCAPE or " + + "ORIENTATION_PORTRAIT: orientation=" + orientation); + } + return createKeyboardSet(context, subtype, width, height, testCasesHaveTouchCoordinates); + } + + private static KeyboardLayoutSet createKeyboardSet(final Context context, + final InputMethodSubtype subtype, final int width, final int height, + final boolean testCasesHaveTouchCoordinates) { + final EditorInfo editorInfo = new EditorInfo(); + editorInfo.inputType = InputType.TYPE_CLASS_TEXT; + final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( + context, editorInfo); + builder.setScreenGeometry(width, height); + builder.setSubtype(subtype); + if (!testCasesHaveTouchCoordinates) { + // For spell checker and tests + builder.disableTouchPositionCorrectionData(); + } + return builder.build(); + } } diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 0556fddd3..2d791648e 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -25,6 +25,7 @@ import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.keyboard.internal.GestureStroke; import com.android.inputmethod.keyboard.internal.GestureStroke.GestureStrokeParams; import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints; +import com.android.inputmethod.keyboard.internal.GestureStrokeWithPreviewPoints.GestureStrokePreviewParams; import com.android.inputmethod.keyboard.internal.PointerTrackerQueue; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.Constants; @@ -161,6 +162,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { // Parameters for pointer handling. private static PointerTrackerParams sParams; private static GestureStrokeParams sGestureStrokeParams; + private static GestureStrokePreviewParams sGesturePreviewParams; private static boolean sNeedsPhantomSuddenMoveEventHack; // Move this threshold to resource. // TODO: Device specific parameter would be better for device specific hack? @@ -339,12 +341,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element { sNeedsPhantomSuddenMoveEventHack = needsPhantomSuddenMoveEventHack; sParams = PointerTrackerParams.DEFAULT; sGestureStrokeParams = GestureStrokeParams.DEFAULT; + sGesturePreviewParams = GestureStrokePreviewParams.DEFAULT; sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); } public static void setParameters(final TypedArray mainKeyboardViewAttr) { sParams = new PointerTrackerParams(mainKeyboardViewAttr); sGestureStrokeParams = new GestureStrokeParams(mainKeyboardViewAttr); + sGesturePreviewParams = new GestureStrokePreviewParams(mainKeyboardViewAttr); sTimeRecorder = new TimeRecorder(sParams, sGestureStrokeParams); } @@ -428,7 +432,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element { } mPointerId = id; mGestureStrokeWithPreviewPoints = new GestureStrokeWithPreviewPoints( - id, sGestureStrokeParams); + id, sGestureStrokeParams, sGesturePreviewParams); setKeyDetectorInner(handler.getKeyDetector()); mListener = handler.getKeyboardActionListener(); mDrawingProxy = handler.getDrawingProxy(); diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index b77e378bf..57d3fede4 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -79,23 +79,6 @@ public class ProximityInfo { mNativeProximityInfo = createNativeProximityInfo(touchPositionCorrection); } - /** - * Constructor for subclasses such as - * {@link com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo}. - */ - protected ProximityInfo(final int[] proximityCharsArray, final int gridWidth, - final int gridHeight) { - this("", 1, 1, 1, 1, 1, 1, EMPTY_KEY_ARRAY, null); - mNativeProximityInfo = setProximityInfoNative("" /* locale */, - gridWidth /* displayWidth */, gridHeight /* displayHeight */, - gridWidth, gridHeight, 1 /* mostCommonKeyWidth */, - 1 /* mostCommonKeyHeight */, proximityCharsArray, 0 /* keyCount */, - null /*keyXCoordinates */, null /* keyYCoordinates */, - null /* keyWidths */, null /* keyHeights */, null /* keyCharCodes */, - null /* sweetSpotCenterXs */, null /* sweetSpotCenterYs */, - null /* sweetSpotRadii */); - } - private long mNativeProximityInfo; static { JniUtils.loadNativeLibrary(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java index 7fd1bedcb..761d9dcd6 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GesturePreviewTrail.java @@ -18,6 +18,7 @@ package com.android.inputmethod.keyboard.internal; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; @@ -35,12 +36,19 @@ import com.android.inputmethod.latin.ResizableIntArray; * @attr ref R.styleable#MainKeyboardView_gesturePreviewTrailWidth */ final class GesturePreviewTrail { + 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; + private static final int DEFAULT_CAPACITY = GestureStrokeWithPreviewPoints.PREVIEW_CAPACITY; // These three {@link ResizableIntArray}s should be synchronized by {@link #mEventTimes}. private final ResizableIntArray mXCoordinates = new ResizableIntArray(DEFAULT_CAPACITY); 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); private int mCurrentStrokeId = -1; // The wall time of the zero value in {@link #mEventTimes} private long mCurrentTimeBase; @@ -75,9 +83,9 @@ final class GesturePreviewTrail { R.styleable.MainKeyboardView_gesturePreviewTrailShadowRatio, 0); mTrailShadowEnabled = (trailShadowRatioInt > 0); mTrailShadowRatio = (float)trailShadowRatioInt / (float)PERCENTAGE_INT; - mFadeoutStartDelay = mainKeyboardViewAttr.getInt( + mFadeoutStartDelay = DBG_SHOW_POINTS ? 2000 : mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutStartDelay, 0); - mFadeoutDuration = mainKeyboardViewAttr.getInt( + mFadeoutDuration = DBG_SHOW_POINTS ? 200 : mainKeyboardViewAttr.getInt( R.styleable.MainKeyboardView_gesturePreviewTrailFadeoutDuration, 0); mTrailLingerDuration = mFadeoutStartDelay + mFadeoutDuration; mUpdateInterval = mainKeyboardViewAttr.getInt( @@ -125,7 +133,7 @@ final class GesturePreviewTrail { final int lastInterpolatedIndex = (strokeId == mCurrentStrokeId) ? mLastInterpolatedDrawIndex : trailSize; mLastInterpolatedDrawIndex = stroke.interpolateStrokeAndReturnStartIndexOfLastSegment( - lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates); + lastInterpolatedIndex, mEventTimes, mXCoordinates, mYCoordinates, mPointTypes); if (strokeId != mCurrentStrokeId) { final int elapsedTime = (int)(downTime - mCurrentTimeBase); for (int i = mTrailStartIndex; i < trailSize; i++) { @@ -204,6 +212,7 @@ final class GesturePreviewTrail { final int[] eventTimes = mEventTimes.getPrimitiveArray(); final int[] xCoords = mXCoordinates.getPrimitiveArray(); final int[] yCoords = mYCoordinates.getPrimitiveArray(); + final int[] pointTypes = mPointTypes.getPrimitiveArray(); final int sinceDown = (int)(SystemClock.uptimeMillis() - mCurrentTimeBase); int startIndex; for (startIndex = mTrailStartIndex; startIndex < trailSize; startIndex++) { @@ -246,6 +255,17 @@ final class GesturePreviewTrail { 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; @@ -265,6 +285,9 @@ final class GesturePreviewTrail { mEventTimes.setLength(newSize); mXCoordinates.setLength(newSize); mYCoordinates.setLength(newSize); + if (DBG_SHOW_POINTS) { + mPointTypes.setLength(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 or been zeroed. diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java index 93ff26466..70363e602 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStroke.java @@ -145,7 +145,7 @@ public class GestureStroke { public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { mKeyWidth = keyWidth; mMinYCoordinate = -(int)(keyboardHeight * EXTRA_GESTURE_TRAIL_AREA_ABOVE_KEYBOARD_RATIO); - mMaxYCoordinate = keyboardHeight - 1; + mMaxYCoordinate = keyboardHeight; // TODO: Find an appropriate base metric for these length. Maybe diagonal length of the key? mDetectFastMoveSpeedThreshold = (int)(keyWidth * mParams.mDetectFastMoveSpeedThreshold); mGestureDynamicDistanceThresholdFrom = diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java index 7a51e2568..ccb8802c6 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureStrokeWithPreviewPoints.java @@ -16,6 +16,9 @@ package com.android.inputmethod.keyboard.internal; +import android.content.res.TypedArray; + +import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.ResizableIntArray; public final class GestureStrokeWithPreviewPoints extends GestureStroke { @@ -25,6 +28,8 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { private final ResizableIntArray mPreviewXCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); private final ResizableIntArray mPreviewYCoordinates = new ResizableIntArray(PREVIEW_CAPACITY); + private final GestureStrokePreviewParams mPreviewParams; + private int mStrokeId; private int mLastPreviewSize; private final HermiteInterpolator mInterpolator = new HermiteInterpolator(); @@ -32,23 +37,53 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { private int mLastX; private int mLastY; - private double mMinPreviewSamplingDistance; private double mDistanceFromLastSample; - private double mInterpolationDistanceThreshold; - - // TODO: Move these constants to resource. - // TODO: Use "dp" instead of ratio to the keyWidth because table has rather large keys. - // 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 = 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; - // The distance threshold to use interpolation in keyWidth unit. - private static final float INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH = 0.5f; - private static final int MAX_INTERPOLATION_PARTITIONS = 6; - - public GestureStrokeWithPreviewPoints(final int pointerId, final GestureStrokeParams params) { - super(pointerId, params); + + public static final class GestureStrokePreviewParams { + public final double mMinSamplingDistance; // in pixel + public final double mMaxInterpolationAngularThreshold; // in radian + public final double mMaxInterpolationDistanceThreshold; // in pixel + public final int mMaxInterpolationSegments; + + public static final GestureStrokePreviewParams DEFAULT = new GestureStrokePreviewParams(); + + private static final int DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD = 15; // in degree + + private GestureStrokePreviewParams() { + mMinSamplingDistance = 0.0d; + mMaxInterpolationAngularThreshold = + degreeToRadian(DEFAULT_MAX_INTERPOLATION_ANGULAR_THRESHOLD); + mMaxInterpolationDistanceThreshold = mMinSamplingDistance; + mMaxInterpolationSegments = 4; + } + + private static double degreeToRadian(final int degree) { + return (double)degree / 180.0d * Math.PI; + } + + public GestureStrokePreviewParams(final TypedArray mainKeyboardViewAttr) { + mMinSamplingDistance = mainKeyboardViewAttr.getDimension( + R.styleable.MainKeyboardView_gesturePreviewTrailMinSamplingDistance, + (float)DEFAULT.mMinSamplingDistance); + final int interpolationAngularDegree = mainKeyboardViewAttr.getInteger(R.styleable + .MainKeyboardView_gesturePreviewTrailMaxInterpolationAngularThreshold, 0); + mMaxInterpolationAngularThreshold = (interpolationAngularDegree <= 0) + ? DEFAULT.mMaxInterpolationAngularThreshold + : degreeToRadian(interpolationAngularDegree); + mMaxInterpolationDistanceThreshold = mainKeyboardViewAttr.getDimension(R.styleable + .MainKeyboardView_gesturePreviewTrailMaxInterpolationDistanceThreshold, + (float)DEFAULT.mMaxInterpolationDistanceThreshold); + mMaxInterpolationSegments = mainKeyboardViewAttr.getInteger( + R.styleable.MainKeyboardView_gesturePreviewTrailMaxInterpolationSegments, + DEFAULT.mMaxInterpolationSegments); + } + } + + public GestureStrokeWithPreviewPoints(final int pointerId, + final GestureStrokeParams strokeParams, + final GestureStrokePreviewParams previewParams) { + super(pointerId, strokeParams); + mPreviewParams = previewParams; } @Override @@ -66,19 +101,12 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { return mStrokeId; } - @Override - public void setKeyboardGeometry(final int keyWidth, final int keyboardHeight) { - super.setKeyboardGeometry(keyWidth, keyboardHeight); - mMinPreviewSamplingDistance = keyWidth * MIN_PREVIEW_SAMPLING_RATIO_TO_KEY_WIDTH; - mInterpolationDistanceThreshold = keyWidth * INTERPOLATION_DISTANCE_THRESHOLD_TO_KEY_WIDTH; - } - private boolean needsSampling(final int x, final int y) { mDistanceFromLastSample += Math.hypot(x - mLastX, y - mLastY); mLastX = x; mLastY = y; final boolean isDownEvent = (mPreviewEventTimes.getLength() == 0); - if (mDistanceFromLastSample >= mMinPreviewSamplingDistance || isDownEvent) { + if (mDistanceFromLastSample >= mPreviewParams.mMinSamplingDistance || isDownEvent) { mDistanceFromLastSample = 0.0d; return true; } @@ -124,7 +152,7 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { */ public int interpolateStrokeAndReturnStartIndexOfLastSegment(final int lastInterpolatedIndex, final ResizableIntArray eventTimes, final ResizableIntArray xCoords, - final ResizableIntArray yCoords) { + final ResizableIntArray yCoords, final ResizableIntArray types) { final int size = mPreviewEventTimes.getLength(); final int[] pt = mPreviewEventTimes.getPrimitiveArray(); final int[] px = mPreviewXCoordinates.getPrimitiveArray(); @@ -144,28 +172,34 @@ public final class GestureStrokeWithPreviewPoints extends GestureStroke { final double m1 = Math.atan2(mInterpolator.mSlope1Y, mInterpolator.mSlope1X); final double m2 = Math.atan2(mInterpolator.mSlope2Y, mInterpolator.mSlope2X); final double deltaAngle = Math.abs(angularDiff(m2, m1)); - final int partitionsByAngle = (int)Math.ceil( - deltaAngle / INTERPOLATION_ANGULAR_THRESHOLD); + final int segmentsByAngle = (int)Math.ceil( + deltaAngle / mPreviewParams.mMaxInterpolationAngularThreshold); final double deltaDistance = Math.hypot(mInterpolator.mP1X - mInterpolator.mP2X, mInterpolator.mP1Y - mInterpolator.mP2Y); - final int partitionsByDistance = (int)Math.ceil(deltaDistance - / mInterpolationDistanceThreshold); - final int partitions = Math.min(MAX_INTERPOLATION_PARTITIONS, - Math.max(partitionsByAngle, partitionsByDistance)); + final int segmentsByDistance = (int)Math.ceil(deltaDistance + / mPreviewParams.mMaxInterpolationDistanceThreshold); + final int segments = Math.min(mPreviewParams.mMaxInterpolationSegments, + Math.max(segmentsByAngle, segmentsByDistance)); final int t1 = eventTimes.get(d1); final int dt = pt[p2] - pt[p1]; d1++; - for (int i = 1; i < partitions; i++) { - final float t = i / (float)partitions; + for (int i = 1; i < segments; i++) { + final float t = i / (float)segments; mInterpolator.interpolate(t); eventTimes.add(d1, (int)(dt * t) + t1); xCoords.add(d1, (int)mInterpolator.mInterpolatedX); yCoords.add(d1, (int)mInterpolator.mInterpolatedY); + if (GesturePreviewTrail.DBG_SHOW_POINTS) { + types.add(d1, GesturePreviewTrail.POINT_TYPE_INTERPOLATED); + } d1++; } eventTimes.add(d1, pt[p2]); xCoords.add(d1, px[p2]); yCoords.add(d1, py[p2]); + if (GesturePreviewTrail.DBG_SHOW_POINTS) { + types.add(d1, GesturePreviewTrail.POINT_TYPE_SAMPLED); + } } return lastInterpolatedDrawIndex; } diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index 75c2cf2c8..b9db9a092 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -16,21 +16,26 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.latin.personalization.AccountUtils; + import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; +import android.net.Uri; import android.os.SystemClock; import android.provider.BaseColumns; +import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.text.TextUtils; import android.util.Log; +import java.util.List; import java.util.Locale; public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { - private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME,}; + private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME}; private static final String[] PROJECTION_ID_ONLY = {BaseColumns._ID}; private static final String TAG = ContactsBinaryDictionary.class.getSimpleName(); @@ -102,9 +107,32 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { @Override public void loadDictionaryAsync() { + clearFusionDictionary(); + loadDeviceAccountsEmailAddresses(); + loadDictionaryAsyncForUri(ContactsContract.Profile.CONTENT_URI); + // TODO: Switch this URL to the newer ContactsContract too + loadDictionaryAsyncForUri(Contacts.CONTENT_URI); + } + + private void loadDeviceAccountsEmailAddresses() { + final List<String> accountVocabulary = + AccountUtils.getDeviceAccountsEmailAddresses(mContext); + if (accountVocabulary == null || accountVocabulary.isEmpty()) { + return; + } + for (String word : accountVocabulary) { + if (DEBUG) { + Log.d(TAG, "loadAccountVocabulary: " + word); + } + super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, + false /* isNotAWord */); + } + } + + private void loadDictionaryAsyncForUri(final Uri uri) { try { Cursor cursor = mContext.getContentResolver() - .query(Contacts.CONTENT_URI, PROJECTION, null, null, null); + .query(uri, PROJECTION, null, null, null); if (cursor != null) { try { if (cursor.moveToFirst()) { @@ -129,7 +157,6 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } private void addWords(final Cursor cursor) { - clearFusionDictionary(); int count = 0; while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) { String name = cursor.getString(INDEX_NAME); @@ -173,6 +200,9 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { // capitalization of i. final int wordLen = StringUtils.codePointCount(word); if (wordLen < MAX_WORD_LENGTH && wordLen > 1) { + if (DEBUG) { + Log.d(TAG, "addName " + name + ", " + word + ", " + prevWord); + } super.addWord(word, null /* shortcut */, FREQUENCY_FOR_CONTACTS, false /* isNotAWord */); if (!TextUtils.isEmpty(prevWord)) { diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 84c752934..fdd470cf1 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -250,6 +250,7 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } public void postResumeSuggestions() { + removeMessages(MSG_RESUME_SUGGESTIONS); sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions); } @@ -759,7 +760,8 @@ public final class LatinIME extends InputMethodService implements KeyboardAction } mSuggestedWords = SuggestedWords.EMPTY; - mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart); + mConnection.resetCachesUponCursorMove(editorInfo.initialSelStart, + false /* shouldFinishComposition */); if (isDifferentTextField) { mainKeyboardView.closing(); @@ -1148,13 +1150,14 @@ public final class LatinIME extends InputMethodService implements KeyboardAction // This will reset the whole input state to the starting state. It will clear // the composing word, reset the last composed word, tell the inputconnection about it. private void resetEntireInputState(final int newCursorPosition) { + final boolean shouldFinishComposition = mWordComposer.isComposingWord(); resetComposingState(true /* alsoResetLastComposedWord */); if (mSettings.getCurrent().mBigramPredictionEnabled) { clearSuggestionStrip(); } else { setSuggestedWords(mSettings.getCurrent().mSuggestPuncList, false); } - mConnection.resetCachesUponCursorMove(newCursorPosition); + mConnection.resetCachesUponCursorMove(newCursorPosition, shouldFinishComposition); } private void resetComposingState(final boolean alsoResetLastComposedWord) { @@ -2436,10 +2439,15 @@ public final class LatinIME extends InputMethodService implements KeyboardAction private void restartSuggestionsOnWordTouchedByCursor() { // 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. + if (mLastSelectionStart < 0) return; if (!mConnection.isCursorTouchingWord(mSettings.getCurrent())) return; final Range range = mConnection.getWordRangeAtCursor(mSettings.getWordSeparators(), 0 /* additionalPrecedingWordsCount */); if (null == range) return; // Happens if we don't have an input connection at all + // If for some strange reason (editor bug or so) we measure the text before the cursor as + // longer than what the entire text is supposed to be, the safe thing to do is bail out. + if (range.mCharsBefore > mLastSelectionStart) return; final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); final String typedWord = range.mWord.toString(); if (range.mWord instanceof SpannableString) { diff --git a/java/src/com/android/inputmethod/latin/ResourceUtils.java b/java/src/com/android/inputmethod/latin/ResourceUtils.java index b74b979b5..f0bfe75fe 100644 --- a/java/src/com/android/inputmethod/latin/ResourceUtils.java +++ b/java/src/com/android/inputmethod/latin/ResourceUtils.java @@ -35,8 +35,7 @@ public final class ResourceUtils { // This utility class is not publicly instantiable. } - private static final String DEFAULT_PREFIX = "DEFAULT,"; - private static final String HARDWARE_PREFIX = Build.HARDWARE + ","; + private static final String DEFAULT_KEY = "DEFAULT"; private static final HashMap<String, String> sDeviceOverrideValueMap = CollectionUtils.newHashMap(); @@ -48,28 +47,29 @@ public final class ResourceUtils { } final String[] overrideArray = res.getStringArray(overrideResId); - final String overrideValue = StringUtils.findPrefixedString(HARDWARE_PREFIX, overrideArray); + final String hardwareKey = "HARDWARE=" + Build.HARDWARE; + final String overrideValue = StringUtils.findValueOfKey(hardwareKey, overrideArray); // The overrideValue might be an empty string. if (overrideValue != null) { if (DEBUG) { Log.d(TAG, "Find override value:" + " resource="+ res.getResourceEntryName(overrideResId) - + " Build.HARDWARE=" + Build.HARDWARE + " override=" + overrideValue); + + " " + hardwareKey + " override=" + overrideValue); } sDeviceOverrideValueMap.put(key, overrideValue); return overrideValue; } - final String defaultValue = StringUtils.findPrefixedString(DEFAULT_PREFIX, overrideArray); + final String defaultValue = StringUtils.findValueOfKey(DEFAULT_KEY, overrideArray); // The defaultValue might be an empty string. if (defaultValue == null) { Log.w(TAG, "Couldn't find override value nor default value:" + " resource="+ res.getResourceEntryName(overrideResId) - + " Build.HARDWARE=" + Build.HARDWARE); + + " " + hardwareKey); } else if (DEBUG) { Log.d(TAG, "Found default value:" + " resource="+ res.getResourceEntryName(overrideResId) - + " Build.HARDWARE=" + Build.HARDWARE + " default=" + defaultValue); + + " " + hardwareKey + " " + DEFAULT_KEY + "=" + defaultValue); } sDeviceOverrideValueMap.put(key, defaultValue); return defaultValue; diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 8ed7ab264..980215de6 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -135,13 +135,14 @@ public final class RichInputConnection { if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); } - public void resetCachesUponCursorMove(final int newCursorPosition) { + public void resetCachesUponCursorMove(final int newCursorPosition, + final boolean shouldFinishComposition) { mCurrentCursorPosition = newCursorPosition; mComposingText.setLength(0); mCommittedTextBeforeComposingText.setLength(0); final CharSequence textBeforeCursor = getTextBeforeCursor(DEFAULT_TEXT_CACHE_SIZE, 0); if (null != textBeforeCursor) mCommittedTextBeforeComposingText.append(textBeforeCursor); - if (null != mIC) { + if (null != mIC && shouldFinishComposition) { mIC.finishComposingText(); if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { ResearchLogger.richInputConnection_finishComposingText(); diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java index 3f7be99e5..94513e635 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -100,6 +100,12 @@ public final class RichInputMethodManager { throw new RuntimeException("Input method id for " + packageName + " not found."); } + public List<InputMethodSubtype> getMyEnabledInputMethodSubtypeList( + boolean allowsImplicitlySelectedSubtypes) { + return mImmWrapper.mImm.getEnabledInputMethodSubtypeList( + mInputMethodInfoOfThisIme, allowsImplicitlySelectedSubtypes); + } + public boolean switchToNextInputMethod(final IBinder token, final boolean onlyCurrentIme) { if (mImmWrapper.switchToNextInputMethod(token, onlyCurrentIme)) { return true; @@ -116,8 +122,8 @@ public final class RichInputMethodManager { final boolean onlyCurrentIme) { final InputMethodManager imm = mImmWrapper.mImm; final InputMethodSubtype currentSubtype = imm.getCurrentInputMethodSubtype(); - final List<InputMethodSubtype> enabledSubtypes = imm.getEnabledInputMethodSubtypeList( - mInputMethodInfoOfThisIme, true /* allowsImplicitlySelectedSubtypes */); + final List<InputMethodSubtype> enabledSubtypes = getMyEnabledInputMethodSubtypeList( + true /* allowsImplicitlySelectedSubtypes */); final int currentIndex = getSubtypeIndexInList(currentSubtype, enabledSubtypes); if (currentIndex == INDEX_NOT_FOUND) { Log.w(TAG, "Can't find current subtype in enabled subtypes: subtype=" @@ -214,8 +220,8 @@ public final class RichInputMethodManager { final InputMethodSubtype subtype) { final boolean subtypeEnabled = checkIfSubtypeBelongsToThisImeAndEnabled(subtype); final boolean subtypeExplicitlyEnabled = checkIfSubtypeBelongsToList( - subtype, mImmWrapper.mImm.getEnabledInputMethodSubtypeList( - mInputMethodInfoOfThisIme, false /* allowsImplicitlySelectedSubtypes */)); + subtype, getMyEnabledInputMethodSubtypeList( + false /* allowsImplicitlySelectedSubtypes */)); return subtypeEnabled && !subtypeExplicitlyEnabled; } @@ -312,8 +318,7 @@ public final class RichInputMethodManager { if (filteredImisCount > 1) { return true; } - final List<InputMethodSubtype> subtypes = - mImmWrapper.mImm.getEnabledInputMethodSubtypeList(null, true); + final List<InputMethodSubtype> subtypes = getMyEnabledInputMethodSubtypeList(true); int keyboardCount = 0; // imm.getEnabledInputMethodSubtypeList(null, true) will return the current IME's // both explicitly and implicitly enabled input method subtype. diff --git a/java/src/com/android/inputmethod/latin/StringUtils.java b/java/src/com/android/inputmethod/latin/StringUtils.java index d5ee58a63..5ff101f7a 100644 --- a/java/src/com/android/inputmethod/latin/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/StringUtils.java @@ -65,17 +65,24 @@ public final class StringUtils { } /** - * Find a string that start with specified prefix from an array. + * Find a value that has a specified key from an array of key-comma-value. * - * @param prefix a prefix string to find. - * @param array an string array to be searched. - * @return the rest part of the string that starts with the prefix. + * @param key a key string to find. + * @param array an array of key-comma-value string to be searched. + * @return the value part of the first string that has a specified key. * Returns null if it couldn't be found. */ - public static String findPrefixedString(final String prefix, final String[] array) { + public static String findValueOfKey(final String key, final String[] array) { + if (array == null) { + return null; + } for (final String element : array) { - if (element.startsWith(prefix)) { - return element.substring(prefix.length()); + final int posComma = element.indexOf(','); + if (posComma < 0) { + throw new RuntimeException("Element has no comma: " + element); + } + if (element.substring(0, posComma).equals(key)) { + return element.substring(posComma + 1); } } return null; diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index bef8a3cf1..282b5794f 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -115,7 +115,7 @@ public final class SubtypeSwitcher { */ public void updateParametersOnStartInputView() { final List<InputMethodSubtype> enabledSubtypesOfThisIme = - mRichImm.getInputMethodManager().getEnabledInputMethodSubtypeList(null, true); + mRichImm.getMyEnabledInputMethodSubtypeList(true); mNeedsToDisplayLanguage.updateEnabledSubtypeCount(enabledSubtypesOfThisIme.size()); updateShortcutIME(); } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 51bd901fb..e078f03f4 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -16,7 +16,6 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; @@ -211,9 +210,8 @@ public final class WordComposer { } /** - * Internal method to retrieve reasonable proximity info for a character. + * Add a dummy key by retrieving reasonable coordinates */ - @UsedForTesting public void addKeyInfo(final int codePoint, final Keyboard keyboard) { final int x, y; final Key key; diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java new file mode 100644 index 000000000..93687e193 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java @@ -0,0 +1,47 @@ +/* + * 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.latin.personalization; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.Context; +import android.util.Patterns; + +import java.util.ArrayList; +import java.util.List; + +public class AccountUtils { + private AccountUtils() { + // This utility class is not publicly instantiable. + } + + private static Account[] getAccounts(final Context context) { + return AccountManager.get(context).getAccounts(); + } + + public static List<String> getDeviceAccountsEmailAddresses(final Context context) { + final ArrayList<String> retval = new ArrayList<String>(); + for (final Account account : getAccounts(context)) { + final String name = account.name; + if (Patterns.EMAIL_ADDRESS.matcher(name).matches()) { + retval.add(name); + retval.add(name.split("@")[0]); + } + } + return retval; + } +} diff --git a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java index affe3a348..8a2de887d 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupActivity.java @@ -17,270 +17,27 @@ package com.android.inputmethod.latin.setup; import android.app.Activity; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.res.Resources; -import android.media.MediaPlayer; -import android.net.Uri; import android.os.Bundle; -import android.os.Message; import android.provider.Settings; -import android.util.Log; -import android.view.View; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.VideoView; -import com.android.inputmethod.compat.TextViewCompatUtils; -import com.android.inputmethod.compat.ViewCompatUtils; -import com.android.inputmethod.latin.CollectionUtils; -import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; -import com.android.inputmethod.latin.SettingsActivity; -import com.android.inputmethod.latin.StaticInnerHandlerWrapper; - -import java.util.ArrayList; - -// TODO: Use Fragment to implement welcome screen and setup steps. -public final class SetupActivity extends Activity implements View.OnClickListener { - private static final String TAG = SetupActivity.class.getSimpleName(); - - private View mWelcomeScreen; - private View mSetupScreen; - private Uri mWelcomeVideoUri; - private VideoView mWelcomeVideoView; - private View mActionStart; - private View mActionNext; - private TextView mStep1Bullet; - private TextView mActionFinish; - private SetupStepGroup mSetupStepGroup; - private static final String STATE_STEP = "step"; - private int mStepNumber; - private static final int STEP_WELCOME = 0; - private static final int STEP_1 = 1; - private static final int STEP_2 = 2; - private static final int STEP_3 = 3; - private boolean mWasLanguageAndInputSettingsInvoked; - - private final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this); - - static final class SettingsPoolingHandler extends StaticInnerHandlerWrapper<SetupActivity> { - private static final int MSG_POLLING_IME_SETTINGS = 0; - private static final long IME_SETTINGS_POLLING_INTERVAL = 200; - - public SettingsPoolingHandler(final SetupActivity outerInstance) { - super(outerInstance); - } - - @Override - public void handleMessage(final Message msg) { - final SetupActivity setupActivity = getOuterInstance(); - if (setupActivity == null) { - return; - } - switch (msg.what) { - case MSG_POLLING_IME_SETTINGS: - if (SetupActivity.isThisImeEnabled(setupActivity)) { - setupActivity.invokeSetupWizardOfThisIme(); - return; - } - startPollingImeSettings(); - break; - } - } - - public void startPollingImeSettings() { - sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS), - IME_SETTINGS_POLLING_INTERVAL); - } - - public void cancelPollingImeSettings() { - removeMessages(MSG_POLLING_IME_SETTINGS); - } - } +public final class SetupActivity extends Activity { @Override protected void onCreate(final Bundle savedInstanceState) { - setTheme(android.R.style.Theme_DeviceDefault_Light_NoActionBar); super.onCreate(savedInstanceState); - - setContentView(R.layout.setup_wizard); - - RichInputMethodManager.init(this); - - if (savedInstanceState == null) { - mStepNumber = determineSetupStepNumber(); - if (mStepNumber == STEP_1 && !mWasLanguageAndInputSettingsInvoked) { - mStepNumber = STEP_WELCOME; - } - if (mStepNumber == STEP_3) { - // This IME already has been enabled and set as current IME. - // TODO: Implement tutorial. - invokeSettingsOfThisIme(); - finish(); - return; - } - } else { - mStepNumber = savedInstanceState.getInt(STATE_STEP); - } - - final String applicationName = getResources().getString(getApplicationInfo().labelRes); - mWelcomeScreen = findViewById(R.id.setup_welcome_screen); - final TextView welcomeTitle = (TextView)findViewById(R.id.setup_welcome_title); - welcomeTitle.setText(getString(R.string.setup_welcome_title, applicationName)); - - mSetupScreen = findViewById(R.id.setup_steps_screen); - final TextView stepsTitle = (TextView)findViewById(R.id.setup_title); - stepsTitle.setText(getString(R.string.setup_steps_title, applicationName)); - - final SetupStepIndicatorView indicatorView = - (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator); - mSetupStepGroup = new SetupStepGroup(indicatorView); - - mStep1Bullet = (TextView)findViewById(R.id.setup_step1_bullet); - mStep1Bullet.setOnClickListener(this); - final SetupStep step1 = new SetupStep(STEP_1, applicationName, - mStep1Bullet, findViewById(R.id.setup_step1), - R.string.setup_step1_title, R.string.setup_step1_instruction, - R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1, - R.string.setup_step1_action); - step1.setAction(new Runnable() { - @Override - public void run() { - invokeLanguageAndInputSettings(); - mHandler.startPollingImeSettings(); - } - }); - mSetupStepGroup.addStep(step1); - - final SetupStep step2 = new SetupStep(STEP_2, applicationName, - (TextView)findViewById(R.id.setup_step2_bullet), findViewById(R.id.setup_step2), - R.string.setup_step2_title, R.string.setup_step2_instruction, - 0 /* finishedInstruction */, R.drawable.ic_setup_step2, - R.string.setup_step2_action); - step2.setAction(new Runnable() { - @Override - public void run() { - // Invoke input method picker. - RichInputMethodManager.getInstance().getInputMethodManager() - .showInputMethodPicker(); - } - }); - mSetupStepGroup.addStep(step2); - - final SetupStep step3 = new SetupStep(STEP_3, applicationName, - (TextView)findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3), - R.string.setup_step3_title, R.string.setup_step3_instruction, - 0 /* finishedInstruction */, R.drawable.ic_setup_step3, - R.string.setup_step3_action); - step3.setAction(new Runnable() { - @Override - public void run() { - invokeSubtypeEnablerOfThisIme(); - } - }); - mSetupStepGroup.addStep(step3); - - mWelcomeVideoUri = new Uri.Builder() - .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(getPackageName()) - .path(Integer.toString(R.raw.setup_welcome_video)) - .build(); - mWelcomeVideoView = (VideoView)findViewById(R.id.setup_welcome_video); - mWelcomeVideoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(final MediaPlayer mp) { - mp.start(); - } - }); - mWelcomeVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { - @Override - public void onPrepared(final MediaPlayer mp) { - // Now VideoView has been laid-out and ready to play, remove background of it to - // reveal the video. - mWelcomeVideoView.setBackgroundResource(0); - } - }); - final ImageView welcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image); - mWelcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { - @Override - public boolean onError(final MediaPlayer mp, final int what, final int extra) { - Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra); - mWelcomeVideoView.setVisibility(View.GONE); - welcomeImageView.setImageResource(R.raw.setup_welcome_image); - welcomeImageView.setVisibility(View.VISIBLE); - return true; - } - }); - - mActionStart = findViewById(R.id.setup_start_label); - mActionStart.setOnClickListener(this); - mActionNext = findViewById(R.id.setup_next); - mActionNext.setOnClickListener(this); - mActionFinish = (TextView)findViewById(R.id.setup_finish); - TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mActionFinish, - getResources().getDrawable(R.drawable.ic_setup_finish), null, null, null); - mActionFinish.setOnClickListener(this); - } - - @Override - public void onClick(final View v) { - if (v == mActionFinish) { - finish(); - return; - } - final int currentStep = determineSetupStepNumber(); - final int nextStep; - if (v == mActionStart) { - nextStep = STEP_1; - } else if (v == mActionNext) { - nextStep = mStepNumber + 1; - } else if (v == mStep1Bullet && currentStep == STEP_2) { - nextStep = STEP_1; - } else { - nextStep = mStepNumber; - } - if (mStepNumber != nextStep) { - mStepNumber = nextStep; - updateSetupStepView(); - } - } - - private void invokeSetupWizardOfThisIme() { - final Intent intent = new Intent(); - intent.setClass(this, SetupActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - } - - private void invokeSettingsOfThisIme() { - final Intent intent = new Intent(); - intent.setClass(this, SettingsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - } - - private void invokeLanguageAndInputSettings() { final Intent intent = new Intent(); - intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS); - intent.addCategory(Intent.CATEGORY_DEFAULT); - startActivity(intent); - mWasLanguageAndInputSettingsInvoked = true; - } - - private void invokeSubtypeEnablerOfThisIme() { - final InputMethodInfo imi = - RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme(); - final Intent intent = new Intent(); - intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); - intent.addCategory(Intent.CATEGORY_DEFAULT); - intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId()); + intent.setClass(this, SetupWizardActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); + if (!isFinishing()) { + finish(); + } } /** @@ -317,164 +74,4 @@ public final class SetupActivity extends Activity implements View.OnClickListene context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); return myImi.getId().equals(currentImeId); } - - private int determineSetupStepNumber() { - mHandler.cancelPollingImeSettings(); - if (!isThisImeEnabled(this)) { - return STEP_1; - } - if (!isThisImeCurrent(this)) { - return STEP_2; - } - return STEP_3; - } - - @Override - protected void onSaveInstanceState(final Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(STATE_STEP, mStepNumber); - } - - @Override - protected void onRestoreInstanceState(final Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - mStepNumber = savedInstanceState.getInt(STATE_STEP); - } - - @Override - protected void onRestart() { - super.onRestart(); - if (mStepNumber != STEP_WELCOME) { - mStepNumber = determineSetupStepNumber(); - } - } - - @Override - protected void onResume() { - super.onResume(); - updateSetupStepView(); - } - - @Override - public void onBackPressed() { - if (mStepNumber == STEP_1) { - mStepNumber = STEP_WELCOME; - updateSetupStepView(); - return; - } - super.onBackPressed(); - } - - @Override - protected void onPause() { - mWelcomeVideoView.stopPlayback(); - super.onPause(); - } - - @Override - public void onWindowFocusChanged(final boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus && mStepNumber != STEP_WELCOME) { - mStepNumber = determineSetupStepNumber(); - updateSetupStepView(); - } - } - - private void updateSetupStepView() { - final boolean welcomeScreen = (mStepNumber == STEP_WELCOME); - mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE); - mSetupScreen.setVisibility(welcomeScreen ? View.GONE: View.VISIBLE); - if (welcomeScreen) { - mWelcomeVideoView.setVideoURI(mWelcomeVideoUri); - mWelcomeVideoView.start(); - return; - } - mWelcomeVideoView.stopPlayback(); - final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber(); - mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone); - mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE); - mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE); - } - - static final class SetupStep implements View.OnClickListener { - public final int mStepNo; - private final View mStepView; - private final TextView mBulletView; - private final int mActivatedColor; - private final int mDeactivatedColor; - private final String mInstruction; - private final String mFinishedInstruction; - private final TextView mActionLabel; - private Runnable mAction; - - public SetupStep(final int stepNo, final String applicationName, final TextView bulletView, - final View stepView, final int title, final int instruction, - final int finishedInstruction,final int actionIcon, final int actionLabel) { - mStepNo = stepNo; - mStepView = stepView; - mBulletView = bulletView; - final Resources res = stepView.getResources(); - mActivatedColor = res.getColor(R.color.setup_text_action); - mDeactivatedColor = res.getColor(R.color.setup_text_dark); - - final TextView titleView = (TextView)mStepView.findViewById(R.id.setup_step_title); - titleView.setText(res.getString(title, applicationName)); - mInstruction = (instruction == 0) ? null - : res.getString(instruction, applicationName); - mFinishedInstruction = (finishedInstruction == 0) ? null - : res.getString(finishedInstruction, applicationName); - - mActionLabel = (TextView)mStepView.findViewById(R.id.setup_step_action_label); - mActionLabel.setText(res.getString(actionLabel)); - if (actionIcon == 0) { - final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel); - ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0); - } else { - TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds( - mActionLabel, res.getDrawable(actionIcon), null, null, null); - } - } - - public void setEnabled(final boolean enabled, final boolean isStepActionAlreadyDone) { - mStepView.setVisibility(enabled ? View.VISIBLE : View.GONE); - mBulletView.setTextColor(enabled ? mActivatedColor : mDeactivatedColor); - final TextView instructionView = (TextView)mStepView.findViewById( - R.id.setup_step_instruction); - instructionView.setText(isStepActionAlreadyDone ? mFinishedInstruction : mInstruction); - mActionLabel.setVisibility(isStepActionAlreadyDone ? View.GONE : View.VISIBLE); - } - - public void setAction(final Runnable action) { - mActionLabel.setOnClickListener(this); - mAction = action; - } - - @Override - public void onClick(final View v) { - if (v == mActionLabel && mAction != null) { - mAction.run(); - return; - } - } - } - - static final class SetupStepGroup { - private final SetupStepIndicatorView mIndicatorView; - private final ArrayList<SetupStep> mGroup = CollectionUtils.newArrayList(); - - public SetupStepGroup(final SetupStepIndicatorView indicatorView) { - mIndicatorView = indicatorView; - } - - public void addStep(final SetupStep step) { - mGroup.add(step); - } - - public void enableStep(final int enableStepNo, final boolean isStepActionAlreadyDone) { - for (final SetupStep step : mGroup) { - step.setEnabled(step.mStepNo == enableStepNo, isStepActionAlreadyDone); - } - mIndicatorView.setIndicatorPosition(enableStepNo - STEP_1, mGroup.size()); - } - } } diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java new file mode 100644 index 000000000..3406ecf34 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java @@ -0,0 +1,466 @@ +/* + * 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.latin.setup; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.res.Resources; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Bundle; +import android.os.Message; +import android.provider.Settings; +import android.util.Log; +import android.view.View; +import android.view.inputmethod.InputMethodInfo; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.VideoView; + +import com.android.inputmethod.compat.TextViewCompatUtils; +import com.android.inputmethod.compat.ViewCompatUtils; +import com.android.inputmethod.latin.CollectionUtils; +import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.RichInputMethodManager; +import com.android.inputmethod.latin.SettingsActivity; +import com.android.inputmethod.latin.StaticInnerHandlerWrapper; + +import java.util.ArrayList; + +// TODO: Use Fragment to implement welcome screen and setup steps. +public final class SetupWizardActivity extends Activity implements View.OnClickListener { + static final String TAG = SetupWizardActivity.class.getSimpleName(); + + private View mSetupWizard; + private View mWelcomeScreen; + private View mSetupScreen; + private Uri mWelcomeVideoUri; + private VideoView mWelcomeVideoView; + private View mActionStart; + private View mActionNext; + private TextView mStep1Bullet; + private TextView mActionFinish; + private SetupStepGroup mSetupStepGroup; + private static final String STATE_STEP = "step"; + private int mStepNumber; + private static final int STEP_WELCOME = 0; + private static final int STEP_1 = 1; + private static final int STEP_2 = 2; + private static final int STEP_3 = 3; + private static final int STEP_LAUNCHING_IME_SETTINGS = 4; + private static final int STEP_BACK_FROM_IME_SETTINGS = 5; + + final SettingsPoolingHandler mHandler = new SettingsPoolingHandler(this); + + static final class SettingsPoolingHandler + extends StaticInnerHandlerWrapper<SetupWizardActivity> { + private static final int MSG_POLLING_IME_SETTINGS = 0; + private static final long IME_SETTINGS_POLLING_INTERVAL = 200; + + public SettingsPoolingHandler(final SetupWizardActivity outerInstance) { + super(outerInstance); + } + + @Override + public void handleMessage(final Message msg) { + final SetupWizardActivity setupWizardActivity = getOuterInstance(); + if (setupWizardActivity == null) { + return; + } + switch (msg.what) { + case MSG_POLLING_IME_SETTINGS: + if (SetupActivity.isThisImeEnabled(setupWizardActivity)) { + setupWizardActivity.invokeSetupWizardOfThisIme(); + return; + } + startPollingImeSettings(); + break; + } + } + + public void startPollingImeSettings() { + sendMessageDelayed(obtainMessage(MSG_POLLING_IME_SETTINGS), + IME_SETTINGS_POLLING_INTERVAL); + } + + public void cancelPollingImeSettings() { + removeMessages(MSG_POLLING_IME_SETTINGS); + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + setTheme(android.R.style.Theme_Translucent_NoTitleBar); + super.onCreate(savedInstanceState); + + setContentView(R.layout.setup_wizard); + mSetupWizard = findViewById(R.id.setup_wizard); + + RichInputMethodManager.init(this); + + if (savedInstanceState == null) { + mStepNumber = determineSetupStepNumberFromLauncher(); + } else { + mStepNumber = savedInstanceState.getInt(STATE_STEP); + } + + final String applicationName = getResources().getString(getApplicationInfo().labelRes); + mWelcomeScreen = findViewById(R.id.setup_welcome_screen); + final TextView welcomeTitle = (TextView)findViewById(R.id.setup_welcome_title); + welcomeTitle.setText(getString(R.string.setup_welcome_title, applicationName)); + + mSetupScreen = findViewById(R.id.setup_steps_screen); + final TextView stepsTitle = (TextView)findViewById(R.id.setup_title); + stepsTitle.setText(getString(R.string.setup_steps_title, applicationName)); + + final SetupStepIndicatorView indicatorView = + (SetupStepIndicatorView)findViewById(R.id.setup_step_indicator); + mSetupStepGroup = new SetupStepGroup(indicatorView); + + mStep1Bullet = (TextView)findViewById(R.id.setup_step1_bullet); + mStep1Bullet.setOnClickListener(this); + final SetupStep step1 = new SetupStep(STEP_1, applicationName, + mStep1Bullet, findViewById(R.id.setup_step1), + R.string.setup_step1_title, R.string.setup_step1_instruction, + R.string.setup_step1_finished_instruction, R.drawable.ic_setup_step1, + R.string.setup_step1_action); + step1.setAction(new Runnable() { + @Override + public void run() { + invokeLanguageAndInputSettings(); + mHandler.startPollingImeSettings(); + } + }); + mSetupStepGroup.addStep(step1); + + final SetupStep step2 = new SetupStep(STEP_2, applicationName, + (TextView)findViewById(R.id.setup_step2_bullet), findViewById(R.id.setup_step2), + R.string.setup_step2_title, R.string.setup_step2_instruction, + 0 /* finishedInstruction */, R.drawable.ic_setup_step2, + R.string.setup_step2_action); + step2.setAction(new Runnable() { + @Override + public void run() { + // Invoke input method picker. + RichInputMethodManager.getInstance().getInputMethodManager() + .showInputMethodPicker(); + } + }); + mSetupStepGroup.addStep(step2); + + final SetupStep step3 = new SetupStep(STEP_3, applicationName, + (TextView)findViewById(R.id.setup_step3_bullet), findViewById(R.id.setup_step3), + R.string.setup_step3_title, R.string.setup_step3_instruction, + 0 /* finishedInstruction */, R.drawable.ic_setup_step3, + R.string.setup_step3_action); + step3.setAction(new Runnable() { + @Override + public void run() { + invokeSubtypeEnablerOfThisIme(); + } + }); + mSetupStepGroup.addStep(step3); + + mWelcomeVideoUri = new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(getPackageName()) + .path(Integer.toString(R.raw.setup_welcome_video)) + .build(); + final VideoView welcomeVideoView = (VideoView)findViewById(R.id.setup_welcome_video); + welcomeVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { + @Override + public void onPrepared(final MediaPlayer mp) { + // Now VideoView has been laid-out and ready to play, remove background of it to + // reveal the video. + welcomeVideoView.setBackgroundResource(0); + mp.setLooping(true); + } + }); + final ImageView welcomeImageView = (ImageView)findViewById(R.id.setup_welcome_image); + welcomeVideoView.setOnErrorListener(new MediaPlayer.OnErrorListener() { + @Override + public boolean onError(final MediaPlayer mp, final int what, final int extra) { + Log.e(TAG, "Playing welcome video causes error: what=" + what + " extra=" + extra); + welcomeVideoView.setVisibility(View.GONE); + welcomeImageView.setImageResource(R.raw.setup_welcome_image); + welcomeImageView.setVisibility(View.VISIBLE); + return true; + } + }); + mWelcomeVideoView = welcomeVideoView; + + mActionStart = findViewById(R.id.setup_start_label); + mActionStart.setOnClickListener(this); + mActionNext = findViewById(R.id.setup_next); + mActionNext.setOnClickListener(this); + mActionFinish = (TextView)findViewById(R.id.setup_finish); + TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds(mActionFinish, + getResources().getDrawable(R.drawable.ic_setup_finish), null, null, null); + mActionFinish.setOnClickListener(this); + } + + @Override + public void onClick(final View v) { + if (v == mActionFinish) { + finish(); + return; + } + final int currentStep = determineSetupStepNumber(); + final int nextStep; + if (v == mActionStart) { + nextStep = STEP_1; + } else if (v == mActionNext) { + nextStep = mStepNumber + 1; + } else if (v == mStep1Bullet && currentStep == STEP_2) { + nextStep = STEP_1; + } else { + nextStep = mStepNumber; + } + if (mStepNumber != nextStep) { + mStepNumber = nextStep; + updateSetupStepView(); + } + } + + void invokeSetupWizardOfThisIme() { + final Intent intent = new Intent(); + intent.setClass(this, SetupWizardActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } + + private void invokeSettingsOfThisIme() { + final Intent intent = new Intent(); + intent.setClass(this, SettingsActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } + + void invokeLanguageAndInputSettings() { + final Intent intent = new Intent(); + intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + startActivity(intent); + } + + void invokeSubtypeEnablerOfThisIme() { + final InputMethodInfo imi = + RichInputMethodManager.getInstance().getInputMethodInfoOfThisIme(); + final Intent intent = new Intent(); + intent.setAction(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imi.getId()); + startActivity(intent); + } + + private int determineSetupStepNumberFromLauncher() { + final int stepNumber = determineSetupStepNumber(); + if (stepNumber == STEP_1) { + return STEP_WELCOME; + } + if (stepNumber == STEP_3) { + return STEP_LAUNCHING_IME_SETTINGS; + } + return stepNumber; + } + + private int determineSetupStepNumber() { + mHandler.cancelPollingImeSettings(); + if (!SetupActivity.isThisImeEnabled(this)) { + return STEP_1; + } + if (!SetupActivity.isThisImeCurrent(this)) { + return STEP_2; + } + return STEP_3; + } + + @Override + protected void onSaveInstanceState(final Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(STATE_STEP, mStepNumber); + } + + @Override + protected void onRestoreInstanceState(final Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + mStepNumber = savedInstanceState.getInt(STATE_STEP); + } + + private static boolean isInSetupSteps(final int stepNumber) { + return stepNumber >= STEP_1 && stepNumber <= STEP_3; + } + + @Override + protected void onRestart() { + super.onRestart(); + if (isInSetupSteps(mStepNumber)) { + mStepNumber = determineSetupStepNumber(); + } + } + + @Override + protected void onResume() { + super.onResume(); + if (mStepNumber == STEP_LAUNCHING_IME_SETTINGS) { + // Prevent white screen flashing while launching settings activity. + mSetupWizard.setVisibility(View.INVISIBLE); + invokeSettingsOfThisIme(); + mStepNumber = STEP_BACK_FROM_IME_SETTINGS; + return; + } + if (mStepNumber == STEP_BACK_FROM_IME_SETTINGS) { + finish(); + return; + } + updateSetupStepView(); + } + + @Override + public void onBackPressed() { + if (mStepNumber == STEP_1) { + mStepNumber = STEP_WELCOME; + updateSetupStepView(); + return; + } + super.onBackPressed(); + } + + private static void hideAndStopVideo(final VideoView videoView) { + videoView.stopPlayback(); + videoView.setVisibility(View.INVISIBLE); + } + + @Override + protected void onPause() { + hideAndStopVideo(mWelcomeVideoView); + super.onPause(); + } + + @Override + public void onWindowFocusChanged(final boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus && isInSetupSteps(mStepNumber)) { + mStepNumber = determineSetupStepNumber(); + updateSetupStepView(); + } + } + + private void updateSetupStepView() { + mSetupWizard.setVisibility(View.VISIBLE); + final boolean welcomeScreen = (mStepNumber == STEP_WELCOME); + mWelcomeScreen.setVisibility(welcomeScreen ? View.VISIBLE : View.GONE); + mSetupScreen.setVisibility(welcomeScreen ? View.GONE : View.VISIBLE); + if (welcomeScreen) { + mWelcomeVideoView.setVisibility(View.VISIBLE); + mWelcomeVideoView.setVideoURI(mWelcomeVideoUri); + mWelcomeVideoView.start(); + return; + } + hideAndStopVideo(mWelcomeVideoView); + final boolean isStepActionAlreadyDone = mStepNumber < determineSetupStepNumber(); + mSetupStepGroup.enableStep(mStepNumber, isStepActionAlreadyDone); + mActionNext.setVisibility(isStepActionAlreadyDone ? View.VISIBLE : View.GONE); + mActionFinish.setVisibility((mStepNumber == STEP_3) ? View.VISIBLE : View.GONE); + } + + static final class SetupStep implements View.OnClickListener { + public final int mStepNo; + private final View mStepView; + private final TextView mBulletView; + private final int mActivatedColor; + private final int mDeactivatedColor; + private final String mInstruction; + private final String mFinishedInstruction; + private final TextView mActionLabel; + private Runnable mAction; + + public SetupStep(final int stepNo, final String applicationName, final TextView bulletView, + final View stepView, final int title, final int instruction, + final int finishedInstruction, final int actionIcon, final int actionLabel) { + mStepNo = stepNo; + mStepView = stepView; + mBulletView = bulletView; + final Resources res = stepView.getResources(); + mActivatedColor = res.getColor(R.color.setup_text_action); + mDeactivatedColor = res.getColor(R.color.setup_text_dark); + + final TextView titleView = (TextView)mStepView.findViewById(R.id.setup_step_title); + titleView.setText(res.getString(title, applicationName)); + mInstruction = (instruction == 0) ? null + : res.getString(instruction, applicationName); + mFinishedInstruction = (finishedInstruction == 0) ? null + : res.getString(finishedInstruction, applicationName); + + mActionLabel = (TextView)mStepView.findViewById(R.id.setup_step_action_label); + mActionLabel.setText(res.getString(actionLabel)); + if (actionIcon == 0) { + final int paddingEnd = ViewCompatUtils.getPaddingEnd(mActionLabel); + ViewCompatUtils.setPaddingRelative(mActionLabel, paddingEnd, 0, paddingEnd, 0); + } else { + TextViewCompatUtils.setCompoundDrawablesRelativeWithIntrinsicBounds( + mActionLabel, res.getDrawable(actionIcon), null, null, null); + } + } + + public void setEnabled(final boolean enabled, final boolean isStepActionAlreadyDone) { + mStepView.setVisibility(enabled ? View.VISIBLE : View.GONE); + mBulletView.setTextColor(enabled ? mActivatedColor : mDeactivatedColor); + final TextView instructionView = (TextView)mStepView.findViewById( + R.id.setup_step_instruction); + instructionView.setText(isStepActionAlreadyDone ? mFinishedInstruction : mInstruction); + mActionLabel.setVisibility(isStepActionAlreadyDone ? View.GONE : View.VISIBLE); + } + + public void setAction(final Runnable action) { + mActionLabel.setOnClickListener(this); + mAction = action; + } + + @Override + public void onClick(final View v) { + if (v == mActionLabel && mAction != null) { + mAction.run(); + return; + } + } + } + + static final class SetupStepGroup { + private final SetupStepIndicatorView mIndicatorView; + private final ArrayList<SetupStep> mGroup = CollectionUtils.newArrayList(); + + public SetupStepGroup(final SetupStepIndicatorView indicatorView) { + mIndicatorView = indicatorView; + } + + public void addStep(final SetupStep step) { + mGroup.add(step); + } + + public void enableStep(final int enableStepNo, final boolean isStepActionAlreadyDone) { + for (final SetupStep step : mGroup) { + step.setEnabled(step.mStepNo == enableStepNo, isStepActionAlreadyDone); + } + mIndicatorView.setIndicatorPosition(enableStepNo - STEP_1, mGroup.size()); + } + } +} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index aa60496ae..13fcaf48a 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -23,7 +23,7 @@ import android.service.textservice.SpellCheckerService; import android.util.Log; import android.view.textservice.SuggestionsInfo; -import com.android.inputmethod.keyboard.ProximityInfo; +import com.android.inputmethod.keyboard.KeyboardLayoutSet; import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.CollectionUtils; import com.android.inputmethod.latin.ContactsBinaryDictionary; @@ -126,6 +126,19 @@ public final class AndroidSpellCheckerService extends SpellCheckerService return script; } + private static String getKeyboardLayoutNameForScript(final int script) { + switch (script) { + case AndroidSpellCheckerService.SCRIPT_LATIN: + return "qwerty"; + case AndroidSpellCheckerService.SCRIPT_CYRILLIC: + return "east_slavic"; + case AndroidSpellCheckerService.SCRIPT_GREEK: + return "greek"; + default: + throw new RuntimeException("Wrong script supplied: " + script); + } + } + @Override public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { if (!PREF_USE_CONTACTS_KEY.equals(key)) return; @@ -385,9 +398,13 @@ public final class AndroidSpellCheckerService extends SpellCheckerService return pool; } - public DictAndProximity createDictAndProximity(final Locale locale) { + public DictAndKeyboard createDictAndKeyboard(final Locale locale) { final int script = getScriptFromLocale(locale); - final ProximityInfo proximityInfo = new SpellCheckerProximityInfo(script); + final String keyboardLayoutName = getKeyboardLayoutNameForScript(script); + final KeyboardLayoutSet keyboardLayoutSet = + KeyboardLayoutSet.createKeyboardSetForSpellChecker(this, locale.toString(), + keyboardLayoutName); + final DictionaryCollection dictionaryCollection = DictionaryFactory.createMainDictionaryFromManager(this, locale, true /* useFullEditDistance */); @@ -412,6 +429,6 @@ public final class AndroidSpellCheckerService extends SpellCheckerService mDictionaryCollectionsList.add( new WeakReference<DictionaryCollection>(dictionaryCollection)); } - return new DictAndProximity(dictionaryCollection, proximityInfo); + return new DictAndKeyboard(dictionaryCollection, keyboardLayoutSet); } } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index 61850e42e..16e9fb77e 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -257,7 +257,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { } if (shouldFilterOut(inText, mScript)) { - DictAndProximity dictInfo = null; + DictAndKeyboard dictInfo = null; try { dictInfo = mDictionaryPool.pollWithDefaultTimeout(); if (!DictionaryPool.isAValidDictionary(dictInfo)) { @@ -286,7 +286,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { final int capitalizeType = StringUtils.getCapitalizationType(text); boolean isInDict = true; - DictAndProximity dictInfo = null; + DictAndKeyboard dictInfo = null; try { dictInfo = mDictionaryPool.pollWithDefaultTimeout(); if (!DictionaryPool.isAValidDictionary(dictInfo)) { @@ -296,20 +296,13 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { final int length = text.length(); for (int i = 0; i < length; i = text.offsetByCodePoints(i, 1)) { final int codePoint = text.codePointAt(i); - // The getXYForCodePointAndScript method returns (Y << 16) + X - final int xy = SpellCheckerProximityInfo.getXYForCodePointAndScript( - codePoint, mScript); - if (SpellCheckerProximityInfo.NOT_A_COORDINATE_PAIR == xy) { - composer.add(codePoint, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } else { - composer.add(codePoint, xy & 0xFFFF, xy >> 16); - } + composer.addKeyInfo(codePoint, dictInfo.getKeyboard(codePoint)); } // TODO: make a spell checker option to block offensive words or not final ArrayList<SuggestedWordInfo> suggestions = dictInfo.mDictionary.getSuggestions(composer, prevWord, - dictInfo.mProximityInfo, true /* blockOffensiveWords */); + dictInfo.getProximityInfo(), + true /* blockOffensiveWords */); for (final SuggestedWordInfo suggestion : suggestions) { final String suggestionStr = suggestion.mWord; suggestionsGatherer.addWord(suggestionStr.toCharArray(), null, 0, diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java new file mode 100644 index 000000000..b77f3e2c5 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 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.spellcheck; + +import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardId; +import com.android.inputmethod.keyboard.KeyboardLayoutSet; +import com.android.inputmethod.keyboard.ProximityInfo; + +/** + * A container for a Dictionary and a Keyboard. + */ +public final class DictAndKeyboard { + public final Dictionary mDictionary; + private final Keyboard mKeyboard; + private final Keyboard mManualShiftedKeyboard; + + public DictAndKeyboard( + final Dictionary dictionary, final KeyboardLayoutSet keyboardLayoutSet) { + mDictionary = dictionary; + if (keyboardLayoutSet == null) { + mKeyboard = null; + mManualShiftedKeyboard = null; + return; + } + mKeyboard = keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); + mManualShiftedKeyboard = + keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED); + } + + public Keyboard getKeyboard(final int codePoint) { + if (mKeyboard == null) { + return null; + } + return mKeyboard.getKey(codePoint) != null ? mKeyboard : mManualShiftedKeyboard; + } + + public ProximityInfo getProximityInfo() { + return mKeyboard == null ? null : mKeyboard.getProximityInfo(); + } +} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java deleted file mode 100644 index 017a4f555..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndProximity.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2011 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.spellcheck; - -import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.keyboard.ProximityInfo; - -/** - * A simple container for both a Dictionary and a ProximityInfo. - */ -public final class DictAndProximity { - public final Dictionary mDictionary; - public final ProximityInfo mProximityInfo; - public DictAndProximity(final Dictionary dictionary, final ProximityInfo proximityInfo) { - mDictionary = dictionary; - mProximityInfo = proximityInfo; - } -} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java index 27964b3c6..a20e09ee8 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java @@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit; * the client code, but may help with sloppy clients. */ @SuppressWarnings("serial") -public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { +public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> { private final static String TAG = DictionaryPool.class.getSimpleName(); // How many seconds we wait for a dictionary to become available. Past this delay, we give up in // fear some bug caused a deadlock, and reset the whole pool. @@ -47,7 +47,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> private int mSize; private volatile boolean mClosed; final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList(); - private final static DictAndProximity dummyDict = new DictAndProximity( + private final static DictAndKeyboard dummyDict = new DictAndKeyboard( new Dictionary(Dictionary.TYPE_MAIN) { @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, @@ -64,7 +64,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> } }, null); - static public boolean isAValidDictionary(final DictAndProximity dictInfo) { + static public boolean isAValidDictionary(final DictAndKeyboard dictInfo) { return null != dictInfo && dummyDict != dictInfo; } @@ -79,32 +79,32 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> } @Override - public DictAndProximity poll(final long timeout, final TimeUnit unit) + public DictAndKeyboard poll(final long timeout, final TimeUnit unit) throws InterruptedException { - final DictAndProximity dict = poll(); + final DictAndKeyboard dict = poll(); if (null != dict) return dict; synchronized(this) { if (mSize >= mMaxSize) { // Our pool is already full. Wait until some dictionary is ready, or TIMEOUT // expires to avoid a deadlock. - final DictAndProximity result = super.poll(timeout, unit); + final DictAndKeyboard result = super.poll(timeout, unit); if (null == result) { Log.e(TAG, "Deadlock detected ! Resetting dictionary pool"); clear(); mSize = 1; - return mService.createDictAndProximity(mLocale); + return mService.createDictAndKeyboard(mLocale); } else { return result; } } else { ++mSize; - return mService.createDictAndProximity(mLocale); + return mService.createDictAndKeyboard(mLocale); } } } // Convenience method - public DictAndProximity pollWithDefaultTimeout() { + public DictAndKeyboard pollWithDefaultTimeout() { try { return poll(TIMEOUT, TimeUnit.SECONDS); } catch (InterruptedException e) { @@ -115,7 +115,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> public void close() { synchronized(this) { mClosed = true; - for (DictAndProximity dict : this) { + for (DictAndKeyboard dict : this) { dict.mDictionary.close(); } clear(); @@ -123,7 +123,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> } @Override - public boolean offer(final DictAndProximity dict) { + public boolean offer(final DictAndKeyboard dict) { if (mClosed) { dict.mDictionary.close(); return super.offer(dummyDict); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java b/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java deleted file mode 100644 index 0c480eaba..000000000 --- a/java/src/com/android/inputmethod/latin/spellcheck/SpellCheckerProximityInfo.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright (C) 2011 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.spellcheck; - -import android.util.SparseIntArray; - -import com.android.inputmethod.keyboard.ProximityInfo; -import com.android.inputmethod.latin.Constants; - -public final class SpellCheckerProximityInfo extends ProximityInfo { - public SpellCheckerProximityInfo(final int script) { - super(getProximityForScript(script), PROXIMITY_GRID_WIDTH, PROXIMITY_GRID_HEIGHT); - } - - private static final int NUL = Constants.NOT_A_CODE; - - // This must be the same as MAX_PROXIMITY_CHARS_SIZE else it will not work inside - // native code - this value is passed at creation of the binary object and reused - // as the size of the passed array afterwards so they can't be different. - private static final int ROW_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; - - // The number of keys in a row of the grid used by the spell checker. - private static final int PROXIMITY_GRID_WIDTH = 11; - // The number of rows in the grid used by the spell checker. - private static final int PROXIMITY_GRID_HEIGHT = 3; - - private static final int NOT_AN_INDEX = -1; - public static final int NOT_A_COORDINATE_PAIR = -1; - - // Helper methods - static void buildProximityIndices(final int[] proximity, final int rowSize, - final SparseIntArray indices) { - for (int i = 0; i < proximity.length; i += rowSize) { - if (NUL != proximity[i]) indices.put(proximity[i], i / rowSize); - } - } - - private static final class Latin { - // The proximity here is the union of - // - the proximity for a QWERTY keyboard. - // - the proximity for an AZERTY keyboard. - // - the proximity for a QWERTZ keyboard. - // ...plus, add all characters in the ('a', 'e', 'i', 'o', 'u') set to each other. - // - // The reasoning behind this construction is, almost any alphabetic text we may want - // to spell check has been entered with one of the keyboards above. Also, specifically - // to English, many spelling errors consist of the last vowel of the word being wrong - // because in English vowels tend to merge with each other in pronunciation. - /* - The Qwerty layout this represents looks like the following: - q w e r t y u i o p - a s d f g h j k l - z x c v b n m - */ - static final int[] PROXIMITY = { - // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter, - // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's. - // The number of rows must be exactly PROXIMITY_GRID_HEIGHT. - 'q', 'w', 's', 'a', 'z', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'w', 'q', 'a', 's', 'd', 'e', 'x', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'e', 'w', 's', 'd', 'f', 'r', 'a', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, - 'r', 'e', 'd', 'f', 'g', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 't', 'r', 'f', 'g', 'h', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'y', 't', 'g', 'h', 'j', 'u', 'a', 's', 'd', 'x', NUL, NUL, NUL, NUL, NUL, NUL, - 'u', 'y', 'h', 'j', 'k', 'i', 'a', 'e', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'i', 'u', 'j', 'k', 'l', 'o', 'a', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'o', 'i', 'k', 'l', 'p', 'a', 'e', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'p', 'o', 'l', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - // Proximity for row 2. See comment above about size. - 'a', 'z', 'x', 's', 'w', 'q', 'e', 'i', 'o', 'u', NUL, NUL, NUL, NUL, NUL, NUL, - 's', 'q', 'a', 'z', 'x', 'c', 'd', 'e', 'w', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'd', 'w', 's', 'x', 'c', 'v', 'f', 'r', 'e', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'f', 'e', 'd', 'c', 'v', 'b', 'g', 't', 'r', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'g', 'r', 'f', 'v', 'b', 'n', 'h', 'y', 't', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'h', 't', 'g', 'b', 'n', 'm', 'j', 'u', 'y', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'j', 'y', 'h', 'n', 'm', 'k', 'i', 'u', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'k', 'u', 'j', 'm', 'l', 'o', 'i', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'l', 'i', 'k', 'p', 'o', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - // Proximity for row 3. See comment above about size. - 'z', 'a', 's', 'd', 'x', 't', 'g', 'h', 'j', 'u', 'q', 'e', NUL, NUL, NUL, NUL, - 'x', 'z', 'a', 's', 'd', 'c', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'c', 'x', 's', 'd', 'f', 'v', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'v', 'c', 'd', 'f', 'g', 'b', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'b', 'v', 'f', 'g', 'h', 'n', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'n', 'b', 'g', 'h', 'j', 'm', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'm', 'n', 'h', 'j', 'k', 'l', 'o', 'p', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - }; - - // This is a mapping array from the code point to the index in the PROXIMITY array. - // When we check the spelling of a word, we need to pass (x,y) coordinates to the native - // code for each letter of the word. These are most easily computed from the index in the - // PROXIMITY array. Since we'll need to do that very often, the index lookup from the code - // point needs to be as fast as possible, and a map is probably the best way to do this. - // To avoid unnecessary boxing conversion to Integer, here we use SparseIntArray. - static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE); - - static { - buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES); - } - } - - private static final class Cyrillic { - // TODO: The following table is solely based on the keyboard layout. Consult with Russian - // speakers on commonly misspelled words/letters. - /* - The Russian layout this represents looks like the following: - й ц у к е н г ш щ з х - ф ы в а п р о л д ж э - я ч с м и т ь б ю - - This gives us the following table: - 'й', 'ц', 'ф', 'ы', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ц', 'й', 'ф', 'ы', 'в', 'у', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'у', 'ц', 'ы', 'в', 'а', 'к', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'к', 'у', 'в', 'а', 'п', 'е', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'е', 'к', 'а', 'п', 'р', 'н', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'н', 'е', 'п', 'р', 'о', 'г', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'г', 'н', 'р', 'о', 'л', 'ш', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ш', 'г', 'о', 'л', 'д', 'щ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'щ', 'ш', 'л', 'д', 'ж', 'з', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'з', 'щ', 'д', 'ж', 'э', 'х', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'х', 'з', 'ж', 'э', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - 'ф', 'й', 'ц', 'ы', 'я', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ы', 'й', 'ц', 'у', 'ф', 'в', 'я', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'в', 'ц', 'у', 'к', 'ы', 'а', 'я', 'ч', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'а', 'у', 'к', 'е', 'в', 'п', 'ч', 'с', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'п', 'к', 'е', 'н', 'а', 'р', 'с', 'м', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'р', 'е', 'н', 'г', 'п', 'о', 'м', 'и', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'о', 'н', 'г', 'ш', 'р', 'л', 'и', 'т', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'л', 'г', 'ш', 'щ', 'о', 'д', 'т', 'ь', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'д', 'ш', 'щ', 'з', 'л', 'ж', 'ь', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ж', 'щ', 'з', 'х', 'д', 'э', 'б', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'э', 'з', 'х', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - 'я', 'ф', 'ы', 'в', 'ч', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ч', 'ы', 'в', 'а', 'я', 'с', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'с', 'в', 'а', 'п', 'ч', 'м', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'м', 'а', 'п', 'р', 'с', 'и', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'и', 'п', 'р', 'о', 'м', 'т', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'т', 'р', 'о', 'л', 'и', 'ь', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ь', 'о', 'л', 'д', 'т', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'б', 'л', 'д', 'ж', 'ь', 'ю', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ю', 'д', 'ж', 'э', 'б', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - Using the following characters: - */ - private static final int CY_SHORT_I = '\u0439'; // й - private static final int CY_TSE = '\u0446'; // ц - private static final int CY_U = '\u0443'; // у - private static final int CY_KA = '\u043A'; // к - private static final int CY_IE = '\u0435'; // е - private static final int CY_EN = '\u043D'; // н - private static final int CY_GHE = '\u0433'; // г - private static final int CY_SHA = '\u0448'; // ш - private static final int CY_SHCHA = '\u0449'; // щ - private static final int CY_ZE = '\u0437'; // з - private static final int CY_HA = '\u0445'; // х - private static final int CY_EF = '\u0444'; // ф - private static final int CY_YERU = '\u044B'; // ы - private static final int CY_VE = '\u0432'; // в - private static final int CY_A = '\u0430'; // а - private static final int CY_PE = '\u043F'; // п - private static final int CY_ER = '\u0440'; // р - private static final int CY_O = '\u043E'; // о - private static final int CY_EL = '\u043B'; // л - private static final int CY_DE = '\u0434'; // д - private static final int CY_ZHE = '\u0436'; // ж - private static final int CY_E = '\u044D'; // э - private static final int CY_YA = '\u044F'; // я - private static final int CY_CHE = '\u0447'; // ч - private static final int CY_ES = '\u0441'; // с - private static final int CY_EM = '\u043C'; // м - private static final int CY_I = '\u0438'; // и - private static final int CY_TE = '\u0442'; // т - private static final int CY_SOFT_SIGN = '\u044C'; // ь - private static final int CY_BE = '\u0431'; // б - private static final int CY_YU = '\u044E'; // ю - static final int[] PROXIMITY = { - // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter, - // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's. - // The number of rows must be exactly PROXIMITY_GRID_HEIGHT. - CY_SHORT_I, CY_TSE, CY_EF, CY_YERU, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_TSE, CY_SHORT_I, CY_EF, CY_YERU, CY_VE, CY_U, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_U, CY_TSE, CY_YERU, CY_VE, CY_A, CY_KA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_KA, CY_U, CY_VE, CY_A, CY_PE, CY_IE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_IE, CY_KA, CY_A, CY_PE, CY_ER, CY_EN, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_EN, CY_IE, CY_PE, CY_ER, CY_O, CY_GHE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_GHE, CY_EN, CY_ER, CY_O, CY_EL, CY_SHA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_SHA, CY_GHE, CY_O, CY_EL, CY_DE, CY_SHCHA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_SHCHA, CY_SHA, CY_EL, CY_DE, CY_ZHE, CY_ZE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_ZE, CY_SHCHA, CY_DE, CY_ZHE, CY_E, CY_HA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_HA, CY_ZE, CY_ZHE, CY_E, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - // Proximity for row 2. See comment above about size. - CY_EF, CY_SHORT_I, CY_TSE, CY_YERU, CY_YA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_YERU, CY_SHORT_I, CY_TSE, CY_U, CY_EF, CY_VE, CY_YA, CY_CHE, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_VE, CY_TSE, CY_U, CY_KA, CY_YERU, CY_A, CY_YA, CY_CHE, - CY_ES, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_A, CY_U, CY_KA, CY_IE, CY_VE, CY_PE, CY_CHE, CY_ES, - CY_EM, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_PE, CY_KA, CY_IE, CY_EN, CY_A, CY_ER, CY_ES, CY_EM, - CY_I, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_ER, CY_IE, CY_EN, CY_GHE, CY_PE, CY_O, CY_EM, CY_I, - CY_TE, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_O, CY_EN, CY_GHE, CY_SHA, CY_ER, CY_EL, CY_I, CY_TE, - CY_SOFT_SIGN, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_EL, CY_GHE, CY_SHA, CY_SHCHA, CY_O, CY_DE, CY_TE, CY_SOFT_SIGN, - CY_BE, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_DE, CY_SHA, CY_SHCHA, CY_ZE, CY_EL, CY_ZHE, CY_SOFT_SIGN, CY_BE, - CY_YU, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_ZHE, CY_SHCHA, CY_ZE, CY_HA, CY_DE, CY_E, CY_BE, CY_YU, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_E, CY_ZE, CY_HA, CY_YU, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - // Proximity for row 3. See comment above about size. - CY_YA, CY_EF, CY_YERU, CY_VE, CY_CHE, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_CHE, CY_YERU, CY_VE, CY_A, CY_YA, CY_ES, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_ES, CY_VE, CY_A, CY_PE, CY_CHE, CY_EM, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_EM, CY_A, CY_PE, CY_ER, CY_ES, CY_I, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_I, CY_PE, CY_ER, CY_O, CY_EM, CY_TE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_TE, CY_ER, CY_O, CY_EL, CY_I, CY_SOFT_SIGN, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_SOFT_SIGN, CY_O, CY_EL, CY_DE, CY_TE, CY_BE, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_BE, CY_EL, CY_DE, CY_ZHE, CY_SOFT_SIGN, CY_YU, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - CY_YU, CY_DE, CY_ZHE, CY_E, CY_BE, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - }; - - static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE); - - static { - buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES); - } - } - - private static final class Greek { - // TODO: The following table is solely based on the keyboard layout. Consult with Greek - // speakers on commonly misspelled words/letters. - /* - The Greek layout this represents looks like the following: - ; ς ε ρ τ υ θ ι ο π - α σ δ φ γ η ξ κ λ - ζ χ ψ ω β ν μ - - This gives us the following table: - 'ς', 'ε', 'α', 'σ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ε', 'ς', 'ρ', 'σ', 'δ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ρ', 'ε', 'τ', 'δ', 'φ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'τ', 'ρ', 'υ', 'φ', 'γ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'υ', 'τ', 'θ', 'γ', 'η', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'θ', 'υ', 'ι', 'η', 'ξ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ι', 'θ', 'ο', 'ξ', 'κ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ο', 'ι', 'π', 'κ', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'π', 'ο', 'λ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - 'α', 'ς', 'σ', 'ζ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'σ', 'ς', 'ε', 'α', 'δ', 'ζ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'δ', 'ε', 'ρ', 'σ', 'φ', 'ζ', 'χ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'φ', 'ρ', 'τ', 'δ', 'γ', 'χ', 'ψ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'γ', 'τ', 'υ', 'φ', 'η', 'ψ', 'ω', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'η', 'υ', 'θ', 'γ', 'ξ', 'ω', 'β', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ξ', 'θ', 'ι', 'η', 'κ', 'β', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'κ', 'ι', 'ο', 'ξ', 'λ', 'ν', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'λ', 'ο', 'π', 'κ', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - 'ζ', 'α', 'σ', 'δ', 'χ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'χ', 'σ', 'δ', 'φ', 'ζ', 'ψ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ψ', 'δ', 'φ', 'γ', 'χ', 'ω', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ω', 'φ', 'γ', 'η', 'ψ', 'β', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'β', 'γ', 'η', 'ξ', 'ω', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'ν', 'η', 'ξ', 'κ', 'β', 'μ', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - 'μ', 'ξ', 'κ', 'λ', 'ν', NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - Using the following characters: - */ - private static final int GR_FINAL_SIGMA = '\u03C2'; // ς - private static final int GR_EPSILON = '\u03B5'; // ε - private static final int GR_RHO = '\u03C1'; // ρ - private static final int GR_TAU = '\u03C4'; // τ - private static final int GR_UPSILON = '\u03C5'; // υ - private static final int GR_THETA = '\u03B8'; // θ - private static final int GR_IOTA = '\u03B9'; // ι - private static final int GR_OMICRON = '\u03BF'; // ο - private static final int GR_PI = '\u03C0'; // π - private static final int GR_ALPHA = '\u03B1'; // α - private static final int GR_SIGMA = '\u03C3'; // σ - private static final int GR_DELTA = '\u03B4'; // δ - private static final int GR_PHI = '\u03C6'; // φ - private static final int GR_GAMMA = '\u03B3'; // γ - private static final int GR_ETA = '\u03B7'; // η - private static final int GR_XI = '\u03BE'; // ξ - private static final int GR_KAPPA = '\u03BA'; // κ - private static final int GR_LAMDA = '\u03BB'; // λ - private static final int GR_ZETA = '\u03B6'; // ζ - private static final int GR_CHI = '\u03C7'; // χ - private static final int GR_PSI = '\u03C8'; // ψ - private static final int GR_OMEGA = '\u03C9'; // ω - private static final int GR_BETA = '\u03B2'; // β - private static final int GR_NU = '\u03BD'; // ν - private static final int GR_MU = '\u03BC'; // μ - static final int[] PROXIMITY = { - // Proximity for row 1. This must have exactly ROW_SIZE entries for each letter, - // and exactly PROXIMITY_GRID_WIDTH letters for a row. Pad with NUL's. - // The number of rows must be exactly PROXIMITY_GRID_HEIGHT. - GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_SIGMA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_EPSILON, GR_FINAL_SIGMA, GR_RHO, GR_SIGMA, GR_DELTA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_RHO, GR_EPSILON, GR_TAU, GR_DELTA, GR_PHI, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_TAU, GR_RHO, GR_UPSILON, GR_PHI, GR_GAMMA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_UPSILON, GR_TAU, GR_THETA, GR_GAMMA, GR_ETA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_THETA, GR_UPSILON, GR_IOTA, GR_ETA, GR_XI, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_IOTA, GR_THETA, GR_OMICRON, GR_XI, GR_KAPPA, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_OMICRON, GR_IOTA, GR_PI, GR_KAPPA, GR_LAMDA, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_PI, GR_OMICRON, GR_LAMDA, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - GR_ALPHA, GR_FINAL_SIGMA, GR_SIGMA, GR_ZETA, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_SIGMA, GR_FINAL_SIGMA, GR_EPSILON, GR_ALPHA, GR_DELTA, GR_ZETA, GR_CHI, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_DELTA, GR_EPSILON, GR_RHO, GR_SIGMA, GR_PHI, GR_ZETA, GR_CHI, GR_PSI, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_PHI, GR_RHO, GR_TAU, GR_DELTA, GR_GAMMA, GR_CHI, GR_PSI, GR_OMEGA, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_GAMMA, GR_TAU, GR_UPSILON, GR_PHI, GR_ETA, GR_PSI, GR_OMEGA, GR_BETA, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_ETA, GR_UPSILON, GR_THETA, GR_GAMMA, GR_XI, GR_OMEGA, GR_BETA, GR_NU, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_XI, GR_THETA, GR_IOTA, GR_ETA, GR_KAPPA, GR_BETA, GR_NU, GR_MU, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_KAPPA, GR_IOTA, GR_OMICRON, GR_XI, GR_LAMDA, GR_NU, GR_MU, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_LAMDA, GR_OMICRON, GR_PI, GR_KAPPA, GR_MU, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - - GR_ZETA, GR_ALPHA, GR_SIGMA, GR_DELTA, GR_CHI, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_CHI, GR_SIGMA, GR_DELTA, GR_PHI, GR_ZETA, GR_PSI, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_PSI, GR_DELTA, GR_PHI, GR_GAMMA, GR_CHI, GR_OMEGA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_OMEGA, GR_PHI, GR_GAMMA, GR_ETA, GR_PSI, GR_BETA, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_BETA, GR_GAMMA, GR_ETA, GR_XI, GR_OMEGA, GR_NU, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_NU, GR_ETA, GR_XI, GR_KAPPA, GR_BETA, GR_MU, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - GR_MU, GR_XI, GR_KAPPA, GR_LAMDA, GR_NU, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL, - }; - - static final SparseIntArray INDICES = new SparseIntArray(PROXIMITY.length / ROW_SIZE); - - static { - buildProximityIndices(PROXIMITY, ROW_SIZE, INDICES); - } - } - - private static int[] getProximityForScript(final int script) { - switch (script) { - case AndroidSpellCheckerService.SCRIPT_LATIN: - return Latin.PROXIMITY; - case AndroidSpellCheckerService.SCRIPT_CYRILLIC: - return Cyrillic.PROXIMITY; - case AndroidSpellCheckerService.SCRIPT_GREEK: - return Greek.PROXIMITY; - default: - throw new RuntimeException("Wrong script supplied: " + script); - } - } - - private static int getIndexOfCodeForScript(final int codePoint, final int script) { - switch (script) { - case AndroidSpellCheckerService.SCRIPT_LATIN: - return Latin.INDICES.get(codePoint, NOT_AN_INDEX); - case AndroidSpellCheckerService.SCRIPT_CYRILLIC: - return Cyrillic.INDICES.get(codePoint, NOT_AN_INDEX); - case AndroidSpellCheckerService.SCRIPT_GREEK: - return Greek.INDICES.get(codePoint, NOT_AN_INDEX); - default: - throw new RuntimeException("Wrong script supplied: " + script); - } - } - - // Returns (Y << 16) + X to avoid creating a temporary object. This is okay because - // X and Y are limited to PROXIMITY_GRID_WIDTH resp. PROXIMITY_GRID_HEIGHT which is very - // inferior to 1 << 16 - // As an exception, this returns NOT_A_COORDINATE_PAIR if the key is not on the grid - public static int getXYForCodePointAndScript(final int codePoint, final int script) { - final int index = getIndexOfCodeForScript(codePoint, script); - if (NOT_AN_INDEX == index) return NOT_A_COORDINATE_PAIR; - final int y = index / PROXIMITY_GRID_WIDTH; - final int x = index % PROXIMITY_GRID_WIDTH; - if (y > PROXIMITY_GRID_HEIGHT) { - // Safety check, should be entirely useless - throw new RuntimeException("Wrong y coordinate in spell checker proximity"); - } - return (y << 16) + x; - } -} diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java index 5f4c44636..58c8f266c 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java @@ -74,14 +74,14 @@ public class UserDictionaryAddWordFragment extends Fragment @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0, + R.string.user_dict_settings_add_menu_title).setIcon(R.drawable.ic_menu_add); + actionItemAdd.setShowAsAction( + MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); final MenuItem actionItemDelete = menu.add(0, OPTIONS_MENU_DELETE, 0, R.string.user_dict_settings_delete).setIcon(android.R.drawable.ic_menu_delete); actionItemDelete.setShowAsAction( MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); - final MenuItem actionItemAdd = menu.add(0, OPTIONS_MENU_ADD, 0, - R.string.user_dict_settings_delete).setIcon(R.drawable.ic_menu_add); - actionItemAdd.setShowAsAction( - MenuItem.SHOW_AS_ACTION_IF_ROOM | MenuItem.SHOW_AS_ACTION_WITH_TEXT); } /** diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java index 36bc5ba49..50dda9663 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionarySettings.java @@ -108,6 +108,7 @@ public class UserDictionarySettings extends ListFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getActivity().getActionBar().setTitle(R.string.edit_personal_dictionary); } @Override diff --git a/java/src/com/android/inputmethod/research/FixedLogBuffer.java b/java/src/com/android/inputmethod/research/FixedLogBuffer.java index 4249af544..8b64de8ae 100644 --- a/java/src/com/android/inputmethod/research/FixedLogBuffer.java +++ b/java/src/com/android/inputmethod/research/FixedLogBuffer.java @@ -65,6 +65,7 @@ public class FixedLogBuffer extends LogBuffer { final int numWordsIncoming = newLogUnit.getNumWords(); if (mNumActualWords >= mWordCapacity) { // Give subclass a chance to handle the buffer full condition by shifting out logUnits. + // TODO: Tell onBufferFull() how much space it needs to make to avoid forced eviction. onBufferFull(); // If still full, evict. if (mNumActualWords >= mWordCapacity) { @@ -119,21 +120,19 @@ public class FixedLogBuffer extends LogBuffer { /** * Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed. * - * If there are less than {@code numWords} word-containing {@link LogUnit}s, shifts out - * all {@code LogUnit}s in the buffer. + * If there are less than {@code numWords} in the buffer, shifts out all {@code LogUnit}s. * - * @param numWords the minimum number of word-containing {@link LogUnit}s to shift out - * @return the number of actual {@code LogUnit}s shifted out + * @param numWords the minimum number of words in {@link LogUnit}s to shift out + * @return the number of actual words LogUnit}s shifted out */ protected int shiftOutWords(final int numWords) { - int numWordContainingLogUnitsShiftedOut = 0; - for (LogUnit logUnit = shiftOut(); logUnit != null - && numWordContainingLogUnitsShiftedOut < numWords; logUnit = shiftOut()) { - if (logUnit.hasOneOrMoreWords()) { - numWordContainingLogUnitsShiftedOut += logUnit.getNumWords(); - } - } - return numWordContainingLogUnitsShiftedOut; + int numWordsShiftedOut = 0; + do { + final LogUnit logUnit = shiftOut(); + if (logUnit == null) break; + numWordsShiftedOut += logUnit.getNumWords(); + } while (numWordsShiftedOut < numWords); + return numWordsShiftedOut; } public void shiftOutAll() { diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java index 42ef5d3b6..9bdedbf6d 100644 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ b/java/src/com/android/inputmethod/research/MainLogBuffer.java @@ -190,22 +190,30 @@ public abstract class MainLogBuffer extends FixedLogBuffer { } protected final void publishLogUnitsAtFrontOfBuffer() { + // TODO: Refactor this method to require fewer passes through the LogUnits. Should really + // require only one pass. ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE); if (isSafeNGram(logUnits, N_GRAM_SIZE)) { // Good n-gram at the front of the buffer. Publish it, disclosing details. publish(logUnits, true /* canIncludePrivateData */); shiftOutWords(N_GRAM_SIZE); mNumWordsUntilSafeToSample = mNumWordsBetweenNGrams; - } else { - // No good n-gram at front, and buffer is full. Shift out up through the first logUnit - // with associated words (or if there is none, all the existing logUnits). - logUnits.clear(); - for (LogUnit logUnit = shiftOut(); logUnit != null && !logUnit.hasOneOrMoreWords(); - logUnit = shiftOut()) { - logUnits.add(logUnit); + return; + } + // No good n-gram at front, and buffer is full. Shift out up through the first logUnit + // with associated words (or if there is none, all the existing logUnits). + logUnits.clear(); + LogUnit logUnit = shiftOut(); + while (logUnit != null) { + logUnits.add(logUnit); + final int numWords = logUnit.getNumWords(); + if (numWords > 0) { + mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWords); + break; } - publish(logUnits, false /* canIncludePrivateData */); + logUnit = shiftOut(); } + publish(logUnits, false /* canIncludePrivateData */); } /** @@ -222,12 +230,11 @@ public abstract class MainLogBuffer extends FixedLogBuffer { @Override protected int shiftOutWords(final int numWords) { - final int numWordContainingLogUnitsShiftedOut = super.shiftOutWords(numWords); - mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - - numWordContainingLogUnitsShiftedOut); + final int numWordsShiftedOut = super.shiftOutWords(numWords); + mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWordsShiftedOut); if (DEBUG) { Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample); } - return numWordContainingLogUnitsShiftedOut; + return numWordsShiftedOut; } } |