diff options
Diffstat (limited to 'java/src/com/android/inputmethod')
162 files changed, 2452 insertions, 7908 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java b/java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java new file mode 100644 index 000000000..967cafad0 --- /dev/null +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityLongPressTimer.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2014 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.accessibility; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.latin.R; + +// Handling long press timer to show a more keys keyboard. +final class AccessibilityLongPressTimer extends Handler { + public interface LongPressTimerCallback { + public void onLongPressed(Key key); + } + + private static final int MSG_LONG_PRESS = 1; + + private final LongPressTimerCallback mCallback; + private final long mConfigAccessibilityLongPressTimeout; + + public AccessibilityLongPressTimer(final LongPressTimerCallback callback, + final Context context) { + super(); + mCallback = callback; + mConfigAccessibilityLongPressTimeout = context.getResources().getInteger( + R.integer.config_accessibility_long_press_key_timeout); + } + + @Override + public void handleMessage(final Message msg) { + switch (msg.what) { + case MSG_LONG_PRESS: + cancelLongPress(); + mCallback.onLongPressed((Key)msg.obj); + return; + default: + super.handleMessage(msg); + return; + } + } + + public void startLongPress(final Key key) { + cancelLongPress(); + final Message longPressMessage = obtainMessage(MSG_LONG_PRESS, key); + sendMessageDelayed(longPressMessage, mConfigAccessibilityLongPressTimeout); + } + + public void cancelLongPress() { + removeMessages(MSG_LONG_PRESS); + } +} diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java index d50dd3ee6..2762a9f25 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java @@ -67,7 +67,6 @@ public final class AccessibilityUtils { // These only need to be initialized if the kill switch is off. sInstance.initInternal(context); - KeyCodeDescriptionMapper.init(); } public static AccessibilityUtils getInstance() { @@ -114,7 +113,7 @@ public final class AccessibilityUtils { * @param event The event to check. * @return {@true} is the event is a touch exploration event */ - public boolean isTouchExplorationEvent(final MotionEvent event) { + public static boolean isTouchExplorationEvent(final MotionEvent event) { final int action = event.getAction(); return action == MotionEvent.ACTION_HOVER_ENTER || action == MotionEvent.ACTION_HOVER_EXIT diff --git a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java index 2c87fc1e9..7a3510ee1 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -28,35 +28,29 @@ import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; +import com.android.inputmethod.latin.utils.StringUtils; import java.util.Locale; -public final class KeyCodeDescriptionMapper { +final class KeyCodeDescriptionMapper { private static final String TAG = KeyCodeDescriptionMapper.class.getSimpleName(); private static final String SPOKEN_LETTER_RESOURCE_NAME_FORMAT = "spoken_accented_letter_%04X"; + private static final String SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT = "spoken_symbol_%04X"; private static final String SPOKEN_EMOJI_RESOURCE_NAME_FORMAT = "spoken_emoji_%04X"; // The resource ID of the string spoken for obscured keys private static final int OBSCURED_KEY_RES_ID = R.string.spoken_description_dot; - private static KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); - - // Sparse array of spoken description resource IDs indexed by key codes - private final SparseIntArray mKeyCodeMap; - - public static void init() { - sInstance.initInternal(); - } + private static final KeyCodeDescriptionMapper sInstance = new KeyCodeDescriptionMapper(); public static KeyCodeDescriptionMapper getInstance() { return sInstance; } - private KeyCodeDescriptionMapper() { - mKeyCodeMap = new SparseIntArray(); - } + // Sparse array of spoken description resource IDs indexed by key codes + private final SparseIntArray mKeyCodeMap = new SparseIntArray(); - private void initInternal() { + private KeyCodeDescriptionMapper() { // Special non-character codes defined in Keyboard mKeyCodeMap.put(Constants.CODE_SPACE, R.string.spoken_description_space); mKeyCodeMap.put(Constants.CODE_DELETE, R.string.spoken_description_delete); @@ -86,14 +80,6 @@ public final class KeyCodeDescriptionMapper { /** * Returns the localized description of the action performed by a specified * key based on the current keyboard state. - * <p> - * The order of precedence for key descriptions is: - * <ol> - * <li>Manually-defined based on the key label</li> - * <li>Automatic or manually-defined based on the key code</li> - * <li>Automatically based on the key label</li> - * <li>{code null} for keys with no label or key code defined</li> - * </p> * * @param context The package's context. * @param keyboard The keyboard on which the key resides. @@ -128,7 +114,20 @@ public final class KeyCodeDescriptionMapper { // Just attempt to speak the description. if (code != Constants.CODE_UNSPECIFIED) { - return getDescriptionForKeyCode(context, keyboard, key, shouldObscure); + // If the key description should be obscured, now is the time to do it. + final boolean isDefinedNonCtrl = Character.isDefined(code) + && !Character.isISOControl(code); + if (shouldObscure && isDefinedNonCtrl) { + return context.getString(OBSCURED_KEY_RES_ID); + } + final String description = getDescriptionForCodePoint(context, code); + if (description != null) { + return description; + } + if (!TextUtils.isEmpty(key.getLabel())) { + return key.getLabel(); + } + return context.getString(R.string.spoken_description_unknown); } return null; } @@ -254,57 +253,39 @@ public final class KeyCodeDescriptionMapper { /** * Returns a localized character sequence describing what will happen when - * the specified key is pressed based on its key code. - * <p> - * The order of precedence for key code descriptions is: - * <ol> - * <li>Manually-defined shift-locked description</li> - * <li>Manually-defined shifted description</li> - * <li>Manually-defined normal description</li> - * <li>Automatic based on the character represented by the key code</li> - * <li>Fall-back for undefined or control characters</li> - * </ol> - * </p> + * the specified key is pressed based on its key code point. * * @param context The package's context. - * @param keyboard The keyboard on which the key resides. - * @param key The key from which to obtain a description. - * @param shouldObscure {@true} if text (e.g. non-control) characters should be obscured. - * @return a character sequence describing the action performed by pressing the key + * @param codePoint The code point from which to obtain a description. + * @return a character sequence describing the code point. */ - private String getDescriptionForKeyCode(final Context context, final Keyboard keyboard, - final Key key, final boolean shouldObscure) { - final int code = key.getCode(); - + public String getDescriptionForCodePoint(final Context context, final int codePoint) { // If the key description should be obscured, now is the time to do it. - final boolean isDefinedNonCtrl = Character.isDefined(code) && !Character.isISOControl(code); - if (shouldObscure && isDefinedNonCtrl) { - return context.getString(OBSCURED_KEY_RES_ID); - } - final int index = mKeyCodeMap.indexOfKey(code); + final int index = mKeyCodeMap.indexOfKey(codePoint); if (index >= 0) { return context.getString(mKeyCodeMap.valueAt(index)); } - final String accentedLetter = getSpokenAccentedLetterDescriptionId(context, code); + final String accentedLetter = getSpokenAccentedLetterDescription(context, codePoint); if (accentedLetter != null) { return accentedLetter; } - // Here, <code>code</code> may be a base letter. - final int spokenEmojiId = getSpokenDescriptionId( - context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT); - if (spokenEmojiId != 0) { - return context.getString(spokenEmojiId); + // Here, <code>code</code> may be a base (non-accented) letter. + final String unsupportedSymbol = getSpokenSymbolDescription(context, codePoint); + if (unsupportedSymbol != null) { + return unsupportedSymbol; } - if (isDefinedNonCtrl) { - return Character.toString((char) code); + final String emojiDescription = getSpokenEmojiDescription(context, codePoint); + if (emojiDescription != null) { + return emojiDescription; } - if (!TextUtils.isEmpty(key.getLabel())) { - return key.getLabel(); + if (Character.isDefined(codePoint) && !Character.isISOControl(codePoint)) { + return StringUtils.newSingleCodePointString(codePoint); } - return context.getString(R.string.spoken_description_unknown, code); + return null; } - private String getSpokenAccentedLetterDescriptionId(final Context context, final int code) { + // TODO: Remove this method once TTS supports those accented letters' verbalization. + private String getSpokenAccentedLetterDescription(final Context context, final int code) { final boolean isUpperCase = Character.isUpperCase(code); final int baseCode = isUpperCase ? Character.toLowerCase(code) : code; final int baseIndex = mKeyCodeMap.indexOfKey(baseCode); @@ -314,10 +295,38 @@ public final class KeyCodeDescriptionMapper { return null; } final String spokenText = context.getString(resId); - return isUpperCase ? context.getString(R.string.spoke_description_upper_case, spokenText) + return isUpperCase ? context.getString(R.string.spoken_description_upper_case, spokenText) : spokenText; } + // TODO: Remove this method once TTS supports those symbols' verbalization. + private String getSpokenSymbolDescription(final Context context, final int code) { + final int resId = getSpokenDescriptionId(context, code, SPOKEN_SYMBOL_RESOURCE_NAME_FORMAT); + if (resId == 0) { + return null; + } + final String spokenText = context.getString(resId); + if (!TextUtils.isEmpty(spokenText)) { + return spokenText; + } + // If a translated description is empty, fall back to unknown symbol description. + return context.getString(R.string.spoken_symbol_unknown); + } + + // TODO: Remove this method once TTS supports emoji verbalization. + private String getSpokenEmojiDescription(final Context context, final int code) { + final int resId = getSpokenDescriptionId(context, code, SPOKEN_EMOJI_RESOURCE_NAME_FORMAT); + if (resId == 0) { + return null; + } + final String spokenText = context.getString(resId); + if (!TextUtils.isEmpty(spokenText)) { + return spokenText; + } + // If a translated description is empty, fall back to unknown emoji description. + return context.getString(R.string.spoken_emoji_unknown); + } + private int getSpokenDescriptionId(final Context context, final int code, final String resourceNameFormat) { final String resourceName = String.format(Locale.ROOT, resourceNameFormat, code); diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java index eed40f4a9..d67d9dc4b 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java +++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java @@ -16,11 +16,12 @@ package com.android.inputmethod.accessibility; -import android.os.SystemClock; +import android.content.Context; import android.support.v4.view.AccessibilityDelegateCompat; import android.support.v4.view.ViewCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.ViewParent; @@ -30,15 +31,31 @@ import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardView; +import com.android.inputmethod.keyboard.PointerTracker; +/** + * This class represents a delegate that can be registered in a class that extends + * {@link KeyboardView} to enhance accessibility support via composition rather via inheritance. + * + * To implement accessibility mode, the target keyboard view has to:<p> + * - Call {@link #setKeyboard(Keyboard)} when a new keyboard is set to the keyboard view. + * - Dispatch a hover event by calling {@link #onHoverEnter(MotionEvent)}. + * + * @param <KV> The keyboard view class type. + */ public class KeyboardAccessibilityDelegate<KV extends KeyboardView> extends AccessibilityDelegateCompat { + private static final String TAG = KeyboardAccessibilityDelegate.class.getSimpleName(); + protected static final boolean DEBUG_HOVER = false; + protected final KV mKeyboardView; protected final KeyDetector mKeyDetector; private Keyboard mKeyboard; private KeyboardAccessibilityNodeProvider mAccessibilityNodeProvider; private Key mLastHoverKey; + public static final int HOVER_EVENT_POINTER_ID = 0; + public KeyboardAccessibilityDelegate(final KV keyboardView, final KeyDetector keyDetector) { super(); mKeyboardView = keyboardView; @@ -65,10 +82,31 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView> mKeyboard = keyboard; } - protected Keyboard getKeyboard() { + protected final Keyboard getKeyboard() { return mKeyboard; } + protected final void setLastHoverKey(final Key key) { + mLastHoverKey = key; + } + + protected final Key getLastHoverKey() { + return mLastHoverKey; + } + + /** + * Sends a window state change event with the specified string resource id. + * + * @param resId The string resource id of the text to send with the event. + */ + protected void sendWindowStateChanged(final int resId) { + if (resId == 0) { + return; + } + final Context context = mKeyboardView.getContext(); + sendWindowStateChanged(context.getString(resId)); + } + /** * Sends a window state change event with the specified text. * @@ -114,119 +152,182 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView> } /** - * Receives hover events when touch exploration is turned on in SDK versions ICS and higher. + * Get a key that a hover event is on. * * @param event The hover event. - * @return {@code true} if the event is handled + * @return key The key that the <code>event</code> is on. */ - public boolean dispatchHoverEvent(final MotionEvent event) { - final int x = (int) event.getX(); - final int y = (int) event.getY(); - final Key previousKey = mLastHoverKey; - final Key key = mKeyDetector.detectHitKey(x, y); - mLastHoverKey = key; + protected final Key getHoverKeyOf(final MotionEvent event) { + final int actionIndex = event.getActionIndex(); + final int x = (int)event.getX(actionIndex); + final int y = (int)event.getY(actionIndex); + return mKeyDetector.detectHitKey(x, y); + } - switch (event.getAction()) { - case MotionEvent.ACTION_HOVER_EXIT: - // Make sure we're not getting an EXIT event because the user slid - // off the keyboard area, then force a key press. - if (key != null) { - final long downTime = simulateKeyPress(key); - simulateKeyRelease(key, downTime); - } - //$FALL-THROUGH$ + /** + * Receives hover events when touch exploration is turned on in SDK versions ICS and higher. + * + * @param event The hover event. + * @return {@code true} if the event is handled. + */ + public boolean onHoverEvent(final MotionEvent event) { + switch (event.getActionMasked()) { case MotionEvent.ACTION_HOVER_ENTER: - return onHoverKey(key, event); + onHoverEnter(event); + break; case MotionEvent.ACTION_HOVER_MOVE: - if (key != previousKey) { - return onTransitionKey(key, previousKey, event); + onHoverMove(event); + break; + case MotionEvent.ACTION_HOVER_EXIT: + onHoverExit(event); + break; + default: + Log.w(getClass().getSimpleName(), "Unknown hover event: " + event); + break; + } + return true; + } + + /** + * Process {@link MotionEvent#ACTION_HOVER_ENTER} event. + * + * @param event A hover enter event. + */ + protected void onHoverEnter(final MotionEvent event) { + final Key key = getHoverKeyOf(event); + if (DEBUG_HOVER) { + Log.d(TAG, "onHoverEnter: key=" + key); + } + if (key != null) { + onHoverEnterTo(key); + } + setLastHoverKey(key); + } + + /** + * Process {@link MotionEvent#ACTION_HOVER_MOVE} event. + * + * @param event A hover move event. + */ + protected void onHoverMove(final MotionEvent event) { + final Key lastKey = getLastHoverKey(); + final Key key = getHoverKeyOf(event); + if (key != lastKey) { + if (lastKey != null) { + onHoverExitFrom(lastKey); } - return onHoverKey(key, event); + if (key != null) { + onHoverEnterTo(key); + } + } + if (key != null) { + onHoverMoveWithin(key); } - return false; + setLastHoverKey(key); } /** - * Simulates a key press by injecting touch an event into the keyboard view. - * This avoids the complexity of trackers and listeners within the keyboard. + * Process {@link MotionEvent#ACTION_HOVER_EXIT} event. * - * @param key The key to press. + * @param event A hover exit event. */ - private long simulateKeyPress(final Key key) { - final int x = key.getHitBox().centerX(); - final int y = key.getHitBox().centerY(); - final long downTime = SystemClock.uptimeMillis(); - final MotionEvent downEvent = MotionEvent.obtain( - downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0); - mKeyboardView.onTouchEvent(downEvent); - downEvent.recycle(); - return downTime; + protected void onHoverExit(final MotionEvent event) { + final Key lastKey = getLastHoverKey(); + if (DEBUG_HOVER) { + Log.d(TAG, "onHoverExit: key=" + getHoverKeyOf(event) + " last=" + lastKey); + } + if (lastKey != null) { + onHoverExitFrom(lastKey); + } + final Key key = getHoverKeyOf(event); + // Make sure we're not getting an EXIT event because the user slid + // off the keyboard area, then force a key press. + if (key != null) { + onRegisterHoverKey(key, event); + onHoverExitFrom(key); + } + setLastHoverKey(null); + } + + /** + * Register a key that is selected by a hover event + * + * @param key A key to be registered. + * @param event A hover exit event that triggers key registering. + */ + protected void onRegisterHoverKey(final Key key, final MotionEvent event) { + if (DEBUG_HOVER) { + Log.d(TAG, "onRegisterHoverKey: key=" + key); + } + simulateTouchEvent(MotionEvent.ACTION_DOWN, event); + simulateTouchEvent(MotionEvent.ACTION_UP, event); } /** - * Simulates a key release by injecting touch an event into the keyboard view. - * This avoids the complexity of trackers and listeners within the keyboard. + * Simulating a touch event by injecting a synthesized touch event into {@link PointerTracker}. * - * @param key The key to release. + * @param touchAction The action of the synthesizing touch event. + * @param hoverEvent The base hover event from that the touch event is synthesized. */ - private void simulateKeyRelease(final Key key, final long downTime) { - final int x = key.getHitBox().centerX(); - final int y = key.getHitBox().centerY(); - final MotionEvent upEvent = MotionEvent.obtain( - downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, x, y, 0); - mKeyboardView.onTouchEvent(upEvent); - upEvent.recycle(); + protected void simulateTouchEvent(final int touchAction, final MotionEvent hoverEvent) { + final MotionEvent touchEvent = synthesizeTouchEvent(touchAction, hoverEvent); + final int actionIndex = touchEvent.getActionIndex(); + final int pointerId = touchEvent.getPointerId(actionIndex); + final PointerTracker tracker = PointerTracker.getPointerTracker(pointerId); + tracker.processMotionEvent(touchEvent, mKeyDetector); + touchEvent.recycle(); } /** - * Simulates a transition between two {@link Key}s by sending a HOVER_EXIT on the previous key, - * a HOVER_ENTER on the current key, and a HOVER_MOVE on the current key. + * Synthesize a touch event from a hover event. * - * @param currentKey The currently hovered key. - * @param previousKey The previously hovered key. - * @param event The event that triggered the transition. - * @return {@code true} if the event was handled. - */ - private boolean onTransitionKey(final Key currentKey, final Key previousKey, - final MotionEvent event) { - final int savedAction = event.getAction(); - event.setAction(MotionEvent.ACTION_HOVER_EXIT); - onHoverKey(previousKey, event); - event.setAction(MotionEvent.ACTION_HOVER_ENTER); - onHoverKey(currentKey, event); - event.setAction(MotionEvent.ACTION_HOVER_MOVE); - final boolean handled = onHoverKey(currentKey, event); - event.setAction(savedAction); - return handled; - } - - /** - * Handles a hover event on a key. If {@link Key} extended View, this would be analogous to - * calling View.onHoverEvent(MotionEvent). + * @param touchAction The action of the synthesizing touch event. + * @param hoverEvent The base hover event from that the touch event is synthesized. + * @return The synthesized touch event of <code>touchAction</code> that has pointer information + * of <code>event</code>. + */ + protected static MotionEvent synthesizeTouchEvent(final int touchAction, + final MotionEvent hoverEvent) { + final MotionEvent touchEvent = MotionEvent.obtain(hoverEvent); + touchEvent.setAction(touchAction); + return touchEvent; + } + + /** + * Handles a hover enter event on a key. * * @param key The currently hovered key. - * @param event The hover event. - * @return {@code true} if the event was handled. */ - private boolean onHoverKey(final Key key, final MotionEvent event) { - // Null keys can't receive events. - if (key == null) { - return false; + protected void onHoverEnterTo(final Key key) { + if (DEBUG_HOVER) { + Log.d(TAG, "onHoverEnterTo: key=" + key); } + key.onPressed(); + mKeyboardView.invalidateKey(key); final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider(); + provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER); + provider.performActionForKey(key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS); + } - switch (event.getAction()) { - case MotionEvent.ACTION_HOVER_ENTER: - provider.sendAccessibilityEventForKey( - key, AccessibilityEventCompat.TYPE_VIEW_HOVER_ENTER); - provider.performActionForKey( - key, AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS, null); - break; - case MotionEvent.ACTION_HOVER_EXIT: - provider.sendAccessibilityEventForKey( - key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT); - break; + /** + * Handles a hover move event on a key. + * + * @param key The currently hovered key. + */ + protected void onHoverMoveWithin(final Key key) { } + + /** + * Handles a hover exit event on a key. + * + * @param key The currently hovered key. + */ + protected void onHoverExitFrom(final Key key) { + if (DEBUG_HOVER) { + Log.d(TAG, "onHoverExitFrom: key=" + key); } - return true; + key.onReleased(); + mKeyboardView.invalidateKey(key); + final KeyboardAccessibilityNodeProvider provider = getAccessibilityNodeProvider(); + provider.sendAccessibilityEventForKey(key, AccessibilityEventCompat.TYPE_VIEW_HOVER_EXIT); } } diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java index cddd1c7ed..18673a366 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java +++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java @@ -47,7 +47,7 @@ import java.util.List; * virtual views, thus conveying their logical structure. * </p> */ -public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodeProviderCompat { +final class KeyboardAccessibilityNodeProvider extends AccessibilityNodeProviderCompat { private static final String TAG = KeyboardAccessibilityNodeProvider.class.getSimpleName(); private static final int UNDEFINED = Integer.MIN_VALUE; @@ -134,7 +134,7 @@ public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodePr event.setClassName(key.getClass().getName()); event.setContentDescription(keyDescription); event.setEnabled(true); - final AccessibilityRecordCompat record = new AccessibilityRecordCompat(event); + final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event); record.setSource(mKeyboardView, virtualViewId); return event; } @@ -229,7 +229,7 @@ public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodePr if (key == null) { return false; } - return performActionForKey(key, action, arguments); + return performActionForKey(key, action); } /** @@ -237,25 +237,16 @@ public final class KeyboardAccessibilityNodeProvider extends AccessibilityNodePr * * @param key The on which to perform the action. * @param action The action to perform. - * @param arguments The action's arguments. * @return The result of performing the action, or false if the action is not supported. */ - boolean performActionForKey(final Key key, final int action, final Bundle arguments) { - final int virtualViewId = getVirtualViewIdOf(key); - + boolean performActionForKey(final Key key, final int action) { switch (action) { case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS: - if (mAccessibilityFocusedView == virtualViewId) { - return false; - } - mAccessibilityFocusedView = virtualViewId; + mAccessibilityFocusedView = getVirtualViewIdOf(key); sendAccessibilityEventForKey( key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED); return true; case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS: - if (mAccessibilityFocusedView != virtualViewId) { - return false; - } mAccessibilityFocusedView = UNDEFINED; sendAccessibilityEventForKey( key, AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); diff --git a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java index c114551c8..96f84dde9 100644 --- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java +++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java @@ -17,9 +17,9 @@ package com.android.inputmethod.accessibility; import android.content.Context; +import android.graphics.Rect; import android.os.SystemClock; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.util.Log; import android.util.SparseIntArray; import android.view.MotionEvent; @@ -28,11 +28,19 @@ import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.keyboard.MainKeyboardView; +import com.android.inputmethod.keyboard.PointerTracker; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; +/** + * This class represents a delegate that can be registered in {@link MainKeyboardView} to enhance + * accessibility support via composition rather via inheritance. + */ public final class MainKeyboardAccessibilityDelegate - extends KeyboardAccessibilityDelegate<MainKeyboardView> { + extends KeyboardAccessibilityDelegate<MainKeyboardView> + implements AccessibilityLongPressTimer.LongPressTimerCallback { + private static final String TAG = MainKeyboardAccessibilityDelegate.class.getSimpleName(); + /** Map of keyboard modes to resource IDs. */ private static final SparseIntArray KEYBOARD_MODE_RES_IDS = new SparseIntArray(); @@ -51,10 +59,16 @@ public final class MainKeyboardAccessibilityDelegate /** The most recently set keyboard mode. */ private int mLastKeyboardMode = KEYBOARD_IS_HIDDEN; private static final int KEYBOARD_IS_HIDDEN = -1; + // The rectangle region to ignore hover events. + private final Rect mBoundsToIgnoreHoverEvent = new Rect(); + + private final AccessibilityLongPressTimer mAccessibilityLongPressTimer; public MainKeyboardAccessibilityDelegate(final MainKeyboardView mainKeyboardView, final KeyDetector keyDetector) { super(mainKeyboardView, keyDetector); + mAccessibilityLongPressTimer = new AccessibilityLongPressTimer( + this /* callback */, mainKeyboardView.getContext()); } /** @@ -142,14 +156,28 @@ public final class MainKeyboardAccessibilityDelegate case KeyboardId.ELEMENT_ALPHABET: if (lastElementId == KeyboardId.ELEMENT_ALPHABET || lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + // Transition between alphabet mode and automatic shifted mode should be silently + // ignored because it can be determined by each key's talk back announce. return; } resId = R.string.spoken_description_mode_alpha; break; case KeyboardId.ELEMENT_ALPHABET_MANUAL_SHIFTED: + if (lastElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) { + // Resetting automatic shifted mode by pressing the shift key causes the transition + // from automatic shifted to manual shifted that should be silently ignored. + return; + } resId = R.string.spoken_description_shiftmode_on; break; case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED: + if (lastElementId == KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED) { + // Resetting caps locked mode by pressing the shift key causes the transition + // from shift locked to shift lock shifted that should be silently ignored. + return; + } + resId = R.string.spoken_description_shiftmode_locked; + break; case KeyboardId.ELEMENT_ALPHABET_SHIFT_LOCKED: resId = R.string.spoken_description_shiftmode_locked; break; @@ -168,17 +196,108 @@ public final class MainKeyboardAccessibilityDelegate default: return; } - final String text = mKeyboardView.getContext().getString(resId); - sendWindowStateChanged(text); + sendWindowStateChanged(resId); } /** * Announces that the keyboard has been hidden. */ private void announceKeyboardHidden() { - final Context context = mKeyboardView.getContext(); - final String text = context.getString(R.string.announce_keyboard_hidden); + sendWindowStateChanged(R.string.announce_keyboard_hidden); + } - sendWindowStateChanged(text); + @Override + protected void onRegisterHoverKey(final Key key, final MotionEvent event) { + final int x = key.getHitBox().centerX(); + final int y = key.getHitBox().centerY(); + if (DEBUG_HOVER) { + Log.d(TAG, "onRegisterHoverKey: key=" + key + + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)); + } + if (mBoundsToIgnoreHoverEvent.contains(x, y)) { + // This hover exit event points to the key that should be ignored. + // Clear the ignoring region to handle further hover events. + mBoundsToIgnoreHoverEvent.setEmpty(); + return; + } + super.onRegisterHoverKey(key, event); + } + + @Override + protected void onHoverEnterTo(final Key key) { + final int x = key.getHitBox().centerX(); + final int y = key.getHitBox().centerY(); + if (DEBUG_HOVER) { + Log.d(TAG, "onHoverEnterTo: key=" + key + + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)); + } + mAccessibilityLongPressTimer.cancelLongPress(); + if (mBoundsToIgnoreHoverEvent.contains(x, y)) { + return; + } + // This hover enter event points to the key that isn't in the ignoring region. + // Further hover events should be handled. + mBoundsToIgnoreHoverEvent.setEmpty(); + super.onHoverEnterTo(key); + if (key.isLongPressEnabled()) { + mAccessibilityLongPressTimer.startLongPress(key); + } + } + + @Override + protected void onHoverExitFrom(final Key key) { + final int x = key.getHitBox().centerX(); + final int y = key.getHitBox().centerY(); + if (DEBUG_HOVER) { + Log.d(TAG, "onHoverExitFrom: key=" + key + + " inIgnoreBounds=" + mBoundsToIgnoreHoverEvent.contains(x, y)); + } + mAccessibilityLongPressTimer.cancelLongPress(); + super.onHoverExitFrom(key); + } + + @Override + public void onLongPressed(final Key key) { + if (DEBUG_HOVER) { + Log.d(TAG, "onLongPressed: key=" + key); + } + final PointerTracker tracker = PointerTracker.getPointerTracker(HOVER_EVENT_POINTER_ID); + final long eventTime = SystemClock.uptimeMillis(); + final int x = key.getHitBox().centerX(); + final int y = key.getHitBox().centerY(); + final MotionEvent downEvent = MotionEvent.obtain( + eventTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0 /* metaState */); + // Inject a fake down event to {@link PointerTracker} to handle a long press correctly. + tracker.processMotionEvent(downEvent, mKeyDetector); + // The above fake down event triggers an unnecessary long press timer that should be + // canceled. + tracker.cancelLongPressTimer(); + downEvent.recycle(); + // Invoke {@link MainKeyboardView#onLongPress(PointerTracker)} as if a long press timeout + // has passed. + mKeyboardView.onLongPress(tracker); + // If {@link Key#hasNoPanelAutoMoreKeys()} is true (such as "0 +" key on the phone layout) + // or a key invokes IME switcher dialog, we should just ignore the next + // {@link #onRegisterHoverKey(Key,MotionEvent)}. It can be determined by whether + // {@link PointerTracker} is in operation or not. + if (tracker.isInOperation()) { + // This long press shows a more keys keyboard and further hover events should be + // handled. + mBoundsToIgnoreHoverEvent.setEmpty(); + return; + } + // This long press has handled at {@link MainKeyboardView#onLongPress(PointerTracker)}. + // We should ignore further hover events on this key. + mBoundsToIgnoreHoverEvent.set(key.getHitBox()); + if (key.hasNoPanelAutoMoreKey()) { + // This long press has registered a code point without showing a more keys keyboard. + // We should talk back the code point if possible. + final int codePointOfNoPanelAutoMoreKey = key.getMoreKeys()[0].mCode; + final String text = KeyCodeDescriptionMapper.getInstance().getDescriptionForCodePoint( + mKeyboardView.getContext(), codePointOfNoPanelAutoMoreKey); + if (text != null) { + sendWindowStateChanged(text); + } + } } } diff --git a/java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java new file mode 100644 index 000000000..3a56c5d2a --- /dev/null +++ b/java/src/com/android/inputmethod/accessibility/MoreKeysKeyboardAccessibilityDelegate.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2014 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.accessibility; + +import android.graphics.Rect; +import android.util.Log; +import android.view.MotionEvent; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.KeyDetector; +import com.android.inputmethod.keyboard.MoreKeysKeyboardView; +import com.android.inputmethod.latin.Constants; + +/** + * This class represents a delegate that can be registered in {@link MoreKeysKeyboardView} to + * enhance accessibility support via composition rather via inheritance. + */ +public class MoreKeysKeyboardAccessibilityDelegate + extends KeyboardAccessibilityDelegate<MoreKeysKeyboardView> { + private static final String TAG = MoreKeysKeyboardAccessibilityDelegate.class.getSimpleName(); + + private final Rect mMoreKeysKeyboardValidBounds = new Rect(); + private static final int CLOSING_INSET_IN_PIXEL = 1; + private int mOpenAnnounceResId; + private int mCloseAnnounceResId; + + public MoreKeysKeyboardAccessibilityDelegate(final MoreKeysKeyboardView moreKeysKeyboardView, + final KeyDetector keyDetector) { + super(moreKeysKeyboardView, keyDetector); + } + + public void setOpenAnnounce(final int resId) { + mOpenAnnounceResId = resId; + } + + public void setCloseAnnounce(final int resId) { + mCloseAnnounceResId = resId; + } + + public void onShowMoreKeysKeyboard() { + sendWindowStateChanged(mOpenAnnounceResId); + } + + @Override + protected void onHoverEnter(final MotionEvent event) { + if (DEBUG_HOVER) { + Log.d(TAG, "onHoverEnter: key=" + getHoverKeyOf(event)); + } + super.onHoverEnter(event); + final int actionIndex = event.getActionIndex(); + final int x = (int)event.getX(actionIndex); + final int y = (int)event.getY(actionIndex); + final int pointerId = event.getPointerId(actionIndex); + final long eventTime = event.getEventTime(); + mKeyboardView.onDownEvent(x, y, pointerId, eventTime); + } + + @Override + protected void onHoverMove(final MotionEvent event) { + super.onHoverMove(event); + final int actionIndex = event.getActionIndex(); + final int x = (int)event.getX(actionIndex); + final int y = (int)event.getY(actionIndex); + final int pointerId = event.getPointerId(actionIndex); + final long eventTime = event.getEventTime(); + mKeyboardView.onMoveEvent(x, y, pointerId, eventTime); + } + + @Override + protected void onHoverExit(final MotionEvent event) { + final Key lastKey = getLastHoverKey(); + if (DEBUG_HOVER) { + Log.d(TAG, "onHoverExit: key=" + getHoverKeyOf(event) + " last=" + lastKey); + } + if (lastKey != null) { + super.onHoverExitFrom(lastKey); + } + setLastHoverKey(null); + final int actionIndex = event.getActionIndex(); + final int x = (int)event.getX(actionIndex); + final int y = (int)event.getY(actionIndex); + final int pointerId = event.getPointerId(actionIndex); + final long eventTime = event.getEventTime(); + // A hover exit event at one pixel width or height area on the edges of more keys keyboard + // are treated as closing. + mMoreKeysKeyboardValidBounds.set(0, 0, mKeyboardView.getWidth(), mKeyboardView.getHeight()); + mMoreKeysKeyboardValidBounds.inset(CLOSING_INSET_IN_PIXEL, CLOSING_INSET_IN_PIXEL); + if (mMoreKeysKeyboardValidBounds.contains(x, y)) { + // Invoke {@link MoreKeysKeyboardView#onUpEvent(int,int,int,long)} as if this hover + // exit event selects a key. + mKeyboardView.onUpEvent(x, y, pointerId, eventTime); + mKeyboardView.dismissMoreKeysPanel(); + return; + } + // Close the more keys keyboard. + mKeyboardView.onMoveEvent( + Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, pointerId, eventTime); + sendWindowStateChanged(mCloseAnnounceResId); + } +} diff --git a/java/src/com/android/inputmethod/accessibility/MoreSuggestionsAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/MoreSuggestionsAccessibilityDelegate.java new file mode 100644 index 000000000..dfc866113 --- /dev/null +++ b/java/src/com/android/inputmethod/accessibility/MoreSuggestionsAccessibilityDelegate.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014 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.accessibility; + +import android.view.MotionEvent; + +import com.android.inputmethod.keyboard.KeyDetector; +import com.android.inputmethod.keyboard.MoreKeysKeyboardView; + +public final class MoreSuggestionsAccessibilityDelegate + extends MoreKeysKeyboardAccessibilityDelegate { + public MoreSuggestionsAccessibilityDelegate(final MoreKeysKeyboardView moreKeysKeyboardView, + final KeyDetector keyDetector) { + super(moreKeysKeyboardView, keyDetector); + } + + @Override + protected void simulateTouchEvent(final int touchAction, final MotionEvent hoverEvent) { + final MotionEvent touchEvent = synthesizeTouchEvent(touchAction, hoverEvent); + mKeyboardView.onTouchEvent(touchEvent); + touchEvent.recycle(); + } +} diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java index 60f7e2def..4d51821f2 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java @@ -27,7 +27,6 @@ import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.SuggestionSpanPickedNotificationReceiver; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.lang.reflect.Field; import java.util.ArrayList; @@ -73,13 +72,13 @@ public final class SuggestionSpanUtils { return pickedWord; } - final ArrayList<String> suggestionsList = CollectionUtils.newArrayList(); + final ArrayList<String> suggestionsList = new ArrayList<>(); for (int i = 0; i < suggestedWords.size(); ++i) { if (suggestionsList.size() >= SuggestionSpan.SUGGESTIONS_MAX_SIZE) { break; } final SuggestedWordInfo info = suggestedWords.getInfo(i); - if (info.mKind == SuggestedWordInfo.KIND_PREDICTION) { + if (info.isKindOf(SuggestedWordInfo.KIND_PREDICTION)) { continue; } final String word = suggestedWords.getWord(i); diff --git a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java index dec739d39..767cc423d 100644 --- a/java/src/com/android/inputmethod/compat/ViewCompatUtils.java +++ b/java/src/com/android/inputmethod/compat/ViewCompatUtils.java @@ -24,23 +24,13 @@ import java.lang.reflect.Method; // Currently {@link #getPaddingEnd(View)} and {@link #setPaddingRelative(View,int,int,int,int)} // are missing from android-support-v4 static library in KitKat SDK. public final class ViewCompatUtils { - // Note that View.LAYOUT_DIRECTION_LTR and View.LAYOUT_DIRECTION_RTL have been introduced in - // API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1). - public static final int LAYOUT_DIRECTION_LTR = (Integer)CompatUtils.getFieldValue(null, 0x0, - CompatUtils.getField(View.class, "LAYOUT_DIRECTION_LTR")); - public static final int LAYOUT_DIRECTION_RTL = (Integer)CompatUtils.getFieldValue(null, 0x1, - CompatUtils.getField(View.class, "LAYOUT_DIRECTION_RTL")); - - // Note that View.getPaddingEnd(), View.setPaddingRelative(int,int,int,int), and - // View.getLayoutDirection() have been introduced in API level 17 - // (Build.VERSION_CODE.JELLY_BEAN_MR1). + // Note that View.getPaddingEnd(), View.setPaddingRelative(int,int,int,int) have been + // introduced in API level 17 (Build.VERSION_CODE.JELLY_BEAN_MR1). private static final Method METHOD_getPaddingEnd = CompatUtils.getMethod( View.class, "getPaddingEnd"); private static final Method METHOD_setPaddingRelative = CompatUtils.getMethod( View.class, "setPaddingRelative", Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE); - private static final Method METHOD_getLayoutDirection = CompatUtils.getMethod( - View.class, "getLayoutDirection"); private ViewCompatUtils() { // This utility class is not publicly instantiable. @@ -61,11 +51,4 @@ public final class ViewCompatUtils { } CompatUtils.invoke(view, null, METHOD_setPaddingRelative, start, top, end, bottom); } - - public static int getLayoutDirection(final View view) { - if (METHOD_getLayoutDirection == null) { - return LAYOUT_DIRECTION_LTR; - } - return (Integer)CompatUtils.invoke(view, 0, METHOD_getLayoutDirection); - } } diff --git a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java index 3f69cedee..3d294acd7 100644 --- a/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java +++ b/java/src/com/android/inputmethod/dictionarypack/ActionBatch.java @@ -16,7 +16,6 @@ package com.android.inputmethod.dictionarypack; -import android.app.DownloadManager; import android.app.DownloadManager.Request; import android.content.ContentValues; import android.content.Context; @@ -600,7 +599,7 @@ public final class ActionBatch { private final Queue<Action> mActions; public ActionBatch() { - mActions = new LinkedList<Action>(); + mActions = new LinkedList<>(); } public void add(final Action a) { diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java index 2623eff56..1d84e5888 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryDownloadProgressBar.java @@ -29,7 +29,6 @@ import android.view.View; import android.widget.ProgressBar; public class DictionaryDownloadProgressBar extends ProgressBar { - @SuppressWarnings("unused") private static final String TAG = DictionaryDownloadProgressBar.class.getSimpleName(); private static final int NOT_A_DOWNLOADMANAGER_PENDING_ID = 0; @@ -119,7 +118,6 @@ public class DictionaryDownloadProgressBar extends ProgressBar { try { final UpdateHelper updateHelper = new UpdateHelper(); final Query query = new Query().setFilterById(mId); - int lastProgress = 0; setIndeterminate(true); while (!isInterrupted()) { final Cursor cursor = mDownloadManagerWrapper.query(query); diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java index 13c07de35..8e026171d 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryListInterfaceState.java @@ -18,8 +18,6 @@ package com.android.inputmethod.dictionarypack; import android.view.View; -import com.android.inputmethod.latin.utils.CollectionUtils; - import java.util.ArrayList; import java.util.HashMap; @@ -39,8 +37,8 @@ public class DictionaryListInterfaceState { public int mStatus = MetadataDbHelper.STATUS_UNKNOWN; } - private HashMap<String, State> mWordlistToState = CollectionUtils.newHashMap(); - private ArrayList<View> mViewCache = CollectionUtils.newArrayList(); + private HashMap<String, State> mWordlistToState = new HashMap<>(); + private ArrayList<View> mViewCache = new ArrayList<>(); public boolean isOpen(final String wordlistId) { final State state = mWordlistToState.get(wordlistId); diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java index c35995b24..f5bd84c8c 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionaryProvider.java @@ -357,7 +357,7 @@ public final class DictionaryProvider extends ContentProvider { return Collections.<WordListInfo>emptyList(); } try { - final HashMap<String, WordListInfo> dicts = new HashMap<String, WordListInfo>(); + final HashMap<String, WordListInfo> dicts = new HashMap<>(); final int idIndex = results.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN); final int localeIndex = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN); final int localFileNameIndex = diff --git a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java index dae2f22a4..11982fa65 100644 --- a/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java +++ b/java/src/com/android/inputmethod/dictionarypack/DictionarySettingsFragment.java @@ -33,13 +33,13 @@ import android.preference.PreferenceGroup; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; -import android.view.animation.AnimationUtils; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.animation.AnimationUtils; import com.android.inputmethod.latin.R; @@ -67,8 +67,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 + // never null + private TreeMap<String, WordListPreference> mCurrentPreferenceMap = new TreeMap<>(); private final BroadcastReceiver mConnectivityChangedReceiver = new BroadcastReceiver() { @Override @@ -280,19 +280,18 @@ public final class DictionarySettingsFragment extends PreferenceFragment : activity.getContentResolver().query(contentUri, null, null, null, null); if (null == cursor) { - final ArrayList<Preference> result = new ArrayList<Preference>(); + final ArrayList<Preference> result = new ArrayList<>(); result.add(createErrorMessage(activity, R.string.cannot_connect_to_dict_service)); return result; } try { if (!cursor.moveToFirst()) { - final ArrayList<Preference> result = new ArrayList<Preference>(); + final ArrayList<Preference> result = new ArrayList<>(); result.add(createErrorMessage(activity, R.string.no_dictionaries_available)); return result; } else { final String systemLocaleString = Locale.getDefault().toString(); - final TreeMap<String, WordListPreference> prefMap = - new TreeMap<String, WordListPreference>(); + final TreeMap<String, WordListPreference> prefMap = new TreeMap<>(); final int idIndex = cursor.getColumnIndex(MetadataDbHelper.WORDLISTID_COLUMN); final int versionIndex = cursor.getColumnIndex(MetadataDbHelper.VERSION_COLUMN); final int localeIndex = cursor.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN); diff --git a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java index 77f67b8a3..4f0805c5c 100644 --- a/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java +++ b/java/src/com/android/inputmethod/dictionarypack/LocaleUtils.java @@ -175,7 +175,7 @@ public final class LocaleUtils { return saveLocale; } - private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>(); + private static final HashMap<String, Locale> sLocaleCache = new HashMap<>(); /** * Creates a locale from a string specification. diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java index 668eb925b..17dd781d5 100644 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java +++ b/java/src/com/android/inputmethod/dictionarypack/MetadataDbHelper.java @@ -47,7 +47,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { // used to identify the versions for upgrades. This should never change going forward. private static final int METADATA_DATABASE_VERSION_WITH_CLIENTID = 6; // The current database version. - private static final int CURRENT_METADATA_DATABASE_VERSION = 8; + private static final int CURRENT_METADATA_DATABASE_VERSION = 9; private final static long NOT_A_DOWNLOAD_ID = -1; @@ -160,7 +160,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { // this legacy database. New clients should make sure to always pass a client ID so as // to avoid conflicts. final String clientId = null != clientIdOrNull ? clientIdOrNull : ""; - if (null == sInstanceMap) sInstanceMap = new TreeMap<String, MetadataDbHelper>(); + if (null == sInstanceMap) sInstanceMap = new TreeMap<>(); MetadataDbHelper helper = sInstanceMap.get(clientId); if (null == helper) { helper = new MetadataDbHelper(context, clientId); @@ -639,7 +639,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { public static ArrayList<DownloadRecord> getDownloadRecordsForDownloadId(final Context context, final long downloadId) { final SQLiteDatabase defaultDb = getDb(context, ""); - final ArrayList<DownloadRecord> results = new ArrayList<DownloadRecord>(); + final ArrayList<DownloadRecord> results = new ArrayList<>(); final Cursor cursor = defaultDb.query(CLIENT_TABLE_NAME, CLIENT_TABLE_COLUMNS, null, null, null, null, null); try { @@ -923,7 +923,7 @@ public class MetadataDbHelper extends SQLiteOpenHelper { // - Remove the old entry from the table // - Erase the old file // We start by gathering the names of the files we should delete. - final List<String> filenames = new LinkedList<String>(); + final List<String> filenames = new LinkedList<>(); final Cursor c = db.query(METADATA_TABLE_NAME, new String[] { LOCAL_FILENAME_COLUMN }, LOCALE_COLUMN + " = ? AND " + diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java index 63e419871..d66b69050 100644 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java +++ b/java/src/com/android/inputmethod/dictionarypack/MetadataHandler.java @@ -43,7 +43,7 @@ public class MetadataHandler { * @return the constructed list of wordlist metadata. */ private static List<WordListMetadata> makeMetadataObject(final Cursor results) { - final ArrayList<WordListMetadata> buildingMetadata = new ArrayList<WordListMetadata>(); + final ArrayList<WordListMetadata> buildingMetadata = new ArrayList<>(); if (null != results && results.moveToFirst()) { final int localeColumn = results.getColumnIndex(MetadataDbHelper.LOCALE_COLUMN); final int typeColumn = results.getColumnIndex(MetadataDbHelper.TYPE_COLUMN); diff --git a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java index a88173e8e..52290cadc 100644 --- a/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java +++ b/java/src/com/android/inputmethod/dictionarypack/MetadataParser.java @@ -52,7 +52,7 @@ public class MetadataParser { */ private static WordListMetadata parseOneWordList(final JsonReader reader) throws IOException, BadFormatException { - final TreeMap<String, String> arguments = new TreeMap<String, String>(); + final TreeMap<String, String> arguments = new TreeMap<>(); reader.beginObject(); while (reader.hasNext()) { final String name = reader.nextName(); @@ -100,7 +100,7 @@ public class MetadataParser { public static List<WordListMetadata> parseMetadata(final InputStreamReader input) throws IOException, BadFormatException { JsonReader reader = new JsonReader(input); - final ArrayList<WordListMetadata> readInfo = new ArrayList<WordListMetadata>(); + final ArrayList<WordListMetadata> readInfo = new ArrayList<>(); reader.beginArray(); while (reader.hasNext()) { final WordListMetadata thisMetadata = parseOneWordList(reader); diff --git a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java index dcff490db..95a094232 100644 --- a/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java +++ b/java/src/com/android/inputmethod/dictionarypack/UpdateHandler.java @@ -177,7 +177,7 @@ public final class UpdateHandler { */ public static boolean tryUpdate(final Context context, final boolean updateNow) { // TODO: loop through all clients instead of only doing the default one. - final TreeSet<String> uris = new TreeSet<String>(); + final TreeSet<String> uris = new TreeSet<>(); final Cursor cursor = MetadataDbHelper.queryClientIds(context); if (null == cursor) return false; try { @@ -557,7 +557,7 @@ public final class UpdateHandler { // Instantiation of a parameterized type is not possible in Java, so it's not possible to // return the same type of list that was passed - probably the same reason why Collections // does not do it. So we need to decide statically which concrete type to return. - return new LinkedList<T>(src); + return new LinkedList<>(src); } /** @@ -740,10 +740,10 @@ public final class UpdateHandler { final ActionBatch actions = new ActionBatch(); // Upgrade existing word lists DebugLogUtils.l("Comparing dictionaries"); - final Set<String> wordListIds = new TreeSet<String>(); + final Set<String> wordListIds = new TreeSet<>(); // TODO: Can these be null? - if (null == from) from = new ArrayList<WordListMetadata>(); - if (null == to) to = new ArrayList<WordListMetadata>(); + if (null == from) from = new ArrayList<>(); + if (null == to) to = new ArrayList<>(); for (WordListMetadata wlData : from) wordListIds.add(wlData.mId); for (WordListMetadata wlData : to) wordListIds.add(wlData.mId); for (String id : wordListIds) { diff --git a/java/src/com/android/inputmethod/event/CombinerChain.java b/java/src/com/android/inputmethod/event/CombinerChain.java index 9e7f04d4f..61bc11b39 100644 --- a/java/src/com/android/inputmethod/event/CombinerChain.java +++ b/java/src/com/android/inputmethod/event/CombinerChain.java @@ -20,7 +20,6 @@ import android.text.SpannableStringBuilder; import android.text.TextUtils; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.HashMap; @@ -44,8 +43,8 @@ public class CombinerChain { private SpannableStringBuilder mStateFeedback; private final ArrayList<Combiner> mCombiners; - private static final HashMap<String, Class> IMPLEMENTED_COMBINERS - = new HashMap<String, Class>(); + private static final HashMap<String, Class<? extends Combiner>> IMPLEMENTED_COMBINERS = + new HashMap<>(); static { IMPLEMENTED_COMBINERS.put("MyanmarReordering", MyanmarReordering.class); } @@ -63,7 +62,7 @@ public class CombinerChain { * @param combinerList A list of combiners to be applied in order. */ public CombinerChain(final String initialText, final Combiner... combinerList) { - mCombiners = CollectionUtils.newArrayList(); + mCombiners = new ArrayList<>(); // The dead key combiner is always active, and always first mCombiners.add(new DeadKeyCombiner()); for (final Combiner combiner : combinerList) { @@ -87,7 +86,7 @@ public class CombinerChain { * @param newEvent the new event to process */ public void processEvent(final ArrayList<Event> previousEvents, final Event newEvent) { - final ArrayList<Event> modifiablePreviousEvents = new ArrayList<Event>(previousEvents); + final ArrayList<Event> modifiablePreviousEvents = new ArrayList<>(previousEvents); Event event = newEvent; for (final Combiner combiner : mCombiners) { // A combiner can never return more than one event; it can return several @@ -136,12 +135,13 @@ public class CombinerChain { final Combiner[] combiners = new Combiner[combinerDescriptors.length]; int i = 0; for (final String combinerDescriptor : combinerDescriptors) { - final Class combinerClass = IMPLEMENTED_COMBINERS.get(combinerDescriptor); + final Class<? extends Combiner> combinerClass = + IMPLEMENTED_COMBINERS.get(combinerDescriptor); if (null == combinerClass) { throw new RuntimeException("Unknown combiner descriptor: " + combinerDescriptor); } try { - combiners[i++] = (Combiner)combinerClass.newInstance(); + combiners[i++] = combinerClass.newInstance(); } catch (InstantiationException e) { throw new RuntimeException("Unable to instantiate combiner: " + combinerDescriptor, e); diff --git a/java/src/com/android/inputmethod/event/MyanmarReordering.java b/java/src/com/android/inputmethod/event/MyanmarReordering.java index da0228bd2..32919932d 100644 --- a/java/src/com/android/inputmethod/event/MyanmarReordering.java +++ b/java/src/com/android/inputmethod/event/MyanmarReordering.java @@ -17,7 +17,6 @@ package com.android.inputmethod.event; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.Arrays; @@ -32,7 +31,7 @@ public class MyanmarReordering implements Combiner { // U+200B ZERO WIDTH SPACE private final static int ZERO_WIDTH_NON_JOINER = 0x200B; // should be 0x200C - private final ArrayList<Event> mCurrentEvents = CollectionUtils.newArrayList(); + private final ArrayList<Event> mCurrentEvents = new ArrayList<>(); // List of consonants : // U+1000 MYANMAR LETTER KA diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index cf68c565d..89a60cc1d 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -467,15 +467,24 @@ public class Key implements Comparable<Key> { @Override public String toString() { - final String label; - if (StringUtils.codePointCount(mLabel) == 1 && mLabel.codePointAt(0) == mCode) { - label = ""; - } else { - label = "/" + mLabel; + return toShortString() + " " + getX() + "," + getY() + " " + getWidth() + "x" + getHeight(); + } + + public String toShortString() { + final int code = getCode(); + if (code == Constants.CODE_OUTPUT_TEXT) { + return getOutputText(); } - return String.format(Locale.ROOT, "%s%s %d,%d %dx%d %s/%s/%s", - Constants.printableCode(mCode), label, mX, mY, mWidth, mHeight, mHintLabel, - KeyboardIconsSet.getIconName(mIconId), backgroundName(mBackgroundType)); + return Constants.printableCode(code); + } + + public String toLongString() { + final int iconId = getIconId(); + final String topVisual = (iconId == KeyboardIconsSet.ICON_UNDEFINED) + ? KeyboardIconsSet.PREFIX_ICON + KeyboardIconsSet.getIconName(iconId) : getLabel(); + final String hintLabel = getHintLabel(); + final String visual = (hintLabel == null) ? topVisual : topVisual + "^" + hintLabel; + return toString() + " " + visual + "/" + backgroundName(mBackgroundType); } private static String backgroundName(final int backgroundType) { diff --git a/java/src/com/android/inputmethod/keyboard/Keyboard.java b/java/src/com/android/inputmethod/keyboard/Keyboard.java index f646a03c2..85dfea4e7 100644 --- a/java/src/com/android/inputmethod/keyboard/Keyboard.java +++ b/java/src/com/android/inputmethod/keyboard/Keyboard.java @@ -22,9 +22,9 @@ import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -83,7 +83,7 @@ public class Keyboard { public final List<Key> mAltCodeKeysWhileTyping; public final KeyboardIconsSet mIconsSet; - private final SparseArray<Key> mKeyCache = CollectionUtils.newSparseArray(); + private final SparseArray<Key> mKeyCache = new SparseArray<>(); private final ProximityInfo mProximityInfo; private final boolean mProximityCharsCorrectionEnabled; @@ -103,8 +103,7 @@ public class Keyboard { mTopPadding = params.mTopPadding; mVerticalGap = params.mVerticalGap; - mSortedKeys = Collections.unmodifiableList( - CollectionUtils.newArrayList(params.mSortedKeys)); + mSortedKeys = Collections.unmodifiableList(new ArrayList<>(params.mSortedKeys)); mShiftKeys = Collections.unmodifiableList(params.mShiftKeys); mAltCodeKeysWhileTyping = Collections.unmodifiableList(params.mAltCodeKeysWhileTyping); mIconsSet = params.mIconsSet; @@ -159,7 +158,7 @@ public class Keyboard { /** * Return the sorted list of keys of this keyboard. * The keys are sorted from top-left to bottom-right order. - * The list may contain {@link Spacer} object as well. + * The list may contain {@link Key.Spacer} object as well. * @return the sorted unmodifiable list of {@link Key}s of this keyboard. */ public List<Key> getSortedKeys() { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardId.java b/java/src/com/android/inputmethod/keyboard/KeyboardId.java index 93a55fe6a..3c1167538 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardId.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardId.java @@ -70,7 +70,6 @@ public final class KeyboardId { public final int mElementId; public final EditorInfo mEditorInfo; public final boolean mClobberSettingsKey; - public final boolean mSupportsSwitchingToShortcutIme; public final boolean mLanguageSwitchKeyEnabled; public final String mCustomActionLabel; public final boolean mHasShortcutKey; @@ -86,11 +85,10 @@ public final class KeyboardId { mElementId = elementId; mEditorInfo = params.mEditorInfo; mClobberSettingsKey = params.mNoSettingsKey; - mSupportsSwitchingToShortcutIme = params.mSupportsSwitchingToShortcutIme; mLanguageSwitchKeyEnabled = params.mLanguageSwitchKeyEnabled; mCustomActionLabel = (mEditorInfo.actionLabel != null) ? mEditorInfo.actionLabel.toString() : null; - mHasShortcutKey = mSupportsSwitchingToShortcutIme && params.mShowsVoiceInputKey; + mHasShortcutKey = params.mVoiceInputKeyEnabled; mHashCode = computeHashCode(this); } @@ -103,7 +101,6 @@ public final class KeyboardId { id.mHeight, id.passwordInput(), id.mClobberSettingsKey, - id.mSupportsSwitchingToShortcutIme, id.mHasShortcutKey, id.mLanguageSwitchKeyEnabled, id.isMultiLine(), @@ -124,7 +121,6 @@ public final class KeyboardId { && other.mHeight == mHeight && other.passwordInput() == passwordInput() && other.mClobberSettingsKey == mClobberSettingsKey - && other.mSupportsSwitchingToShortcutIme == mSupportsSwitchingToShortcutIme && other.mHasShortcutKey == mHasShortcutKey && other.mLanguageSwitchKeyEnabled == mLanguageSwitchKeyEnabled && other.isMultiLine() == isMultiLine() @@ -179,7 +175,7 @@ public final class KeyboardId { @Override public String toString() { - return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s]", + return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s]", elementIdToName(mElementId), mLocale, mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), mWidth, mHeight, @@ -189,7 +185,6 @@ public final class KeyboardId { (navigatePrevious() ? " navigatePrevious" : ""), (mClobberSettingsKey ? " clobberSettingsKey" : ""), (passwordInput() ? " passwordInput" : ""), - (mSupportsSwitchingToShortcutIme ? " supportsSwitchingToShortcutIme" : ""), (mHasShortcutKey ? " hasShortcutKey" : ""), (mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""), (isMultiLine() ? " isMultiLine" : "") diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index cde5091c4..3e5cfc11a 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -17,8 +17,6 @@ package com.android.inputmethod.keyboard; import static com.android.inputmethod.latin.Constants.ImeOption.FORCE_ASCII; -import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE; -import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT; import static com.android.inputmethod.latin.Constants.ImeOption.NO_SETTINGS_KEY; import android.content.Context; @@ -41,7 +39,6 @@ import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.InputTypeUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import com.android.inputmethod.latin.utils.XmlParseUtils; @@ -81,7 +78,7 @@ public final class KeyboardLayoutSet { // them from disappearing from sKeyboardCache. private static final Keyboard[] sForcibleKeyboardCache = new Keyboard[FORCIBLE_CACHE_SIZE]; private static final HashMap<KeyboardId, SoftReference<Keyboard>> sKeyboardCache = - CollectionUtils.newHashMap(); + new HashMap<>(); private static final KeysCache sKeysCache = new KeysCache(); @SuppressWarnings("serial") @@ -103,12 +100,11 @@ public final class KeyboardLayoutSet { public static final class Params { String mKeyboardLayoutSetName; int mMode; - EditorInfo mEditorInfo; boolean mDisableTouchPositionCorrectionDataForTest; + // TODO: Use {@link InputAttributes} instead of these variables. + EditorInfo mEditorInfo; boolean mIsPasswordField; - boolean mSupportsSwitchingToShortcutIme; - boolean mShowsVoiceInputKey; - boolean mNoMicrophoneKey; + boolean mVoiceInputKeyEnabled; boolean mNoSettingsKey; boolean mLanguageSwitchKeyEnabled; InputMethodSubtype mSubtype; @@ -117,7 +113,7 @@ public final class KeyboardLayoutSet { int mKeyboardHeight; // Sparse array of KeyboardLayoutSet element parameters indexed by element's id. final SparseArray<ElementParams> mKeyboardLayoutSetElementIdToParamsMap = - CollectionUtils.newSparseArray(); + new SparseArray<>(); } public static void clearKeyboardCache() { @@ -181,7 +177,7 @@ public final class KeyboardLayoutSet { } final KeyboardBuilder<KeyboardParams> builder = - new KeyboardBuilder<KeyboardParams>(mContext, new KeyboardParams()); + new KeyboardBuilder<>(mContext, new KeyboardParams()); if (id.isAlphabetKeyboard()) { builder.setAutoGenerate(sKeysCache); } @@ -192,7 +188,7 @@ public final class KeyboardLayoutSet { } builder.setProximityCharsCorrectionEnabled(elementParams.mProximityCharsCorrectionEnabled); final Keyboard keyboard = builder.build(); - sKeyboardCache.put(id, new SoftReference<Keyboard>(keyboard)); + sKeyboardCache.put(id, new SoftReference<>(keyboard)); if ((id.mElementId == KeyboardId.ELEMENT_ALPHABET || id.mElementId == KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) && !mParams.mIsSpellChecker) { @@ -229,14 +225,9 @@ public final class KeyboardLayoutSet { final EditorInfo editorInfo = (ei != null) ? ei : EMPTY_EDITOR_INFO; params.mMode = getKeyboardMode(editorInfo); + // TODO: Consolidate those with {@link InputAttributes}. params.mEditorInfo = editorInfo; params.mIsPasswordField = InputTypeUtils.isPasswordInputType(editorInfo.inputType); - @SuppressWarnings("deprecation") - final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions( - null, NO_MICROPHONE_COMPAT, editorInfo); - params.mNoMicrophoneKey = InputAttributes.inPrivateImeOptions( - mPackageName, NO_MICROPHONE, editorInfo) - || deprecatedNoMicrophone; params.mNoSettingsKey = InputAttributes.inPrivateImeOptions( mPackageName, NO_SETTINGS_KEY, editorInfo); } @@ -249,6 +240,7 @@ public final class KeyboardLayoutSet { public Builder setSubtype(final InputMethodSubtype subtype) { final boolean asciiCapable = InputMethodSubtypeCompatUtils.isAsciiCapable(subtype); + // TODO: Consolidate with {@link InputAttributes}. @SuppressWarnings("deprecation") final boolean deprecatedForceAscii = InputAttributes.inPrivateImeOptions( mPackageName, FORCE_ASCII, mParams.mEditorInfo); @@ -269,12 +261,13 @@ public final class KeyboardLayoutSet { return this; } - public Builder setOptions(final boolean isShortcutImeEnabled, - final boolean showsVoiceInputKey, final boolean languageSwitchKeyEnabled) { - mParams.mSupportsSwitchingToShortcutIme = - isShortcutImeEnabled && !mParams.mNoMicrophoneKey && !mParams.mIsPasswordField; - mParams.mShowsVoiceInputKey = showsVoiceInputKey; - mParams.mLanguageSwitchKeyEnabled = languageSwitchKeyEnabled; + public Builder setVoiceInputKeyEnabled(final boolean enabled) { + mParams.mVoiceInputKeyEnabled = enabled; + return this; + } + + public Builder setLanguageSwitchKeyEnabled(final boolean enabled) { + mParams.mLanguageSwitchKeyEnabled = enabled; return this; } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 61d51d1c9..6aeff189f 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java @@ -33,7 +33,6 @@ import com.android.inputmethod.keyboard.internal.KeyboardState; import com.android.inputmethod.keyboard.internal.KeyboardTextsSet; import com.android.inputmethod.latin.InputView; import com.android.inputmethod.latin.LatinIME; -import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.SubtypeSwitcher; @@ -116,10 +115,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res); builder.setKeyboardGeometry(keyboardWidth, keyboardHeight); builder.setSubtype(mSubtypeSwitcher.getCurrentSubtype()); - builder.setOptions( - mSubtypeSwitcher.isShortcutImeEnabled(), - settingsValues.mShowsVoiceInputKey, - mLatinIME.shouldShowLanguageSwitchKey()); + builder.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey); + builder.setLanguageSwitchKeyEnabled(mLatinIME.shouldShowLanguageSwitchKey()); mKeyboardLayoutSet = builder.build(); mCurrentSettingsValues = settingsValues; try { @@ -127,7 +124,6 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { mKeyboardTextsSet.setLocale(mSubtypeSwitcher.getCurrentSubtypeLocale(), mThemeContext); } catch (KeyboardLayoutSetException e) { Log.w(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); - LatinImeLogger.logOnException(e.mKeyboardId.toString(), e.getCause()); return; } } diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java index 1f14aa2e3..0ea9c742f 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java @@ -21,46 +21,43 @@ import android.os.Build; import android.os.Build.VERSION_CODES; import android.util.Log; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.R; import java.util.Arrays; -import java.util.Comparator; -public final class KeyboardTheme { +public final class KeyboardTheme implements Comparable<KeyboardTheme> { private static final String TAG = KeyboardTheme.class.getSimpleName(); static final String KLP_KEYBOARD_THEME_KEY = "pref_keyboard_layout_20110916"; static final String LXX_KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509"; - static final int THEME_ID_ICS = 0; - static final int THEME_ID_KLP = 2; - static final int THEME_ID_LXX_DARK = 3; - static final int DEFAULT_THEME_ID = THEME_ID_KLP; + public static final int THEME_ID_ICS = 0; + public static final int THEME_ID_KLP = 2; + public static final int THEME_ID_LXX_DARK = 3; + public static final int DEFAULT_THEME_ID = THEME_ID_KLP; private static final KeyboardTheme[] KEYBOARD_THEMES = { new KeyboardTheme(THEME_ID_ICS, R.style.KeyboardTheme_ICS, + // This has never been selected because we support ICS or later. VERSION_CODES.BASE), new KeyboardTheme(THEME_ID_KLP, R.style.KeyboardTheme_KLP, + // Default theme for ICS, JB, and KLP. VERSION_CODES.ICE_CREAM_SANDWICH), new KeyboardTheme(THEME_ID_LXX_DARK, R.style.KeyboardTheme_LXX_Dark, + // Default theme for LXX. // TODO: Update this constant once the *next* version becomes available. VERSION_CODES.CUR_DEVELOPMENT), }; + static { // Sort {@link #KEYBOARD_THEME} by descending order of {@link #mMinApiVersion}. - Arrays.sort(KEYBOARD_THEMES, new Comparator<KeyboardTheme>() { - @Override - public int compare(final KeyboardTheme lhs, final KeyboardTheme rhs) { - if (lhs.mMinApiVersion > rhs.mMinApiVersion) return -1; - if (lhs.mMinApiVersion < rhs.mMinApiVersion) return 1; - return 0; - } - }); + Arrays.sort(KEYBOARD_THEMES); } public final int mThemeId; public final int mStyleId; - final int mMinApiVersion; + private final int mMinApiVersion; // Note: The themeId should be aligned with "themeId" attribute of Keyboard style // in values/themes-<style>.xml. @@ -71,6 +68,13 @@ public final class KeyboardTheme { } @Override + public int compareTo(final KeyboardTheme rhs) { + if (mMinApiVersion > rhs.mMinApiVersion) return -1; + if (mMinApiVersion < rhs.mMinApiVersion) return 1; + return 0; + } + + @Override public boolean equals(final Object o) { if (o == this) return true; return (o instanceof KeyboardTheme) && ((KeyboardTheme)o).mThemeId == mThemeId; @@ -81,21 +85,8 @@ public final class KeyboardTheme { return mThemeId; } - // TODO: This method should be removed when {@link LatinImeLogger} is removed. - public int getCompatibleThemeIdForLogging() { - switch (mThemeId) { - case THEME_ID_ICS: - return 5; - case THEME_ID_KLP: - return 9; - case THEME_ID_LXX_DARK: - return 10; - default: // Invalid theme - return -1; - } - } - - private static KeyboardTheme searchKeyboardThemeById(final int themeId) { + @UsedForTesting + static KeyboardTheme searchKeyboardThemeById(final int themeId) { // TODO: This search algorithm isn't optimal if there are many themes. for (final KeyboardTheme theme : KEYBOARD_THEMES) { if (theme.mThemeId == themeId) { @@ -114,6 +105,7 @@ public final class KeyboardTheme { return sdkVersion; } + @UsedForTesting static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs, final int sdkVersion) { final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null); @@ -148,6 +140,7 @@ public final class KeyboardTheme { saveKeyboardThemeId(themeIdString, prefs, getSdkVersion()); } + @UsedForTesting static String getPreferenceKey(final int sdkVersion) { if (sdkVersion <= VERSION_CODES.KITKAT) { return KLP_KEYBOARD_THEME_KEY; @@ -155,8 +148,9 @@ public final class KeyboardTheme { return LXX_KEYBOARD_THEME_KEY; } - static void saveKeyboardThemeId(final String themeIdString, final SharedPreferences prefs, - final int sdkVersion) { + @UsedForTesting + static void saveKeyboardThemeId(final String themeIdString, + final SharedPreferences prefs, final int sdkVersion) { final String prefKey = getPreferenceKey(sdkVersion); prefs.edit().putString(prefKey, themeIdString).apply(); } @@ -165,6 +159,7 @@ public final class KeyboardTheme { return getKeyboardTheme(prefs, getSdkVersion()); } + @UsedForTesting static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion) { final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null); if (lxxThemeIdString == null) { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardView.java b/java/src/com/android/inputmethod/keyboard/KeyboardView.java index edfc5fded..c4ca1c495 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardView.java @@ -35,12 +35,8 @@ import android.view.View; import com.android.inputmethod.keyboard.internal.KeyDrawParams; import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.define.ProductionFlag; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.TypefaceUtils; -import com.android.inputmethod.research.ResearchLogger; import java.util.HashSet; @@ -110,7 +106,7 @@ public class KeyboardView extends View { /** True if all keys should be drawn */ private boolean mInvalidateAllKeys; /** The keys that should be drawn */ - private final HashSet<Key> mInvalidatedKeys = CollectionUtils.newHashSet(); + private final HashSet<Key> mInvalidatedKeys = new HashSet<>(); /** The working rectangle variable */ private final Rect mWorkingRect = new Rect(); /** The keyboard bitmap buffer for faster updates */ @@ -188,7 +184,6 @@ public class KeyboardView extends View { */ public void setKeyboard(final Keyboard keyboard) { mKeyboard = keyboard; - LatinImeLogger.onSetKeyboard(keyboard); final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; mKeyDrawParams.updateParams(keyHeight, mKeyVisualAttributes); mKeyDrawParams.updateParams(keyHeight, keyboard.mKeyVisualAttributes); @@ -318,13 +313,6 @@ public class KeyboardView extends View { } } - // Research Logging (Development Only Diagnostics) indicator. - // TODO: Reimplement using a keyboard background image specific to the ResearchLogger, - // and remove this call. - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().paintIndicator(this, paint, canvas, width, height); - } - mInvalidatedKeys.clear(); mInvalidateAllKeys = false; } @@ -363,9 +351,6 @@ public class KeyboardView extends View { } canvas.translate(bgX, bgY); background.draw(canvas); - if (LatinImeLogger.sVISUALDEBUG) { - drawRectangle(canvas, 0.0f, 0.0f, bgWidth, bgHeight, 0x80c00000, new Paint()); - } canvas.translate(-bgX, -bgY); } @@ -377,10 +362,6 @@ public class KeyboardView extends View { final float centerX = keyWidth * 0.5f; final float centerY = keyHeight * 0.5f; - if (LatinImeLogger.sVISUALDEBUG) { - drawRectangle(canvas, 0.0f, 0.0f, keyWidth, keyHeight, 0x800000c0, new Paint()); - } - // Draw key label. final Drawable icon = key.getIcon(mKeyboard.mIconsSet, params.mAnimAlpha); float positionX = centerX; @@ -462,12 +443,6 @@ public class KeyboardView extends View { drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); } } - - if (LatinImeLogger.sVISUALDEBUG) { - final Paint line = new Paint(); - drawHorizontalLine(canvas, baseline, keyWidth, 0xc0008000, line); - drawVerticalLine(canvas, positionX, keyHeight, 0xc0800080, line); - } } // Draw hint label. @@ -507,12 +482,6 @@ public class KeyboardView extends View { paint.setTextAlign(Align.CENTER); } canvas.drawText(hintLabel, 0, hintLabel.length(), hintX, hintY + adjustmentY, paint); - - if (LatinImeLogger.sVISUALDEBUG) { - final Paint line = new Paint(); - drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line); - drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line); - } } // Draw key icon. @@ -524,26 +493,17 @@ public class KeyboardView extends View { iconWidth = Math.min(icon.getIntrinsicWidth(), keyWidth); } final int iconHeight = icon.getIntrinsicHeight(); - final int iconX, alignX; final int iconY = key.isAlignButtom() ? keyHeight - iconHeight : (keyHeight - iconHeight) / 2; + final int iconX; if (key.isAlignLeft()) { iconX = mKeyLabelHorizontalPadding; - alignX = iconX; } else if (key.isAlignRight()) { iconX = keyWidth - mKeyLabelHorizontalPadding - iconWidth; - alignX = iconX + iconWidth; } else { // Align center iconX = (keyWidth - iconWidth) / 2; - alignX = iconX + iconWidth / 2; } drawIcon(canvas, icon, iconX, iconY, iconWidth, iconHeight); - - if (LatinImeLogger.sVISUALDEBUG) { - final Paint line = new Paint(); - drawVerticalLine(canvas, alignX, keyHeight, 0xc0800080, line); - drawRectangle(canvas, iconX, iconY, iconWidth, iconHeight, 0x80c00000, line); - } } if (key.hasPopupHint() && key.getMoreKeys() != null) { @@ -565,12 +525,6 @@ public class KeyboardView extends View { - TypefaceUtils.getReferenceCharWidth(paint) / 2.0f; final float hintY = keyHeight - mKeyPopupHintLetterPadding; canvas.drawText(POPUP_HINT_CHAR, hintX, hintY, paint); - - if (LatinImeLogger.sVISUALDEBUG) { - final Paint line = new Paint(); - drawHorizontalLine(canvas, (int)hintY, keyWidth, 0xc0808000, line); - drawVerticalLine(canvas, (int)hintX, keyHeight, 0xc0808000, line); - } } protected static void drawIcon(final Canvas canvas, final Drawable icon, final int x, @@ -581,32 +535,6 @@ public class KeyboardView extends View { canvas.translate(-x, -y); } - private static void drawHorizontalLine(final Canvas canvas, final float y, final float w, - final int color, final Paint paint) { - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(1.0f); - paint.setColor(color); - canvas.drawLine(0.0f, y, w, y, paint); - } - - private static void drawVerticalLine(final Canvas canvas, final float x, final float h, - final int color, final Paint paint) { - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(1.0f); - paint.setColor(color); - canvas.drawLine(x, 0.0f, x, h, paint); - } - - private static void drawRectangle(final Canvas canvas, final float x, final float y, - final float w, final float h, final int color, final Paint paint) { - paint.setStyle(Paint.Style.STROKE); - paint.setStrokeWidth(1.0f); - paint.setColor(color); - canvas.translate(x, y); - canvas.drawRect(0.0f, 0.0f, w, h, paint); - canvas.translate(-x, -y); - } - public Paint newLabelPaint(final Key key) { final Paint paint = new Paint(); paint.setAntiAlias(true); diff --git a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java index 86036ccc1..bcd0cd848 100644 --- a/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MainKeyboardView.java @@ -52,17 +52,12 @@ import com.android.inputmethod.keyboard.internal.NonDistinctMultitouchHelper; import com.android.inputmethod.keyboard.internal.SlidingKeyInputDrawingPreview; import com.android.inputmethod.keyboard.internal.TimerHandler; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.settings.DebugSettings; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.SpacebarLanguageUtils; import com.android.inputmethod.latin.utils.TypefaceUtils; -import com.android.inputmethod.latin.utils.UsabilityStudyLogUtils; -import com.android.inputmethod.research.ResearchLogger; import java.util.WeakHashMap; @@ -150,8 +145,7 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private final Paint mBackgroundDimAlphaPaint = new Paint(); private boolean mNeedsToDimEntireKeyboard; private final View mMoreKeysKeyboardContainer; - private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = - CollectionUtils.newWeakHashMap(); + private final WeakHashMap<Key, Keyboard> mMoreKeysKeyboardCache = new WeakHashMap<>(); private final boolean mConfigShowMoreKeysKeyboardAtTouchedPoint; // More keys panel (used by both more keys keyboard and more suggestions view) // TODO: Consider extending to support multiple more keys panels @@ -167,10 +161,9 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack private final TimerHandler mKeyTimerHandler; private final int mLanguageOnSpacebarHorizontalMargin; - private final DrawingHandler mDrawingHandler = - new DrawingHandler(this); + private final DrawingHandler mDrawingHandler = new DrawingHandler(this); - private final MainKeyboardAccessibilityDelegate mAccessibilityDelegate; + private MainKeyboardAccessibilityDelegate mAccessibilityDelegate; public MainKeyboardView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.mainKeyboardViewStyle); @@ -268,8 +261,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack mLanguageOnSpacebarHorizontalMargin = (int)getResources().getDimension( R.dimen.config_language_on_spacebar_horizontal_margin); - - mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this, mKeyDetector); } @Override @@ -389,12 +380,15 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack mSpaceKey = keyboard.getKey(Constants.CODE_SPACE); final int keyHeight = keyboard.mMostCommonKeyHeight - keyboard.mVerticalGap; mLanguageOnSpacebarTextSize = keyHeight * mLanguageOnSpacebarTextRatio; - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - final int orientation = getContext().getResources().getConfiguration().orientation; - ResearchLogger.mainKeyboardView_setKeyboard(keyboard, orientation); - } - mAccessibilityDelegate.setKeyboard(keyboard); + if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { + if (mAccessibilityDelegate == null) { + mAccessibilityDelegate = new MainKeyboardAccessibilityDelegate(this, mKeyDetector); + } + mAccessibilityDelegate.setKeyboard(keyboard); + } else { + mAccessibilityDelegate = null; + } } /** @@ -554,24 +548,12 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack protected void onAttachedToWindow() { super.onAttachedToWindow(); installPreviewPlacerView(); - // Notify the ResearchLogger (development only diagnostics) that the keyboard view has - // been attached. This is needed to properly show the splash screen, which requires that - // the window token of the KeyboardView be non-null. - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().mainKeyboardView_onAttachedToWindow(this); - } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mDrawingPreviewPlacerView.removeAllViews(); - // Notify the ResearchLogger (development only diagnostics) that the keyboard view has - // been detached. This is needed to invalidate the reference of {@link MainKeyboardView} - // to null. - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().mainKeyboardView_onDetachedFromWindow(); - } } private MoreKeysPanel onCreateMoreKeysPanel(final Key key, final Context context) { @@ -607,9 +589,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack if (key == null) { return; } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.mainKeyboardView_onLongPress(); - } final KeyboardActionListener listener = mKeyboardActionListener; if (key.hasNoPanelAutoMoreKey()) { final int moreKeyCode = key.getMoreKeys()[0].mCode; @@ -722,14 +701,6 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } public boolean processMotionEvent(final MotionEvent me) { - if (LatinImeLogger.sUsabilityStudy) { - UsabilityStudyLogUtils.writeMotionEvent(me); - } - // Currently the same "move" event is being logged twice. - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.mainKeyboardView_processMotionEvent(me); - } - final int index = me.getActionIndex(); final int id = me.getPointerId(index); final PointerTracker tracker = PointerTracker.getPointerTracker(id); @@ -759,25 +730,22 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } public void onHideWindow() { - if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { - mAccessibilityDelegate.onHideWindow(); + final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; + if (accessibilityDelegate != null) { + accessibilityDelegate.onHideWindow(); } } /** - * Receives hover events from the input framework. - * - * @param event The motion event to be dispatched. - * @return {@code true} if the event was handled by the view, {@code false} - * otherwise + * {@inheritDoc} */ @Override - public boolean dispatchHoverEvent(final MotionEvent event) { - if (!AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - // Reflection doesn't support calling superclass methods. - return false; + public boolean onHoverEvent(final MotionEvent event) { + final MainKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; + if (accessibilityDelegate != null) { + return accessibilityDelegate.onHoverEvent(event); } - return mAccessibilityDelegate.dispatchHoverEvent(event); + return super.onHoverEvent(event); } public void updateShortcutKey(final boolean available) { diff --git a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java index 4a2b37e4c..0f575d30c 100644 --- a/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/MoreKeysKeyboardView.java @@ -17,12 +17,13 @@ package com.android.inputmethod.keyboard; import android.content.Context; -import android.content.res.Resources; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import com.android.inputmethod.accessibility.AccessibilityUtils; +import com.android.inputmethod.accessibility.MoreKeysKeyboardAccessibilityDelegate; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.utils.CoordinateUtils; @@ -34,7 +35,7 @@ import com.android.inputmethod.latin.utils.CoordinateUtils; public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel { private final int[] mCoordinates = CoordinateUtils.newInstance(); - protected final KeyDetector mKeyDetector; + protected KeyDetector mKeyDetector; private Controller mController = EMPTY_CONTROLLER; protected KeyboardActionListener mListener; private int mOriginX; @@ -43,6 +44,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel private int mActivePointerId; + protected MoreKeysKeyboardAccessibilityDelegate mAccessibilityDelegate; + public MoreKeysKeyboardView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.moreKeysKeyboardViewStyle); } @@ -50,10 +53,8 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel public MoreKeysKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); - - final Resources res = context.getResources(); - mKeyDetector = new MoreKeysDetector( - res.getDimension(R.dimen.config_more_keys_keyboard_slide_allowance)); + mKeyDetector = new MoreKeysDetector(getResources().getDimension( + R.dimen.config_more_keys_keyboard_slide_allowance)); } @Override @@ -71,8 +72,26 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel @Override public void setKeyboard(final Keyboard keyboard) { super.setKeyboard(keyboard); - mKeyDetector.setKeyboard(keyboard, -getPaddingLeft(), - -getPaddingTop() + getVerticalCorrection()); + if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { + // With accessibility mode on, any hover event outside {@link MoreKeysKeyboardView} is + // discarded at {@link InputView#dispatchHoverEvent(MotionEvent)}. Because only a hover + // event that is on this view is dispatched by the platform, we should use a + // {@link KeyDetector} that has no sliding allowance and no hysteresis. + if (mAccessibilityDelegate == null) { + mKeyDetector = new KeyDetector(); + mAccessibilityDelegate = new MoreKeysKeyboardAccessibilityDelegate( + this, mKeyDetector); + mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_keys_keyboard); + mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_keys_keyboard); + } + mAccessibilityDelegate.setKeyboard(keyboard); + } else { + mKeyDetector = new MoreKeysDetector(getResources().getDimension( + R.dimen.config_more_keys_keyboard_slide_allowance)); + mAccessibilityDelegate = null; + } + mKeyDetector.setKeyboard( + keyboard, -getPaddingLeft(), -getPaddingTop() + getVerticalCorrection()); } @Override @@ -98,6 +117,10 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel mOriginX = x + container.getPaddingLeft(); mOriginY = y + container.getPaddingTop(); controller.onShowMoreKeysPanel(this); + final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; + if (accessibilityDelegate != null) { + accessibilityDelegate.onShowMoreKeysKeyboard(); + } } /** @@ -110,25 +133,31 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel @Override public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) { mActivePointerId = pointerId; - onMoveKeyInternal(x, y, pointerId); + mCurrentKey = detectKey(x, y, pointerId); } @Override - public void onMoveEvent(int x, int y, final int pointerId, long eventTime) { + public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) { if (mActivePointerId != pointerId) { return; } final boolean hasOldKey = (mCurrentKey != null); - onMoveKeyInternal(x, y, pointerId); + mCurrentKey = detectKey(x, y, pointerId); if (hasOldKey && mCurrentKey == null) { - // If the pointer has moved too far away from any target then cancel the panel. + // A more keys keyboard is canceled when detecting no key. mController.onCancelMoreKeysPanel(); } } @Override public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) { - if (mCurrentKey != null && mActivePointerId == pointerId) { + if (mActivePointerId != pointerId) { + return; + } + // Calling {@link #detectKey(int,int,int)} here is harmless because the last move event and + // the following up event share the same coordinates. + mCurrentKey = detectKey(x, y, pointerId); + if (mCurrentKey != null) { updateReleaseKeyGraphics(mCurrentKey); onKeyInput(mCurrentKey, x, y); mCurrentKey = null; @@ -152,23 +181,22 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel } } - private void onMoveKeyInternal(int x, int y, int pointerId) { - if (mActivePointerId != pointerId) { - // Ignore old pointers when newer pointer is active. - return; - } + private Key detectKey(int x, int y, int pointerId) { final Key oldKey = mCurrentKey; final Key newKey = mKeyDetector.detectHitKey(x, y); - if (newKey != oldKey) { - mCurrentKey = newKey; - invalidateKey(mCurrentKey); - if (oldKey != null) { - updateReleaseKeyGraphics(oldKey); - } - if (newKey != null) { - updatePressKeyGraphics(newKey); - } + if (newKey == oldKey) { + return newKey; } + // A new key is detected. + if (oldKey != null) { + updateReleaseKeyGraphics(oldKey); + invalidateKey(oldKey); + } + if (newKey != null) { + updatePressKeyGraphics(newKey); + invalidateKey(newKey); + } + return newKey; } private void updateReleaseKeyGraphics(final Key key) { @@ -223,6 +251,18 @@ public class MoreKeysKeyboardView extends KeyboardView implements MoreKeysPanel return true; } + /** + * {@inheritDoc} + */ + @Override + public boolean onHoverEvent(final MotionEvent event) { + final MoreKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; + if (accessibilityDelegate != null) { + return accessibilityDelegate.onHoverEvent(event); + } + return super.onHoverEvent(event); + } + private View getContainerView() { return (View)getParent(); } diff --git a/java/src/com/android/inputmethod/keyboard/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 4777166ea..b6905bc1c 100644 --- a/java/src/com/android/inputmethod/keyboard/PointerTracker.java +++ b/java/src/com/android/inputmethod/keyboard/PointerTracker.java @@ -35,12 +35,9 @@ import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.ResourceUtils; -import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayList; @@ -144,7 +141,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, // TODO: Device specific parameter would be better for device specific hack? private static final float PHANTOM_SUDDEN_MOVE_THRESHOLD = 0.25f; // in keyWidth - private static final ArrayList<PointerTracker> sTrackers = CollectionUtils.newArrayList(); + private static final ArrayList<PointerTracker> sTrackers = new ArrayList<>(); private static final PointerTrackerQueue sPointerTrackerQueue = new PointerTrackerQueue(); public final int mPointerId; @@ -336,10 +333,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element, output, ignoreModifierKey ? " ignoreModifier" : "", altersCode ? " altersCode" : "", key.isEnabled() ? "" : " disabled")); } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.pointerTracker_callListenerOnCodeInput(key, x, y, ignoreModifierKey, - altersCode, code); - } if (ignoreModifierKey) { return; } @@ -374,10 +367,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element, withSliding ? " sliding" : "", ignoreModifierKey ? " ignoreModifier" : "", key.isEnabled() ? "": " disabled")); } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.pointerTracker_callListenerOnRelease(key, primaryCode, withSliding, - ignoreModifierKey); - } if (ignoreModifierKey) { return; } @@ -397,9 +386,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element, if (DEBUG_LISTENER) { Log.d(TAG, String.format("[%d] onCancelInput", mPointerId)); } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.pointerTracker_callListenerOnCancelInput(); - } sListener.onCancelInput(); } @@ -690,10 +676,10 @@ public final class PointerTracker implements PointerTrackerQueue.Element, private void onDownEvent(final int x, final int y, final long eventTime, final KeyDetector keyDetector) { + setKeyDetectorInner(keyDetector); if (DEBUG_EVENT) { printTouchEvent("onDownEvent:", x, y, eventTime); } - setKeyDetectorInner(keyDetector); // Naive up-to-down noise filter. final long deltaT = eventTime - mUpTime; if (deltaT < sParams.mTouchNoiseThresholdTime) { @@ -703,9 +689,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element, Log.w(TAG, String.format("[%d] onDownEvent:" + " ignore potential noise: time=%d distance=%d", mPointerId, deltaT, distance)); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.pointerTracker_onDownEvent(deltaT, distance * distance); - } cancelTrackingForAction(); return; } @@ -877,10 +860,6 @@ public final class PointerTracker implements PointerTrackerQueue.Element, lastX, lastY, Constants.printableCode(oldKey.getCode()), x, y, Constants.printableCode(key.getCode()))); } - // TODO: This should be moved to outside of this nested if-clause? - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.pointerTracker_onMoveEvent(x, y, lastX, lastY); - } onUpEventInternal(x, y, eventTime); onDownEventInternal(x, y, eventTime); } @@ -1054,8 +1033,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, final int translatedY = mMoreKeysPanel.translateY(y); mMoreKeysPanel.onUpEvent(translatedX, translatedY, mPointerId, eventTime); } - mMoreKeysPanel.dismissMoreKeysPanel(); - mMoreKeysPanel = null; + dismissMoreKeysPanel(); return; } @@ -1100,6 +1078,14 @@ public final class PointerTracker implements PointerTrackerQueue.Element, mIsTrackingForActionDisabled = true; } + public boolean isInOperation() { + return !mIsTrackingForActionDisabled; + } + + public void cancelLongPressTimer() { + sTimerProxy.cancelLongPressTimerOf(this); + } + public void onLongPressed() { resetKeySelectionByDraggingFinger(); cancelTrackingForAction(); @@ -1122,10 +1108,7 @@ public final class PointerTracker implements PointerTrackerQueue.Element, sTimerProxy.cancelKeyTimersOf(this); setReleasedKeyGraphics(mCurrentKey); resetKeySelectionByDraggingFinger(); - if (isShowingMoreKeysPanel()) { - mMoreKeysPanel.dismissMoreKeysPanel(); - mMoreKeysPanel = null; - } + dismissMoreKeysPanel(); } private boolean isMajorEnoughMoveToBeOnNewKey(final int x, final int y, final long eventTime, diff --git a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java index c89bda40e..c19cd671a 100644 --- a/java/src/com/android/inputmethod/keyboard/ProximityInfo.java +++ b/java/src/com/android/inputmethod/keyboard/ProximityInfo.java @@ -22,7 +22,6 @@ import android.util.Log; import com.android.inputmethod.keyboard.internal.TouchPositionCorrection; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.JniUtils; import java.util.ArrayList; @@ -55,6 +54,7 @@ public class ProximityInfo { private final List<Key>[] mGridNeighbors; private final String mLocaleStr; + @SuppressWarnings("unchecked") ProximityInfo(final String localeStr, final int gridWidth, final int gridHeight, final int minWidth, final int height, final int mostCommonKeyWidth, final int mostCommonKeyHeight, final List<Key> sortedKeys, @@ -360,7 +360,7 @@ y |---+---+---+---+-v-+-|-+---+---+---+---+---| | thresholdBase and get for (int i = 0; i < gridSize; ++i) { final int indexStart = i * keyCount; final int indexEnd = indexStart + neighborCountPerCell[i]; - final ArrayList<Key> neighbors = CollectionUtils.newArrayList(indexEnd - indexStart); + final ArrayList<Key> neighbors = new ArrayList<>(indexEnd - indexStart); for (int index = indexStart; index < indexEnd; index++) { neighbors.add(neighborsFlatBuffer[index]); } diff --git a/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java b/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java index c7a9025c0..daeb1f928 100644 --- a/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java +++ b/java/src/com/android/inputmethod/keyboard/emoji/DynamicGridKeyboard.java @@ -23,7 +23,6 @@ import android.util.Log; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.JsonUtils; import java.util.ArrayDeque; @@ -47,8 +46,8 @@ final class DynamicGridKeyboard extends Keyboard { private final int mColumnsNum; private final int mMaxKeyCount; private final boolean mIsRecents; - private final ArrayDeque<GridKey> mGridKeys = CollectionUtils.newArrayDeque(); - private final ArrayDeque<Key> mPendingKeys = CollectionUtils.newArrayDeque(); + private final ArrayDeque<GridKey> mGridKeys = new ArrayDeque<>(); + private final ArrayDeque<Key> mPendingKeys = new ArrayDeque<>(); private List<Key> mCachedGridKeys; @@ -131,7 +130,7 @@ final class DynamicGridKeyboard extends Keyboard { } private void saveRecentKeys() { - final ArrayList<Object> keys = CollectionUtils.newArrayList(); + final ArrayList<Object> keys = new ArrayList<>(); for (final Key key : mGridKeys) { if (key.getOutputText() != null) { keys.add(key.getOutputText()); diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java index 859099110..512d4615d 100644 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategory.java @@ -31,7 +31,6 @@ import com.android.inputmethod.keyboard.KeyboardLayoutSet; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.Collections; @@ -101,12 +100,11 @@ final class EmojiCategory { private final Resources mRes; private final int mMaxPageKeyCount; private final KeyboardLayoutSet mLayoutSet; - private final HashMap<String, Integer> mCategoryNameToIdMap = CollectionUtils.newHashMap(); + private final HashMap<String, Integer> mCategoryNameToIdMap = new HashMap<>(); private final int[] mCategoryTabIconId = new int[sCategoryName.length]; - private final ArrayList<CategoryProperties> mShownCategories = - CollectionUtils.newArrayList(); - private final ConcurrentHashMap<Long, DynamicGridKeyboard> - mCategoryKeyboardMap = new ConcurrentHashMap<Long, DynamicGridKeyboard>(); + private final ArrayList<CategoryProperties> mShownCategories = new ArrayList<>(); + private final ConcurrentHashMap<Long, DynamicGridKeyboard> mCategoryKeyboardMap = + new ConcurrentHashMap<>(); private int mCurrentCategoryId = EmojiCategory.ID_UNSPECIFIED; private int mCurrentCategoryPageId = 0; @@ -257,7 +255,7 @@ final class EmojiCategory { final int temp = sum; sum += properties.mPageCount; if (sum > position) { - return new Pair<Integer, Integer>(properties.mCategoryId, position - temp); + return new Pair<>(properties.mCategoryId, position - temp); } } return null; @@ -343,7 +341,7 @@ final class EmojiCategory { }; private static Key[][] sortKeysIntoPages(final List<Key> inKeys, final int maxPageCount) { - final ArrayList<Key> keys = CollectionUtils.newArrayList(inKeys); + final ArrayList<Key> keys = new ArrayList<>(inKeys); Collections.sort(keys, EMOJI_KEY_COMPARATOR); final int pageCount = (keys.size() - 1) / maxPageCount + 1; final Key[][] retval = new Key[pageCount][maxPageCount]; diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java index a6b089169..43d62c71a 100644 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiCategoryPageIndicatorView.java @@ -17,14 +17,11 @@ package com.android.inputmethod.keyboard.emoji; import android.content.Context; -import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; -import com.android.inputmethod.latin.R; - public final class EmojiCategoryPageIndicatorView extends View { private static final float BOTTOM_MARGIN_RATIO = 1.0f; private final Paint mPaint = new Paint(); @@ -33,19 +30,17 @@ public final class EmojiCategoryPageIndicatorView extends View { private float mOffset = 0.0f; public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.emojiCategoryPageIndicatorViewStyle); + this(context, attrs, 0); } public EmojiCategoryPageIndicatorView(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); - final TypedArray indicatorViewAttr = context.obtainStyledAttributes(attrs, - R.styleable.EmojiCategoryPageIndicatorView, defStyle, - R.style.EmojiCategoryPageIndicatorView); - final int indicatorColor = indicatorViewAttr.getColor( - R.styleable.EmojiCategoryPageIndicatorView_emojiCategoryPageIndicatorColor, 0); - indicatorViewAttr.recycle(); - mPaint.setColor(indicatorColor); + } + + public void setColors(final int foregroundColor, final int backgroundColor) { + mPaint.setColor(foregroundColor); + setBackgroundColor(backgroundColor); } public void setCategoryPageId(final int size, final int id, final float offset) { diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java index 48efa17ad..80ba60c82 100644 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java @@ -55,7 +55,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements private OnKeyEventListener mListener = EMPTY_LISTENER; private final KeyDetector mKeyDetector = new KeyDetector(); private final GestureDetector mGestureDetector; - private final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate; + private KeyboardAccessibilityDelegate<EmojiPageKeyboardView> mAccessibilityDelegate; public EmojiPageKeyboardView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.keyboardViewStyle); @@ -67,7 +67,6 @@ final class EmojiPageKeyboardView extends KeyboardView implements mGestureDetector = new GestureDetector(context, this); mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */); mHandler = new Handler(); - mAccessibilityDelegate = new KeyboardAccessibilityDelegate<>(this, mKeyDetector); } public void setOnKeyEventListener(final OnKeyEventListener listener) { @@ -81,15 +80,27 @@ final class EmojiPageKeyboardView extends KeyboardView implements public void setKeyboard(final Keyboard keyboard) { super.setKeyboard(keyboard); mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */); + if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { + if (mAccessibilityDelegate == null) { + mAccessibilityDelegate = new KeyboardAccessibilityDelegate<>(this, mKeyDetector); + } + mAccessibilityDelegate.setKeyboard(keyboard); + } else { + mAccessibilityDelegate = null; + } } + /** + * {@inheritDoc} + */ @Override - public boolean dispatchHoverEvent(final MotionEvent event) { - if (!AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { - // Reflection doesn't support calling superclass methods. - return false; + public boolean onHoverEvent(final MotionEvent event) { + final KeyboardAccessibilityDelegate<EmojiPageKeyboardView> accessibilityDelegate = + mAccessibilityDelegate; + if (accessibilityDelegate != null) { + return accessibilityDelegate.onHoverEvent(event); } - return mAccessibilityDelegate.dispatchHoverEvent(event); + return super.onHoverEvent(event); } /** @@ -102,7 +113,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements } final Key key = getKey(e); if (key != null && key != mCurrentKey) { - releaseCurrentKey(); + releaseCurrentKey(false /* withKeyRegistering */); } return true; } @@ -119,7 +130,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements return mKeyDetector.detectHitKey(x, y); } - public void releaseCurrentKey() { + public void releaseCurrentKey(final boolean withKeyRegistering) { mHandler.removeCallbacks(mPendingKeyDown); mPendingKeyDown = null; final Key currentKey = mCurrentKey; @@ -128,13 +139,16 @@ final class EmojiPageKeyboardView extends KeyboardView implements } currentKey.onReleased(); invalidateKey(currentKey); + if (withKeyRegistering) { + mListener.onReleaseKey(currentKey); + } mCurrentKey = null; } @Override public boolean onDown(final MotionEvent e) { final Key key = getKey(e); - releaseCurrentKey(); + releaseCurrentKey(false /* withKeyRegistering */); mCurrentKey = key; if (key == null) { return false; @@ -163,7 +177,7 @@ final class EmojiPageKeyboardView extends KeyboardView implements final Key key = getKey(e); final Runnable pendingKeyDown = mPendingKeyDown; final Key currentKey = mCurrentKey; - releaseCurrentKey(); + releaseCurrentKey(false /* withKeyRegistering */); if (key == null) { return false; } @@ -189,14 +203,14 @@ final class EmojiPageKeyboardView extends KeyboardView implements @Override public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY) { - releaseCurrentKey(); + releaseCurrentKey(false /* withKeyRegistering */); return false; } @Override public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) { - releaseCurrentKey(); + releaseCurrentKey(false /* withKeyRegistering */); return false; } diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java index 52a4dde97..68056e0eb 100644 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesAdapter.java @@ -27,7 +27,6 @@ import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.CollectionUtils; final class EmojiPalettesAdapter extends PagerAdapter { private static final String TAG = EmojiPalettesAdapter.class.getSimpleName(); @@ -35,8 +34,7 @@ final class EmojiPalettesAdapter extends PagerAdapter { private final EmojiPageKeyboardView.OnKeyEventListener mListener; private final DynamicGridKeyboard mRecentsKeyboard; - private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews = - CollectionUtils.newSparseArray(); + private final SparseArray<EmojiPageKeyboardView> mActiveKeyboardViews = new SparseArray<>(); private final EmojiCategory mEmojiCategory; private int mActivePosition = 0; @@ -70,13 +68,18 @@ final class EmojiPalettesAdapter extends PagerAdapter { } public void onPageScrolled() { + releaseCurrentKey(false /* withKeyRegistering */); + } + + public void releaseCurrentKey(final boolean withKeyRegistering) { // Make sure the delayed key-down event (highlight effect and haptic feedback) will be // canceled. final EmojiPageKeyboardView currentKeyboardView = mActiveKeyboardViews.get(mActivePosition); - if (currentKeyboardView != null) { - currentKeyboardView.releaseCurrentKey(); + if (currentKeyboardView == null) { + return; } + currentKeyboardView.releaseCurrentKey(withKeyRegistering); } @Override @@ -92,7 +95,7 @@ final class EmojiPalettesAdapter extends PagerAdapter { } final EmojiPageKeyboardView oldKeyboardView = mActiveKeyboardViews.get(mActivePosition); if (oldKeyboardView != null) { - oldKeyboardView.releaseCurrentKey(); + oldKeyboardView.releaseCurrentKey(false /* withKeyRegistering */); oldKeyboardView.deallocateMemory(); } mActivePosition = position; diff --git a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java index c0c9e205a..e37cd2369 100644 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPalettesView.java @@ -36,6 +36,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TabHost; import android.widget.TabHost.OnTabChangeListener; +import android.widget.TabWidget; import android.widget.TextView; import com.android.inputmethod.keyboard.Key; @@ -45,6 +46,7 @@ import com.android.inputmethod.keyboard.KeyboardView; import com.android.inputmethod.keyboard.internal.KeyDrawParams; import com.android.inputmethod.keyboard.internal.KeyVisualAttributes; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; +import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; @@ -68,6 +70,11 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange EmojiPageKeyboardView.OnKeyEventListener { private final int mFunctionalKeyBackgroundId; private final int mSpacebarBackgroundId; + private final boolean mCategoryIndicatorEnabled; + private final int mCategoryIndicatorDrawableResId; + private final int mCategoryIndicatorBackgroundResId; + private final int mCategoryPageIndicatorColor; + private final int mCategoryPageIndicatorBackground; private final DeleteKeyOnTouchListener mDeleteKeyOnTouchListener; private EmojiPalettesAdapter mEmojiPalettesAdapter; private final EmojiLayoutParams mEmojiLayoutParams; @@ -109,13 +116,21 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange builder.setSubtype(SubtypeSwitcher.getInstance().getEmojiSubtype()); builder.setKeyboardGeometry(ResourceUtils.getDefaultKeyboardWidth(res), mEmojiLayoutParams.mEmojiKeyboardHeight); - builder.setOptions(false /* shortcutImeEnabled */, false /* showsVoiceInputKey */, - false /* languageSwitchKeyEnabled */); final KeyboardLayoutSet layoutSet = builder.build(); final TypedArray emojiPalettesViewAttr = context.obtainStyledAttributes(attrs, R.styleable.EmojiPalettesView, defStyle, R.style.EmojiPalettesView); mEmojiCategory = new EmojiCategory(PreferenceManager.getDefaultSharedPreferences(context), res, layoutSet, emojiPalettesViewAttr); + mCategoryIndicatorEnabled = emojiPalettesViewAttr.getBoolean( + R.styleable.EmojiPalettesView_categoryIndicatorEnabled, false); + mCategoryIndicatorDrawableResId = emojiPalettesViewAttr.getResourceId( + R.styleable.EmojiPalettesView_categoryIndicatorDrawable, 0); + mCategoryIndicatorBackgroundResId = emojiPalettesViewAttr.getResourceId( + R.styleable.EmojiPalettesView_categoryIndicatorBackground, 0); + mCategoryPageIndicatorColor = emojiPalettesViewAttr.getColor( + R.styleable.EmojiPalettesView_categoryPageIndicatorColor, 0); + mCategoryPageIndicatorBackground = emojiPalettesViewAttr.getColor( + R.styleable.EmojiPalettesView_categoryPageIndicatorBackground, 0); emojiPalettesViewAttr.recycle(); mDeleteKeyOnTouchListener = new DeleteKeyOnTouchListener(context); } @@ -154,7 +169,15 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange addTab(mTabHost, properties.mCategoryId); } mTabHost.setOnTabChangedListener(this); - mTabHost.getTabWidget().setStripEnabled(true); + final TabWidget tabWidget = mTabHost.getTabWidget(); + tabWidget.setStripEnabled(mCategoryIndicatorEnabled); + if (mCategoryIndicatorEnabled) { + // On TabWidget's strip, what looks like an indicator is actually a background. + // And what looks like a background are actually left and right drawables. + tabWidget.setBackgroundResource(mCategoryIndicatorDrawableResId); + tabWidget.setLeftStripDrawable(mCategoryIndicatorBackgroundResId); + tabWidget.setRightStripDrawable(mCategoryIndicatorBackgroundResId); + } mEmojiPalettesAdapter = new EmojiPalettesAdapter(mEmojiCategory, this); @@ -167,6 +190,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange mEmojiCategoryPageIndicatorView = (EmojiCategoryPageIndicatorView)findViewById(R.id.emoji_category_page_id_view); + mEmojiCategoryPageIndicatorView.setColors( + mCategoryPageIndicatorColor, mCategoryPageIndicatorBackground); mEmojiLayoutParams.setCategoryPageIdViewProperties(mEmojiCategoryPageIndicatorView); setCurrentCategoryId(mEmojiCategory.getCurrentCategoryId(), true /* force */); @@ -216,6 +241,8 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange @Override public void onTabChanged(final String tabId) { + AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback( + Constants.CODE_UNSPECIFIED, this); final int categoryId = mEmojiCategory.getCategoryId(tabId); setCurrentCategoryId(categoryId, false /* force */); updateEmojiCategoryPageIdView(); @@ -364,6 +391,7 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange } public void stopEmojiPalettes() { + mEmojiPalettesAdapter.releaseCurrentKey(true /* withKeyRegistering */); mEmojiPalettesAdapter.flushPendingRecentKeys(); mEmojiPager.setAdapter(null); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java index fdc2458d4..3b4c43418 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java +++ b/java/src/com/android/inputmethod/keyboard/internal/DrawingPreviewPlacerView.java @@ -24,7 +24,6 @@ import android.graphics.PorterDuffXfermode; import android.util.AttributeSet; import android.widget.RelativeLayout; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import java.util.ArrayList; @@ -32,7 +31,7 @@ import java.util.ArrayList; public final class DrawingPreviewPlacerView extends RelativeLayout { private final int[] mKeyboardViewOrigin = CoordinateUtils.newInstance(); - private final ArrayList<AbstractDrawingPreview> mPreviews = CollectionUtils.newArrayList(); + private final ArrayList<AbstractDrawingPreview> mPreviews = new ArrayList<>(); public DrawingPreviewPlacerView(final Context context, final AttributeSet attrs) { super(context, attrs); diff --git a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java index d8b00c707..72628e38a 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java +++ b/java/src/com/android/inputmethod/keyboard/internal/GestureTrailsDrawingPreview.java @@ -29,15 +29,13 @@ import android.util.SparseArray; import android.view.View; import com.android.inputmethod.keyboard.PointerTracker; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; /** * Draw preview graphics of multiple gesture trails during gesture input. */ public final class GestureTrailsDrawingPreview extends AbstractDrawingPreview { - private final SparseArray<GestureTrailDrawingPoints> mGestureTrails = - CollectionUtils.newSparseArray(); + private final SparseArray<GestureTrailDrawingPoints> mGestureTrails = new SparseArray<>(); private final GestureTrailDrawingParams mDrawingParams; private final Paint mGesturePaint; private int mOffscreenWidth; diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java index 625d1f0a4..605519b02 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyPreviewChoreographer.java @@ -32,7 +32,6 @@ import android.widget.TextView; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.ViewLayoutUtils; @@ -48,9 +47,9 @@ import java.util.HashSet; */ public final class KeyPreviewChoreographer { // Free {@link TextView} pool that can be used for key preview. - private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = CollectionUtils.newArrayDeque(); + private final ArrayDeque<TextView> mFreeKeyPreviewTextViews = new ArrayDeque<>(); // Map from {@link Key} to {@link TextView} that is currently being displayed as key preview. - private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = CollectionUtils.newHashMap(); + private final HashMap<Key,TextView> mShowingKeyPreviewTextViews = new HashMap<>(); private final KeyPreviewDrawParams mParams; @@ -83,7 +82,7 @@ public final class KeyPreviewChoreographer { } public void dismissAllKeyPreviews() { - for (final Key key : new HashSet<Key>(mShowingKeyPreviewTextViews.keySet())) { + for (final Key key : new HashSet<>(mShowingKeyPreviewTextViews.keySet())) { dismissKeyPreview(key, false /* withAnimation */); } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java index 700c9b07c..0b0e761d2 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStylesSet.java @@ -21,7 +21,6 @@ import android.util.Log; import android.util.SparseArray; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.XmlParseUtils; import org.xmlpull.v1.XmlPullParser; @@ -34,7 +33,7 @@ public final class KeyStylesSet { private static final String TAG = KeyStylesSet.class.getSimpleName(); private static final boolean DEBUG = false; - private final HashMap<String, KeyStyle> mStyles = CollectionUtils.newHashMap(); + private final HashMap<String, KeyStyle> mStyles = new HashMap<>(); private final KeyboardTextsSet mTextsSet; private final KeyStyle mEmptyKeyStyle; @@ -75,7 +74,7 @@ public final class KeyStylesSet { private static final class DeclaredKeyStyle extends KeyStyle { private final HashMap<String, KeyStyle> mStyles; private final String mParentStyleName; - private final SparseArray<Object> mStyleAttributes = CollectionUtils.newSparseArray(); + private final SparseArray<Object> mStyleAttributes = new SparseArray<>(); public DeclaredKeyStyle(final String parentStyleName, final KeyboardTextsSet textsSet, final HashMap<String, KeyStyle> styles) { diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java index 2aeeed87f..e69499829 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardBuilder.java @@ -652,9 +652,6 @@ public class KeyboardBuilder<KP extends KeyboardParams> { R.styleable.Keyboard_Case_passwordInput, id.passwordInput()); final boolean clobberSettingsKeyMatched = matchBoolean(caseAttr, R.styleable.Keyboard_Case_clobberSettingsKey, id.mClobberSettingsKey); - final boolean supportsSwitchingToShortcutImeMatched = matchBoolean(caseAttr, - R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme, - id.mSupportsSwitchingToShortcutIme); final boolean hasShortcutKeyMatched = matchBoolean(caseAttr, R.styleable.Keyboard_Case_hasShortcutKey, id.mHasShortcutKey); final boolean languageSwitchKeyEnabledMatched = matchBoolean(caseAttr, @@ -674,14 +671,13 @@ public class KeyboardBuilder<KP extends KeyboardParams> { R.styleable.Keyboard_Case_countryCode, id.mLocale.getCountry()); final boolean selected = keyboardLayoutSetMatched && keyboardLayoutSetElementMatched && modeMatched && navigateNextMatched && navigatePreviousMatched - && passwordInputMatched && clobberSettingsKeyMatched - && supportsSwitchingToShortcutImeMatched && hasShortcutKeyMatched + && passwordInputMatched && clobberSettingsKeyMatched && hasShortcutKeyMatched && languageSwitchKeyEnabledMatched && isMultiLineMatched && imeActionMatched && isIconDefinedMatched && localeCodeMatched && languageCodeMatched && countryCodeMatched; if (DEBUG) { - startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, + startTag("<%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s>%s", TAG_CASE, textAttr(caseAttr.getString( R.styleable.Keyboard_Case_keyboardLayoutSet), "keyboardLayoutSet"), textAttr(caseAttr.getString( @@ -698,9 +694,6 @@ public class KeyboardBuilder<KP extends KeyboardParams> { "clobberSettingsKey"), booleanAttr(caseAttr, R.styleable.Keyboard_Case_passwordInput, "passwordInput"), - booleanAttr( - caseAttr, R.styleable.Keyboard_Case_supportsSwitchingToShortcutIme, - "supportsSwitchingToShortcutIme"), booleanAttr(caseAttr, R.styleable.Keyboard_Case_hasShortcutKey, "hasShortcutKey"), booleanAttr(caseAttr, R.styleable.Keyboard_Case_languageSwitchKeyEnabled, diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java index 06da5719b..62b69dcc9 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardCodesSet.java @@ -17,14 +17,13 @@ package com.android.inputmethod.keyboard.internal; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; public final class KeyboardCodesSet { public static final String PREFIX_CODE = "!code/"; - private static final HashMap<String, Integer> sNameToIdMap = CollectionUtils.newHashMap(); + private static final HashMap<String, Integer> sNameToIdMap = new HashMap<>(); private KeyboardCodesSet() { // This utility class is not publicly instantiable. diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java index f79bde017..7146deb4b 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardIconsSet.java @@ -23,7 +23,6 @@ import android.util.Log; import android.util.SparseIntArray; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; @@ -60,7 +59,7 @@ public final class KeyboardIconsSet { private static final SparseIntArray ATTR_ID_TO_ICON_ID = new SparseIntArray(); // Icon name to icon id map. - private static final HashMap<String, Integer> sNameToIdsMap = CollectionUtils.newHashMap(); + private static final HashMap<String, Integer> sNameToIdsMap = new HashMap<>(); private static final Object[] NAMES_AND_ATTR_IDS = { NAME_UNDEFINED, ATTR_UNDEFINED, diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java index a61a79b85..5df9d3ece 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardParams.java @@ -21,7 +21,6 @@ import android.util.SparseIntArray; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyboardId; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.Comparator; @@ -61,9 +60,9 @@ public class KeyboardParams { public int GRID_HEIGHT; // Keys are sorted from top-left to bottom-right order. - public final SortedSet<Key> mSortedKeys = new TreeSet<Key>(ROW_COLUMN_COMPARATOR); - public final ArrayList<Key> mShiftKeys = CollectionUtils.newArrayList(); - public final ArrayList<Key> mAltCodeKeysWhileTyping = CollectionUtils.newArrayList(); + public final SortedSet<Key> mSortedKeys = new TreeSet<>(ROW_COLUMN_COMPARATOR); + public final ArrayList<Key> mShiftKeys = new ArrayList<>(); + public final ArrayList<Key> mAltCodeKeysWhileTyping = new ArrayList<>(); public final KeyboardIconsSet mIconsSet = new KeyboardIconsSet(); public final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); public final KeyStylesSet mKeyStyles = new KeyStylesSet(mTextsSet); diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java index 0f9497c27..6db1d02c9 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardRow.java @@ -23,7 +23,6 @@ import android.util.Xml; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.ResourceUtils; import org.xmlpull.v1.XmlPullParser; @@ -44,7 +43,7 @@ public final class KeyboardRow { /** The height of this row. */ private final int mRowHeight; - private final ArrayDeque<RowAttributes> mRowAttributesStack = CollectionUtils.newArrayDeque(); + private final ArrayDeque<RowAttributes> mRowAttributesStack = new ArrayDeque<>(); private static class RowAttributes { /** Default width of a key in this row. */ diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 0047aa4a1..cd6abeed3 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -22,7 +22,6 @@ import android.text.TextUtils; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.RunInLocale; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; @@ -38,7 +37,7 @@ public final class KeyboardTextsSet { private String[] mTextsTable; // Resource name to text map. - private HashMap<String, String> mResourceNameToTextsMap = CollectionUtils.newHashMap(); + private HashMap<String, String> mResourceNameToTextsMap = new HashMap<>(); public void setLocale(final Locale locale, final Context context) { mTextsTable = KeyboardTextsTable.getTextsTable(locale); @@ -141,6 +140,7 @@ public final class KeyboardTextsSet { "label_send_key", "label_next_key", "label_done_key", + "label_search_key", "label_previous_key", // Other labels. "label_pause_key", diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java index 7e6181a4e..ab2555802 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java @@ -16,8 +16,6 @@ package com.android.inputmethod.keyboard.internal; -import com.android.inputmethod.latin.utils.CollectionUtils; - import java.util.HashMap; import java.util.Locale; @@ -44,14 +42,12 @@ import java.util.Locale; */ public final class KeyboardTextsTable { // Name to index map. - private static final HashMap<String, Integer> sNameToIndexesMap = CollectionUtils.newHashMap(); + private static final HashMap<String, Integer> sNameToIndexesMap = new HashMap<>(); // Locale to texts table map. - private static final HashMap<String, String[]> sLocaleToTextsTableMap = - CollectionUtils.newHashMap(); + private static final HashMap<String, String[]> sLocaleToTextsTableMap = new HashMap<>(); // TODO: Remove this variable after debugging. // Texts table to locale maps. - private static final HashMap<String[], String> sTextsTableToLocaleMap = - CollectionUtils.newHashMap(); + private static final HashMap<String[], String> sTextsTableToLocaleMap = new HashMap<>(); public static String getText(final String name, final String[] textsTable) { final Integer indexObj = sNameToIndexesMap.get(name); @@ -606,7 +602,7 @@ public final class KeyboardTextsTable { /* keyspec_right_double_angle_quote */ "\u00BB|\u00AB", /* keyspec_left_single_angle_quote */ "\u2039|\u203A", /* keyspec_right_single_angle_quote */ "\u203A|\u2039", - /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,\",\'", + /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,\",\'", // U+0651: "ّ" ARABIC SHADDA /* keyhintlabel_period */ "\u0651", /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics", @@ -1555,7 +1551,7 @@ public final class KeyboardTextsTable { /* keyspec_right_double_angle_quote */ "\u00BB|\u00AB", /* keyspec_left_single_angle_quote */ "\u2039|\u203A", /* keyspec_right_single_angle_quote */ "\u203A|\u2039", - /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,/,!text/keyspec_left_double_angle_quote,!text/keyspec_right_double_angle_quote", + /* morekeys_tablet_comma */ "!fixedColumnOrder!4,:,!,\u061F,\u061B,-,!text/keyspec_left_double_angle_quote,!text/keyspec_right_double_angle_quote", // U+064B: "ً" ARABIC FATHATAN /* keyhintlabel_period */ "\u064B", /* morekeys_tablet_period */ "!text/morekeys_arabic_diacritics", diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java index 7c2e3e174..7743d4744 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeysCache.java @@ -17,12 +17,11 @@ package com.android.inputmethod.keyboard.internal; import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.HashMap; public final class KeysCache { - private final HashMap<Key, Key> mMap = CollectionUtils.newHashMap(); + private final HashMap<Key, Key> mMap = new HashMap<>(); public void clear() { mMap.clear(); diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java index 56ef4767f..e0d5173ac 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java @@ -149,7 +149,7 @@ public final class MoreKeySpec { // Skip empty entry. if (pos - start > 0) { if (list == null) { - list = CollectionUtils.newArrayList(); + list = new ArrayList<>(); } list.add(text.substring(start, pos)); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java index 5ac34188c..8e89e61ea 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java +++ b/java/src/com/android/inputmethod/keyboard/internal/PointerTrackerQueue.java @@ -18,8 +18,6 @@ package com.android.inputmethod.keyboard.internal; import android.util.Log; -import com.android.inputmethod.latin.utils.CollectionUtils; - import java.util.ArrayList; public final class PointerTrackerQueue { @@ -37,7 +35,7 @@ public final class PointerTrackerQueue { // Note: {@link #mExpandableArrayOfActivePointers} and {@link #mArraySize} are synchronized by // {@link #mExpandableArrayOfActivePointers} private final ArrayList<Element> mExpandableArrayOfActivePointers = - CollectionUtils.newArrayList(INITIAL_CAPACITY); + new ArrayList<>(INITIAL_CAPACITY); private int mArraySize = 0; public int size() { diff --git a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java index 54bc29559..eb8b34ccd 100644 --- a/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java +++ b/java/src/com/android/inputmethod/latin/AudioAndHapticFeedbackManager.java @@ -16,14 +16,14 @@ package com.android.inputmethod.latin; -import com.android.inputmethod.latin.settings.SettingsValues; - import android.content.Context; import android.media.AudioManager; import android.os.Vibrator; import android.view.HapticFeedbackConstants; import android.view.View; +import com.android.inputmethod.latin.settings.SettingsValues; + /** * This class gathers audio feedback and haptic feedback functions. * @@ -86,40 +86,41 @@ public final class AudioAndHapticFeedbackManager { if (mAudioManager == null) { return; } - if (mSoundOn) { - final int sound; - switch (code) { - case Constants.CODE_DELETE: - sound = AudioManager.FX_KEYPRESS_DELETE; - break; - case Constants.CODE_ENTER: - sound = AudioManager.FX_KEYPRESS_RETURN; - break; - case Constants.CODE_SPACE: - sound = AudioManager.FX_KEYPRESS_SPACEBAR; - break; - default: - sound = AudioManager.FX_KEYPRESS_STANDARD; - break; - } - mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume); + if (!mSoundOn) { + return; + } + final int sound; + switch (code) { + case Constants.CODE_DELETE: + sound = AudioManager.FX_KEYPRESS_DELETE; + break; + case Constants.CODE_ENTER: + sound = AudioManager.FX_KEYPRESS_RETURN; + break; + case Constants.CODE_SPACE: + sound = AudioManager.FX_KEYPRESS_SPACEBAR; + break; + default: + sound = AudioManager.FX_KEYPRESS_STANDARD; + break; } + mAudioManager.playSoundEffect(sound, mSettingsValues.mKeypressSoundVolume); } public void performHapticFeedback(final View viewToPerformHapticFeedbackOn) { if (!mSettingsValues.mVibrateOn) { return; } - if (mSettingsValues.mKeypressVibrationDuration < 0) { - // Go ahead with the system default - if (viewToPerformHapticFeedbackOn != null) { - viewToPerformHapticFeedbackOn.performHapticFeedback( - HapticFeedbackConstants.KEYBOARD_TAP, - HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } + if (mSettingsValues.mKeypressVibrationDuration >= 0) { + vibrate(mSettingsValues.mKeypressVibrationDuration); return; } - vibrate(mSettingsValues.mKeypressVibrationDuration); + // Go ahead with the system default + if (viewToPerformHapticFeedbackOn != null) { + viewToPerformHapticFeedbackOn.performHapticFeedback( + HapticFeedbackConstants.KEYBOARD_TAP, + HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + } } public void onSettingsChanged(final SettingsValues settingsValues) { diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index e7ab02ac1..1d087439d 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -30,7 +30,6 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.WordProperty; import com.android.inputmethod.latin.settings.NativeSuggestOptions; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; @@ -104,8 +103,7 @@ public final class BinaryDictionary extends Dictionary { private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions(); - private final SparseArray<DicTraverseSession> mDicTraverseSessions = - CollectionUtils.newSparseArray(); + private final SparseArray<DicTraverseSession> mDicTraverseSessions = new SparseArray<>(); // TODO: There should be a way to remove used DicTraverseSession objects from // {@code mDicTraverseSessions}. @@ -185,13 +183,15 @@ public final class BinaryDictionary extends Dictionary { private static native void getHeaderInfoNative(long dict, int[] outHeaderSize, int[] outFormatVersion, ArrayList<int[]> outAttributeKeys, ArrayList<int[]> outAttributeValues); - private static native void flushNative(long dict, String filePath); + private static native boolean flushNative(long dict, String filePath); private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC); - private static native void flushWithGCNative(long dict, String filePath); + private static native boolean flushWithGCNative(long dict, String filePath); private static native void closeNative(long dict); private static native int getFormatVersionNative(long dict); private static native int getProbabilityNative(long dict, int[] word); - private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1); + private static native int getMaxProbabilityOfExactMatchesNative(long dict, int[] word); + private static native int getBigramProbabilityNative(long dict, int[] word0, + boolean isBeginningOfSentence, int[] word1); private static native void getWordPropertyNative(long dict, int[] word, int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo, ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo, @@ -200,15 +200,17 @@ public final class BinaryDictionary extends Dictionary { private static native void getSuggestionsNative(long dict, long proximityInfo, long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions, - int[] prevWordCodePointArray, int[] outputSuggestionCount, int[] outputCodePoints, - int[] outputScores, int[] outputIndices, int[] outputTypes, - int[] outputAutoCommitFirstWordConfidence, float[] inOutLanguageWeight); - private static native void addUnigramWordNative(long dict, int[] word, int probability, - int[] shortcutTarget, int shortcutProbability, boolean isNotAWord, - boolean isBlacklisted, int timestamp); - private static native void addBigramWordsNative(long dict, int[] word0, int[] word1, - int probability, int timestamp); - private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1); + int[] prevWordCodePointArray, boolean isBeginningOfSentence, + int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores, + int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence, + float[] inOutLanguageWeight); + private static native boolean addUnigramWordNative(long dict, int[] word, int probability, + int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence, + boolean isNotAWord, boolean isBlacklisted, int timestamp); + private static native boolean addBigramWordsNative(long dict, int[] word0, + boolean isBeginningOfSentence, int[] word1, int probability, int timestamp); + private static native boolean removeBigramWordsNative(long dict, int[] word0, + boolean isBeginningOfSentence, int[] word1); private static native int addMultipleDictionaryEntriesNative(long dict, LanguageModelParam[] languageModelParams, int startIndex); private static native String getPropertyNative(long dict, String query); @@ -245,11 +247,11 @@ public final class BinaryDictionary extends Dictionary { } final int[] outHeaderSize = new int[1]; final int[] outFormatVersion = new int[1]; - final ArrayList<int[]> outAttributeKeys = CollectionUtils.newArrayList(); - final ArrayList<int[]> outAttributeValues = CollectionUtils.newArrayList(); + final ArrayList<int[]> outAttributeKeys = new ArrayList<>(); + final ArrayList<int[]> outAttributeValues = new ArrayList<>(); getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys, outAttributeValues); - final HashMap<String, String> attributes = new HashMap<String, String>(); + final HashMap<String, String> attributes = new HashMap<>(); for (int i = 0; i < outAttributeKeys.size(); i++) { final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray( outAttributeKeys.get(i)); @@ -301,14 +303,15 @@ public final class BinaryDictionary extends Dictionary { getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(), inputPointers.getYCoordinates(), inputPointers.getTimes(), inputPointers.getPointerIds(), mInputCodePoints, inputSize, - mNativeSuggestOptions.getOptions(), prevWordCodePointArray, mOutputSuggestionCount, + mNativeSuggestOptions.getOptions(), prevWordCodePointArray, + prevWordsInfo.mIsBeginningOfSentence, mOutputSuggestionCount, mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes, mOutputAutoCommitFirstWordConfidence, mInputOutputLanguageWeight); if (inOutLanguageWeight != null) { inOutLanguageWeight[0] = mInputOutputLanguageWeight[0]; } final int count = mOutputSuggestionCount[0]; - final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); + final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>(); for (int j = 0; j < count; ++j) { final int start = j * MAX_WORD_LENGTH; int len = 0; @@ -316,23 +319,18 @@ public final class BinaryDictionary extends Dictionary { ++len; } if (len > 0) { - final int flags = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_FLAGS; - if (blockOffensiveWords - && 0 != (flags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE) - && 0 == (flags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH)) { + final SuggestedWordInfo suggestedWordInfo = + new SuggestedWordInfo(new String(mOutputCodePoints, start, len), + mOutputScores[j], mOutputTypes[j], this /* sourceDict */, + mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */, + mOutputAutoCommitFirstWordConfidence[0]); + if (blockOffensiveWords && suggestedWordInfo.isPossiblyOffensive() + && !suggestedWordInfo.isExactMatch()) { // If we block potentially offensive words, and if the word is possibly // offensive, then we don't output it unless it's also an exact match. continue; } - final int kind = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_KIND; - final int score = SuggestedWordInfo.KIND_WHITELIST == kind - ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j]; - // TODO: check that all users of the `kind' parameter are ready to accept - // flags too and pass mOutputTypes[j] instead of kind - suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len), - score, kind, this /* sourceDict */, - mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */, - mOutputAutoCommitFirstWordConfidence[0])); + suggestions.add(suggestedWordInfo); } } return suggestions; @@ -347,29 +345,37 @@ public final class BinaryDictionary extends Dictionary { } @Override - public boolean isValidWord(final String word) { + public boolean isInDictionary(final String word) { return getFrequency(word) != NOT_A_PROBABILITY; } @Override public int getFrequency(final String word) { - if (word == null) return NOT_A_PROBABILITY; + if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY; int[] codePoints = StringUtils.toCodePointArray(word); return getProbabilityNative(mNativeDict, codePoints); } + @Override + public int getMaxFrequencyOfExactMatches(final String word) { + if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY; + int[] codePoints = StringUtils.toCodePointArray(word); + return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints); + } + @UsedForTesting public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) { return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY; } public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) { - if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) { + if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { return NOT_A_PROBABILITY; } final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord); final int[] codePoints1 = StringUtils.toCodePointArray(word); - return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1); + return getBigramProbabilityNative(mNativeDict, codePoints0, + prevWordsInfo.mIsBeginningOfSentence, codePoints1); } public WordProperty getWordProperty(final String word) { @@ -381,10 +387,10 @@ public final class BinaryDictionary extends Dictionary { final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT]; final int[] outProbabilityInfo = new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT]; - final ArrayList<int[]> outBigramTargets = CollectionUtils.newArrayList(); - final ArrayList<int[]> outBigramProbabilityInfo = CollectionUtils.newArrayList(); - final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList(); - final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList(); + final ArrayList<int[]> outBigramTargets = new ArrayList<>(); + final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>(); + final ArrayList<int[]> outShortcutTargets = new ArrayList<>(); + final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>(); getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo, outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, outShortcutProbabilities); @@ -419,42 +425,53 @@ public final class BinaryDictionary extends Dictionary { } // Add a unigram entry to binary dictionary with unigram attributes in native code. - public void addUnigramEntry(final String word, final int probability, - final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord, + public boolean addUnigramEntry(final String word, final int probability, + final String shortcutTarget, final int shortcutProbability, + final boolean isBeginningOfSentence, final boolean isNotAWord, final boolean isBlacklisted, final int timestamp) { - if (TextUtils.isEmpty(word)) { - return; + if (word == null || (word.isEmpty() && !isBeginningOfSentence)) { + return false; } final int[] codePoints = StringUtils.toCodePointArray(word); final int[] shortcutTargetCodePoints = (shortcutTarget != null) ? StringUtils.toCodePointArray(shortcutTarget) : null; - addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints, - shortcutProbability, isNotAWord, isBlacklisted, timestamp); + if (!addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints, + shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) { + return false; + } mHasUpdated = true; + return true; } // Add an n-gram entry to the binary dictionary with timestamp in native code. - public void addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word, - final int probability, - final int timestamp) { - if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) { - return; + public boolean addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word, + final int probability, final int timestamp) { + if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { + return false; } final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord); final int[] codePoints1 = StringUtils.toCodePointArray(word); - addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp); + if (!addBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence, + codePoints1, probability, timestamp)) { + return false; + } mHasUpdated = true; + return true; } // Remove an n-gram entry from the binary dictionary in native code. - public void removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) { - if (TextUtils.isEmpty(prevWordsInfo.mPrevWord) || TextUtils.isEmpty(word)) { - return; + public boolean removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) { + if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { + return false; } final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord); final int[] codePoints1 = StringUtils.toCodePointArray(word); - removeBigramWordsNative(mNativeDict, codePoints0, codePoints1); + if (!removeBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence, + codePoints1)) { + return false; + } mHasUpdated = true; + return true; } public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) { @@ -484,26 +501,33 @@ public final class BinaryDictionary extends Dictionary { } // Flush to dict file if the dictionary has been updated. - public void flush() { - if (!isValidDictionary()) return; + public boolean flush() { + if (!isValidDictionary()) return false; if (mHasUpdated) { - flushNative(mNativeDict, mDictFilePath); + if (!flushNative(mNativeDict, mDictFilePath)) { + return false; + } reopen(); } + return true; } // Run GC and flush to dict file if the dictionary has been updated. - public void flushWithGCIfHasUpdated() { + public boolean flushWithGCIfHasUpdated() { if (mHasUpdated) { - flushWithGC(); + return flushWithGC(); } + return true; } // Run GC and flush to dict file. - public void flushWithGC() { - if (!isValidDictionary()) return; - flushWithGCNative(mNativeDict, mDictFilePath); + public boolean flushWithGC() { + if (!isValidDictionary()) return false; + if (!flushWithGCNative(mNativeDict, mDictFilePath)) { + return false; + } reopen(); + return true; } /** diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java index 72757e086..10b1f1b77 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryFileDumper.java @@ -29,7 +29,6 @@ import android.util.Log; import com.android.inputmethod.dictionarypack.DictionaryPackConstants; import com.android.inputmethod.dictionarypack.MD5Calculator; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.DictionaryInfoUtils; import com.android.inputmethod.latin.utils.DictionaryInfoUtils.DictionaryInfo; import com.android.inputmethod.latin.utils.FileTransforms; @@ -44,8 +43,8 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; @@ -165,7 +164,7 @@ public final class BinaryDictionaryFileDumper { if (cursor.getCount() <= 0 || !cursor.moveToFirst()) { return Collections.<WordListInfo>emptyList(); } - final ArrayList<WordListInfo> list = CollectionUtils.newArrayList(); + final ArrayList<WordListInfo> list = new ArrayList<>(); do { final String wordListId = cursor.getString(0); final String wordListLocale = cursor.getString(1); diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java index 4c49cb31c..867c18686 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionaryGetter.java @@ -24,7 +24,6 @@ import android.util.Log; import com.android.inputmethod.latin.makedict.DictionaryHeader; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.DictionaryInfoUtils; import com.android.inputmethod.latin.utils.LocaleUtils; @@ -160,7 +159,7 @@ final public class BinaryDictionaryGetter { public static File[] getCachedWordLists(final String locale, final Context context) { final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context); if (null == directoryList) return EMPTY_FILE_ARRAY; - final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap(); + final HashMap<String, FileAndMatchLevel> cacheFiles = new HashMap<>(); for (File directory : directoryList) { if (!directory.isDirectory()) continue; final String dirLocale = @@ -273,7 +272,7 @@ final public class BinaryDictionaryGetter { final DictPackSettings dictPackSettings = new DictPackSettings(context); boolean foundMainDict = false; - final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList(); + final ArrayList<AssetFileAddress> fileList = new ArrayList<>(); // cachedWordLists may not be null, see doc for getCachedDictionaryList for (final File f : cachedWordLists) { final String wordListId = DictionaryInfoUtils.getWordListIdFromFileName(f.getName()); diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index 67ca59540..35012a452 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -158,6 +158,10 @@ public final class Constants { // A hint on how many characters to cache from the TextView. A good value of this is given by // how many characters we need to be able to almost always find the caps mode. public static final int EDITOR_CONTENTS_CACHE_SIZE = 1024; + // How many characters we accept for the recapitalization functionality. This needs to be + // large enough for all reasonable purposes, but avoid purposeful attacks. 100k sounds about + // right for this. + public static final int MAX_CHARACTERS_FOR_RECAPITALIZATION = 1024 * 100; // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h public static final int DICTIONARY_MAX_WORD_LENGTH = 48; @@ -192,7 +196,6 @@ public final class Constants { public static final int CODE_SPACE = ' '; public static final int CODE_PERIOD = '.'; public static final int CODE_COMMA = ','; - public static final int CODE_ARMENIAN_PERIOD = 0x0589; public static final int CODE_DASH = '-'; public static final int CODE_SINGLE_QUOTE = '\''; public static final int CODE_DOUBLE_QUOTE = '"'; @@ -208,6 +211,8 @@ public final class Constants { public static final int CODE_CLOSING_SQUARE_BRACKET = ']'; public static final int CODE_CLOSING_CURLY_BRACKET = '}'; public static final int CODE_CLOSING_ANGLE_BRACKET = '>'; + public static final int CODE_INVERTED_QUESTION_MARK = 0xBF; // ¿ + public static final int CODE_INVERTED_EXCLAMATION_MARK = 0xA1; // ¡ /** * Special keys code. Must be negative. @@ -249,14 +254,16 @@ public final class Constants { case CODE_LANGUAGE_SWITCH: return "languageSwitch"; case CODE_EMOJI: return "emoji"; case CODE_SHIFT_ENTER: return "shiftEnter"; + case CODE_ALPHA_FROM_EMOJI: return "alpha"; case CODE_UNSPECIFIED: return "unspec"; case CODE_TAB: return "tab"; case CODE_ENTER: return "enter"; - case CODE_ALPHA_FROM_EMOJI: return "alpha"; + case CODE_SPACE: return "space"; default: - if (code < CODE_SPACE) return String.format("'\\u%02x'", code); - if (code < 0x100) return String.format("'%c'", code); - return String.format("'\\u%04x'", code); + if (code < CODE_SPACE) return String.format("\\u%02x", code); + if (code < 0x100) return String.format("%c", code); + if (code < 0x10000) return String.format("\\u04x", code); + return String.format("\\U%05x", code); } } diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index 3fb76b142..dd5b376a1 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -31,7 +31,6 @@ import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.personalization.AccountUtils; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.StringUtils; @@ -180,7 +179,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { private void addWordsLocked(final Cursor cursor) { int count = 0; - final ArrayList<String> names = CollectionUtils.newArrayList(); + final ArrayList<String> names = new ArrayList<>(); while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) { String name = cursor.getString(INDEX_NAME); if (isValidName(name)) { @@ -224,7 +223,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { */ private void addNameLocked(final String name) { int len = StringUtils.codePointCount(name); - PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null); + PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; // TODO: Better tokenization for non-Latin writing systems for (int i = 0; i < len; i++) { if (Character.isLetter(name.codePointAt(i))) { @@ -298,7 +297,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { if (null == cursor) { return false; } - final ArrayList<String> names = CollectionUtils.newArrayList(); + final ArrayList<String> names = new ArrayList<>(); try { if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { diff --git a/java/src/com/android/inputmethod/latin/Dictionary.java b/java/src/com/android/inputmethod/latin/Dictionary.java index aab16653e..b55ed125f 100644 --- a/java/src/com/android/inputmethod/latin/Dictionary.java +++ b/java/src/com/android/inputmethod/latin/Dictionary.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; @@ -85,16 +86,28 @@ public abstract class Dictionary { final int sessionId, final float[] inOutLanguageWeight); /** - * Checks if the given word occurs in the dictionary + * Checks if the given word has to be treated as a valid word. Please note that some + * dictionaries have entries that should be treated as invalid words. * @param word the word to search for. The search should be case-insensitive. - * @return true if the word exists, false otherwise + * @return true if the word is valid, false otherwise */ - abstract public boolean isValidWord(final String word); + public boolean isValidWord(final String word) { + return isInDictionary(word); + } + + /** + * Checks if the given word is in the dictionary regardless of it being valid or not. + */ + abstract public boolean isInDictionary(final String word); public int getFrequency(final String word) { return NOT_A_PROBABILITY; } + public int getMaxFrequencyOfExactMatches(final String word) { + return NOT_A_PROBABILITY; + } + /** * Compares the contents of the character array with the typed word and returns true if they * are the same. @@ -161,7 +174,7 @@ public abstract class Dictionary { } @Override - public boolean isValidWord(String word) { + public boolean isInDictionary(String word) { return false; } } diff --git a/java/src/com/android/inputmethod/latin/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index e6e4e0938..89d61ce2a 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryCollection.java +++ b/java/src/com/android/inputmethod/latin/DictionaryCollection.java @@ -20,7 +20,6 @@ import android.util.Log; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.Collection; @@ -36,22 +35,22 @@ public final class DictionaryCollection extends Dictionary { public DictionaryCollection(final String dictType) { super(dictType); - mDictionaries = CollectionUtils.newCopyOnWriteArrayList(); + mDictionaries = new CopyOnWriteArrayList<>(); } public DictionaryCollection(final String dictType, final Dictionary... dictionaries) { super(dictType); if (null == dictionaries) { - mDictionaries = CollectionUtils.newCopyOnWriteArrayList(); + mDictionaries = new CopyOnWriteArrayList<>(); } else { - mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries); + mDictionaries = new CopyOnWriteArrayList<>(dictionaries); mDictionaries.removeAll(Collections.singleton(null)); } } public DictionaryCollection(final String dictType, final Collection<Dictionary> dictionaries) { super(dictType); - mDictionaries = CollectionUtils.newCopyOnWriteArrayList(dictionaries); + mDictionaries = new CopyOnWriteArrayList<>(dictionaries); mDictionaries.removeAll(Collections.singleton(null)); } @@ -67,7 +66,7 @@ public final class DictionaryCollection extends Dictionary { ArrayList<SuggestedWordInfo> suggestions = dictionaries.get(0).getSuggestions(composer, prevWordsInfo, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId, inOutLanguageWeight); - if (null == suggestions) suggestions = CollectionUtils.newArrayList(); + if (null == suggestions) suggestions = new ArrayList<>(); final int length = dictionaries.size(); for (int i = 1; i < length; ++ i) { final ArrayList<SuggestedWordInfo> sugg = dictionaries.get(i).getSuggestions(composer, @@ -79,9 +78,9 @@ public final class DictionaryCollection extends Dictionary { } @Override - public boolean isValidWord(final String word) { + public boolean isInDictionary(final String word) { for (int i = mDictionaries.size() - 1; i >= 0; --i) - if (mDictionaries.get(i).isValidWord(word)) return true; + if (mDictionaries.get(i).isInDictionary(word)) return true; return false; } @@ -90,9 +89,17 @@ public final class DictionaryCollection extends Dictionary { int maxFreq = -1; for (int i = mDictionaries.size() - 1; i >= 0; --i) { final int tempFreq = mDictionaries.get(i).getFrequency(word); - if (tempFreq >= maxFreq) { - maxFreq = tempFreq; - } + maxFreq = Math.max(tempFreq, maxFreq); + } + return maxFreq; + } + + @Override + public int getMaxFrequencyOfExactMatches(final String word) { + int maxFreq = -1; + for (int i = mDictionaries.size() - 1; i >= 0; --i) { + final int tempFreq = mDictionaries.get(i).getMaxFrequencyOfExactMatches(word); + maxFreq = Math.max(tempFreq, maxFreq); } return maxFreq; } diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index 301b832b6..09401c0c6 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -19,14 +19,18 @@ package com.android.inputmethod.latin; import android.content.Context; import android.text.TextUtils; import android.util.Log; +import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.personalization.ContextualDictionary; +import com.android.inputmethod.latin.personalization.PersonalizationDataChunk; import com.android.inputmethod.latin.personalization.PersonalizationDictionary; import com.android.inputmethod.latin.personalization.UserHistoryDictionary; -import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.settings.SpacingAndPunctuations; +import com.android.inputmethod.latin.utils.DistracterFilter; +import com.android.inputmethod.latin.utils.DistracterFilterCheckingIsInDictionary; import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; import com.android.inputmethod.latin.utils.SuggestionResults; @@ -37,16 +41,17 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; // TODO: Consolidate dictionaries in native code. -public class DictionaryFacilitatorForSuggest { - public static final String TAG = DictionaryFacilitatorForSuggest.class.getSimpleName(); +public class DictionaryFacilitator { + public static final String TAG = DictionaryFacilitator.class.getSimpleName(); // HACK: This threshold is being used when adding a capitalized entry in the User History // dictionary. @@ -57,8 +62,9 @@ public class DictionaryFacilitatorForSuggest { private volatile CountDownLatch mLatchForWaitingLoadingMainDictionary = new CountDownLatch(0); // To synchronize assigning mDictionaries to ensure closing dictionaries. private final Object mLock = new Object(); + private final DistracterFilter mDistracterFilter; - private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTION = + private static final String[] DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS = new String[] { Dictionary.TYPE_MAIN, Dictionary.TYPE_USER_HISTORY, @@ -68,8 +74,8 @@ public class DictionaryFacilitatorForSuggest { Dictionary.TYPE_CONTEXTUAL }; - private static final Map<String, Class<? extends ExpandableBinaryDictionary>> - DICT_TYPE_TO_CLASS = CollectionUtils.newHashMap(); + public static final Map<String, Class<? extends ExpandableBinaryDictionary>> + DICT_TYPE_TO_CLASS = new HashMap<>(); static { DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class); @@ -84,8 +90,8 @@ public class DictionaryFacilitatorForSuggest { new Class[] { Context.class, Locale.class, File.class }; private static final String[] SUB_DICT_TYPES = - Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTION, 1 /* start */, - DICT_TYPES_ORDERED_TO_GET_SUGGESTION.length); + Arrays.copyOfRange(DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS, 1 /* start */, + DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS.length); /** * Class contains dictionaries for a locale. @@ -94,7 +100,7 @@ public class DictionaryFacilitatorForSuggest { public final Locale mLocale; private Dictionary mMainDict; public final ConcurrentHashMap<String, ExpandableBinaryDictionary> mSubDictMap = - CollectionUtils.newConcurrentHashMap(); + new ConcurrentHashMap<>(); public Dictionaries() { mLocale = null; @@ -162,7 +168,17 @@ public class DictionaryFacilitatorForSuggest { public void onUpdateMainDictionaryAvailability(boolean isMainDictionaryAvailable); } - public DictionaryFacilitatorForSuggest() {} + public DictionaryFacilitator() { + mDistracterFilter = DistracterFilter.EMPTY_DISTRACTER_FILTER; + } + + public DictionaryFacilitator(final DistracterFilter distracterFilter) { + mDistracterFilter = distracterFilter; + } + + public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) { + mDistracterFilter.updateEnabledSubtypes(enabledSubtypes); + } public Locale getLocale() { return mDictionaries.mLocale; @@ -196,7 +212,7 @@ public class DictionaryFacilitatorForSuggest { // We always try to have the main dictionary. Other dictionaries can be unused. final boolean reloadMainDictionary = localeHasBeenChanged || forceReloadMainDictionary; // TODO: Make subDictTypesToUse configurable by resource or a static final list. - final Set<String> subDictTypesToUse = CollectionUtils.newHashSet(); + final HashSet<String> subDictTypesToUse = new HashSet<>(); if (useContactsDict) { subDictTypesToUse.add(Dictionary.TYPE_CONTACTS); } @@ -215,7 +231,7 @@ public class DictionaryFacilitatorForSuggest { newMainDict = mDictionaries.getDict(Dictionary.TYPE_MAIN); } - final Map<String, ExpandableBinaryDictionary> subDicts = CollectionUtils.newHashMap(); + final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>(); for (final String dictType : SUB_DICT_TYPES) { if (!subDictTypesToUse.contains(dictType)) { // This dictionary will not be used. @@ -288,7 +304,7 @@ public class DictionaryFacilitatorForSuggest { final ArrayList<String> dictionaryTypes, final HashMap<String, File> dictionaryFiles, final Map<String, Map<String, String>> additionalDictAttributes) { Dictionary mainDictionary = null; - final Map<String, ExpandableBinaryDictionary> subDicts = CollectionUtils.newHashMap(); + final Map<String, ExpandableBinaryDictionary> subDicts = new HashMap<>(); for (final String dictType : dictionaryTypes) { if (dictType.equals(Dictionary.TYPE_MAIN)) { @@ -318,9 +334,10 @@ public class DictionaryFacilitatorForSuggest { dictionaries = mDictionaries; mDictionaries = new Dictionaries(); } - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { dictionaries.closeDict(dictType); } + mDistracterFilter.close(); } // The main dictionary could have been loaded asynchronously. Don't cache the return value @@ -392,7 +409,7 @@ public class DictionaryFacilitatorForSuggest { if (userHistoryDictionary == null) { return; } - final int maxFreq = getMaxFrequency(word); + final int maxFreq = getFrequency(word); if (maxFreq == 0 && blockPotentiallyOffensive) { return; } @@ -432,7 +449,7 @@ public class DictionaryFacilitatorForSuggest { // We don't add words with 0-frequency (assuming they would be profanity etc.). final boolean isValid = maxFreq > 0; UserHistoryDictionary.addToDictionary(userHistoryDictionary, prevWordsInfo, secondWord, - isValid, timeStampInSeconds); + isValid, timeStampInSeconds, mDistracterFilter); } public void cancelAddingUserHistory(final PrevWordsInfo prevWordsInfo, @@ -453,7 +470,7 @@ public class DictionaryFacilitatorForSuggest { final SuggestionResults suggestionResults = new SuggestionResults(dictionaries.mLocale, SuggestedWords.MAX_SUGGESTIONS); final float[] languageWeight = new float[] { Dictionary.NOT_A_LANGUAGE_WEIGHT }; - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { final Dictionary dictionary = dictionaries.getDict(dictType); if (null == dictionary) continue; final ArrayList<SuggestedWordInfo> dictionarySuggestions = @@ -486,7 +503,7 @@ public class DictionaryFacilitatorForSuggest { return false; } final String lowerCasedWord = word.toLowerCase(dictionaries.mLocale); - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { final Dictionary dictionary = dictionaries.getDict(dictType); // Ideally the passed map would come out of a {@link java.util.concurrent.Future} and // would be immutable once it's finished initializing, but concretely a null test is @@ -500,16 +517,22 @@ public class DictionaryFacilitatorForSuggest { return false; } - private int getMaxFrequency(final String word) { + private int getFrequencyInternal(final String word, + final boolean isGettingMaxFrequencyOfExactMatches) { if (TextUtils.isEmpty(word)) { return Dictionary.NOT_A_PROBABILITY; } - int maxFreq = -1; + int maxFreq = Dictionary.NOT_A_PROBABILITY; final Dictionaries dictionaries = mDictionaries; - for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { + for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTIONS) { final Dictionary dictionary = dictionaries.getDict(dictType); if (dictionary == null) continue; - final int tempFreq = dictionary.getFrequency(word); + final int tempFreq; + if (isGettingMaxFrequencyOfExactMatches) { + tempFreq = dictionary.getMaxFrequencyOfExactMatches(word); + } else { + tempFreq = dictionary.getFrequency(word); + } if (tempFreq >= maxFreq) { maxFreq = tempFreq; } @@ -517,6 +540,14 @@ public class DictionaryFacilitatorForSuggest { return maxFreq; } + public int getFrequency(final String word) { + return getFrequencyInternal(word, false /* isGettingMaxFrequencyOfExactMatches */); + } + + public int getMaxFrequencyOfExactMatches(final String word) { + return getFrequencyInternal(word, true /* isGettingMaxFrequencyOfExactMatches */); + } + public void clearUserHistoryDictionary() { final ExpandableBinaryDictionary userHistoryDict = mDictionaries.getSubDict(Dictionary.TYPE_USER_HISTORY); @@ -537,13 +568,26 @@ public class DictionaryFacilitatorForSuggest { personalizationDict.clear(); } - public void addMultipleDictionaryEntriesToPersonalizationDictionary( - final ArrayList<LanguageModelParam> languageModelParams, + public void addEntriesToPersonalizationDictionary( + final PersonalizationDataChunk personalizationDataChunk, + final SpacingAndPunctuations spacingAndPunctuations, final ExpandableBinaryDictionary.AddMultipleDictionaryEntriesCallback callback) { final ExpandableBinaryDictionary personalizationDict = mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); - if (personalizationDict == null || languageModelParams == null - || languageModelParams.isEmpty()) { + if (personalizationDict == null) { + if (callback != null) { + callback.onFinished(); + } + return; + } + final ArrayList<LanguageModelParam> languageModelParams = + LanguageModelParam.createLanguageModelParamsFrom( + personalizationDataChunk.mTokens, + personalizationDataChunk.mTimestampInSeconds, + this /* dictionaryFacilitator */, spacingAndPunctuations, + new DistracterFilterCheckingIsInDictionary( + mDistracterFilter, personalizationDict)); + if (languageModelParams == null || languageModelParams.isEmpty()) { if (callback != null) { callback.onFinished(); } diff --git a/java/src/com/android/inputmethod/latin/DictionaryFactory.java b/java/src/com/android/inputmethod/latin/DictionaryFactory.java index e09c309ea..59de4f82a 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFactory.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFactory.java @@ -23,7 +23,6 @@ import android.content.res.Resources; import android.util.Log; import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.DictionaryInfoUtils; import java.io.File; @@ -55,7 +54,7 @@ public final class DictionaryFactory { createReadOnlyBinaryDictionary(context, locale)); } - final LinkedList<Dictionary> dictList = CollectionUtils.newLinkedList(); + final LinkedList<Dictionary> dictList = new LinkedList<>(); final ArrayList<AssetFileAddress> assetFileList = BinaryDictionaryGetter.getDictionaryFiles(locale, context); if (null != assetFileList) { diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index d67253c3b..4dbfa0bac 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -27,6 +27,7 @@ import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.WordProperty; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.utils.CombinedFormatUtils; +import com.android.inputmethod.latin.utils.DistracterFilter; import com.android.inputmethod.latin.utils.ExecutorUtils; import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; @@ -48,12 +49,12 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * queries in native code. This binary dictionary is written to internal storage. */ abstract public class ExpandableBinaryDictionary extends Dictionary { + private static final boolean DEBUG = false; /** Used for Log actions from this class */ private static final String TAG = ExpandableBinaryDictionary.class.getSimpleName(); /** Whether to print debug output to log */ - private static boolean DEBUG = false; private static final boolean DBG_STRESS_TEST = false; private static final int TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS = 100; @@ -191,7 +192,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } protected Map<String, String> getHeaderAttributeMap() { - HashMap<String, String> attributeMap = new HashMap<String, String>(); + HashMap<String, String> attributeMap = new HashMap<>(); if (mAdditionalAttributeMap != null) { attributeMap.putAll(mAdditionalAttributeMap); } @@ -271,9 +272,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { /** * Adds unigram information of a word to the dictionary. May overwrite an existing entry. */ - public void addUnigramEntry(final String word, final int frequency, + public void addUnigramEntryWithCheckingDistracter(final String word, final int frequency, final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord, - final boolean isBlacklisted, final int timestamp) { + final boolean isBlacklisted, final int timestamp, + final DistracterFilter distracterFilter) { reloadDictionaryIfRequired(); asyncExecuteTaskWithWriteLock(new Runnable() { @Override @@ -281,6 +283,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { if (mBinaryDictionary == null) { return; } + if (distracterFilter.isDistracterToWordsInDictionaries( + PrevWordsInfo.EMPTY_PREV_WORDS_INFO, word, mLocale)) { + // The word is a distracter. + return; + } runGCIfRequiredLocked(true /* mindsBlockByGC */); addUnigramLocked(word, frequency, shortcutTarget, shortcutFreq, isNotAWord, isBlacklisted, timestamp); @@ -291,8 +298,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected void addUnigramLocked(final String word, final int frequency, final String shortcutTarget, final int shortcutFreq, final boolean isNotAWord, final boolean isBlacklisted, final int timestamp) { - mBinaryDictionary.addUnigramEntry(word, frequency, shortcutTarget, shortcutFreq, - isNotAWord, isBlacklisted, timestamp); + if (!mBinaryDictionary.addUnigramEntry(word, frequency, shortcutTarget, shortcutFreq, + false /* isBeginningOfSentence */, isNotAWord, isBlacklisted, timestamp)) { + Log.e(TAG, "Cannot add unigram entry. word: " + word); + } } /** @@ -315,13 +324,18 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected void addNgramEntryLocked(final PrevWordsInfo prevWordsInfo, final String word, final int frequency, final int timestamp) { - mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp); + if (!mBinaryDictionary.addNgramEntry(prevWordsInfo, word, frequency, timestamp)) { + if (DEBUG) { + Log.i(TAG, "Cannot add n-gram entry."); + Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word); + } + } } /** * Dynamically remove the n-gram entry in the dictionary. */ - public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word1) { + public void removeNgramDynamically(final PrevWordsInfo prevWordsInfo, final String word) { reloadDictionaryIfRequired(); asyncExecuteTaskWithWriteLock(new Runnable() { @Override @@ -330,7 +344,12 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return; } runGCIfRequiredLocked(true /* mindsBlockByGC */); - mBinaryDictionary.removeNgramEntry(prevWordsInfo, word1); + if (!mBinaryDictionary.removeNgramEntry(prevWordsInfo, word)) { + if (DEBUG) { + Log.i(TAG, "Cannot remove n-gram entry."); + Log.i(TAG, " PrevWordsInfo: " + prevWordsInfo + ", word: " + word); + } + } } }); } @@ -401,7 +420,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } @Override - public boolean isValidWord(final String word) { + public boolean isInDictionary(final String word) { reloadDictionaryIfRequired(); boolean lockAcquired = false; try { @@ -411,10 +430,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { if (mBinaryDictionary == null) { return false; } - return isValidWordLocked(word); + return isInDictionaryLocked(word); } } catch (final InterruptedException e) { - Log.e(TAG, "Interrupted tryLock() in isValidWord().", e); + Log.e(TAG, "Interrupted tryLock() in isInDictionary().", e); } finally { if (lockAcquired) { mLock.readLock().unlock(); @@ -423,11 +442,35 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return false; } - protected boolean isValidWordLocked(final String word) { + protected boolean isInDictionaryLocked(final String word) { if (mBinaryDictionary == null) return false; - return mBinaryDictionary.isValidWord(word); + return mBinaryDictionary.isInDictionary(word); + } + + @Override + public int getMaxFrequencyOfExactMatches(final String word) { + reloadDictionaryIfRequired(); + boolean lockAcquired = false; + try { + lockAcquired = mLock.readLock().tryLock( + TIMEOUT_FOR_READ_OPS_IN_MILLISECONDS, TimeUnit.MILLISECONDS); + if (lockAcquired) { + if (mBinaryDictionary == null) { + return NOT_A_PROBABILITY; + } + return mBinaryDictionary.getMaxFrequencyOfExactMatches(word); + } + } catch (final InterruptedException e) { + Log.e(TAG, "Interrupted tryLock() in getMaxFrequencyOfExactMatches().", e); + } finally { + if (lockAcquired) { + mLock.readLock().unlock(); + } + } + return NOT_A_PROBABILITY; } + protected boolean isValidNgramLocked(final PrevWordsInfo prevWordsInfo, final String word) { if (mBinaryDictionary == null) return false; return mBinaryDictionary.isValidNgram(prevWordsInfo, word); @@ -553,20 +596,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { }); } - // TODO: Implement BinaryDictionary.isInDictionary(). - @UsedForTesting - public boolean isInUnderlyingBinaryDictionaryForTests(final String word) { - mLock.readLock().lock(); - try { - if (mBinaryDictionary != null && mDictType == Dictionary.TYPE_USER_HISTORY) { - return mBinaryDictionary.isValidWord(word); - } - return false; - } finally { - mLock.readLock().unlock(); - } - } - @UsedForTesting public void waitAllTasksForTests() { final CountDownLatch countDownLatch = new CountDownLatch(1); diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index df4948322..e1ae3dfe3 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -16,11 +16,13 @@ package com.android.inputmethod.latin; +import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE; +import static com.android.inputmethod.latin.Constants.ImeOption.NO_MICROPHONE_COMPAT; + import android.text.InputType; import android.util.Log; import android.view.inputmethod.EditorInfo; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.InputTypeUtils; import com.android.inputmethod.latin.utils.StringUtils; @@ -36,12 +38,17 @@ public final class InputAttributes { final public String mTargetApplicationPackageName; final public boolean mInputTypeNoAutoCorrect; final public boolean mIsPasswordField; - final public boolean mIsSettingsSuggestionStripOn; + final public boolean mShouldShowSuggestions; final public boolean mApplicationSpecifiedCompletionOn; final public boolean mShouldInsertSpacesAutomatically; final private int mInputType; + final private EditorInfo mEditorInfo; + final private String mPackageNameForPrivateImeOptions; - public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode) { + public InputAttributes(final EditorInfo editorInfo, final boolean isFullscreenMode, + final String packageNameForPrivateImeOptions) { + mEditorInfo = editorInfo; + mPackageNameForPrivateImeOptions = packageNameForPrivateImeOptions; mTargetApplicationPackageName = null != editorInfo ? editorInfo.packageName : null; final int inputType = null != editorInfo ? editorInfo.inputType : 0; final int inputClass = inputType & InputType.TYPE_MASK_CLASS; @@ -63,7 +70,7 @@ public final class InputAttributes { Log.w(TAG, String.format("Unexpected input class: inputType=0x%08x" + " imeOptions=0x%08x", inputType, editorInfo.imeOptions)); } - mIsSettingsSuggestionStripOn = false; + mShouldShowSuggestions = false; mInputTypeNoAutoCorrect = false; mApplicationSpecifiedCompletionOn = false; mShouldInsertSpacesAutomatically = false; @@ -82,13 +89,13 @@ public final class InputAttributes { // TODO: Have a helper method in InputTypeUtils // Make sure that passwords are not displayed in {@link SuggestionStripView}. - final boolean noSuggestionStrip = mIsPasswordField + final boolean shouldSuppressSuggestions = mIsPasswordField || InputTypeUtils.isEmailVariation(variation) || InputType.TYPE_TEXT_VARIATION_URI == variation || InputType.TYPE_TEXT_VARIATION_FILTER == variation || flagNoSuggestions || flagAutoComplete; - mIsSettingsSuggestionStripOn = !noSuggestionStrip; + mShouldShowSuggestions = !shouldSuppressSuggestions; mShouldInsertSpacesAutomatically = InputTypeUtils.isAutoSpaceFriendlyType(inputType); @@ -112,6 +119,15 @@ public final class InputAttributes { return editorInfo.inputType == mInputType; } + public boolean hasNoMicrophoneKeyOption() { + @SuppressWarnings("deprecation") + final boolean deprecatedNoMicrophone = InputAttributes.inPrivateImeOptions( + null, NO_MICROPHONE_COMPAT, mEditorInfo); + final boolean noMicrophone = InputAttributes.inPrivateImeOptions( + mPackageNameForPrivateImeOptions, NO_MICROPHONE, mEditorInfo); + return noMicrophone || deprecatedNoMicrophone; + } + @SuppressWarnings("unused") private void dumpFlags(final int inputType) { final int inputClass = inputType & InputType.TYPE_MASK_CLASS; @@ -214,7 +230,7 @@ public final class InputAttributes { } private static String toFlagsString(final int flags) { - final ArrayList<String> flagsArray = CollectionUtils.newArrayList(); + final ArrayList<String> flagsArray = new ArrayList<>(); if (0 != (flags & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS)) flagsArray.add("TYPE_TEXT_FLAG_NO_SUGGESTIONS"); if (0 != (flags & InputType.TYPE_TEXT_FLAG_MULTI_LINE)) @@ -242,7 +258,7 @@ public final class InputAttributes { mInputType, (mInputTypeNoAutoCorrect ? " noAutoCorrect" : ""), (mIsPasswordField ? " password" : ""), - (mIsSettingsSuggestionStripOn ? " suggestionStrip" : ""), + (mShouldShowSuggestions ? " shouldShowSuggestions" : ""), (mApplicationSpecifiedCompletionOn ? " appSpecified" : ""), (mShouldInsertSpacesAutomatically ? " insertSpaces" : ""), mTargetApplicationPackageName); diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java index ea7859e60..0801cfa88 100644 --- a/java/src/com/android/inputmethod/latin/InputView.java +++ b/java/src/com/android/inputmethod/latin/InputView.java @@ -23,12 +23,14 @@ import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; +import com.android.inputmethod.accessibility.AccessibilityUtils; import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.latin.suggestions.MoreSuggestionsView; import com.android.inputmethod.latin.suggestions.SuggestionStripView; public final class InputView extends LinearLayout { private final Rect mInputViewRect = new Rect(); + private MainKeyboardView mMainKeyboardView; private KeyboardTopPaddingForwarder mKeyboardTopPaddingForwarder; private MoreSuggestionsViewCanceler mMoreSuggestionsViewCanceler; private MotionEventForwarder<?, ?> mActiveForwarder; @@ -41,12 +43,11 @@ public final class InputView extends LinearLayout { protected void onFinishInflate() { final SuggestionStripView suggestionStripView = (SuggestionStripView)findViewById(R.id.suggestion_strip_view); - final MainKeyboardView mainKeyboardView = - (MainKeyboardView)findViewById(R.id.keyboard_view); + mMainKeyboardView = (MainKeyboardView)findViewById(R.id.keyboard_view); mKeyboardTopPaddingForwarder = new KeyboardTopPaddingForwarder( - mainKeyboardView, suggestionStripView); + mMainKeyboardView, suggestionStripView); mMoreSuggestionsViewCanceler = new MoreSuggestionsViewCanceler( - mainKeyboardView, suggestionStripView); + mMainKeyboardView, suggestionStripView); } public void setKeyboardTopPadding(final int keyboardTopPadding) { @@ -54,6 +55,17 @@ public final class InputView extends LinearLayout { } @Override + protected boolean dispatchHoverEvent(final MotionEvent event) { + if (AccessibilityUtils.getInstance().isTouchExplorationEnabled() + && mMainKeyboardView.isShowingMoreKeysPanel()) { + // With accessibility mode on, discard hover events while a more keys keyboard is shown. + // The {@link MoreKeysKeyboard} receives hover events directly from the platform. + return true; + } + return super.dispatchHoverEvent(event); + } + + @Override public boolean onInterceptTouchEvent(final MotionEvent me) { final Rect rect = mInputViewRect; getGlobalVisibleRect(rect); diff --git a/java/src/com/android/inputmethod/latin/LastComposedWord.java b/java/src/com/android/inputmethod/latin/LastComposedWord.java index 9caec3e01..8cbf8379b 100644 --- a/java/src/com/android/inputmethod/latin/LastComposedWord.java +++ b/java/src/com/android/inputmethod/latin/LastComposedWord.java @@ -69,7 +69,7 @@ public final class LastComposedWord { mInputPointers.copy(inputPointers); } mTypedWord = typedWord; - mEvents = new ArrayList<Event>(events); + mEvents = new ArrayList<>(events); mCommittedWord = committedWord; mSeparatorString = separatorString; mActive = true; diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index ab7e66a09..35966bb71 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -28,7 +28,6 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; @@ -38,7 +37,6 @@ import android.net.ConnectivityManager; import android.os.Debug; import android.os.IBinder; import android.os.Message; -import android.preference.PreferenceManager; import android.text.InputType; import android.text.TextUtils; import android.util.Log; @@ -83,18 +81,18 @@ import com.android.inputmethod.latin.utils.ApplicationUtils; import com.android.inputmethod.latin.utils.CapsModeUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.DialogUtils; -import com.android.inputmethod.latin.utils.DistracterFilter; +import com.android.inputmethod.latin.utils.DistracterFilterCheckingExactMatches; import com.android.inputmethod.latin.utils.ImportantNoticeUtils; import com.android.inputmethod.latin.utils.IntentUtils; import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; import com.android.inputmethod.latin.utils.StatsUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; -import com.android.inputmethod.research.ResearchLogger; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -103,7 +101,7 @@ import java.util.concurrent.TimeUnit; */ public class LatinIME extends InputMethodService implements KeyboardActionListener, SuggestionStripView.Listener, SuggestionStripViewAccessor, - DictionaryFacilitatorForSuggest.DictionaryInitializationListener, + DictionaryFacilitator.DictionaryInitializationListener, ImportantNoticeDialog.ImportantNoticeDialogListener { private static final String TAG = LatinIME.class.getSimpleName(); private static final boolean TRACE = false; @@ -122,12 +120,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final String SCHEME_PACKAGE = "package"; private final Settings mSettings; + private final DictionaryFacilitator mDictionaryFacilitator = + new DictionaryFacilitator(new DistracterFilterCheckingExactMatches(this /* context */)); private final InputLogic mInputLogic = new InputLogic(this /* LatinIME */, - this /* SuggestionStripViewAccessor */); + this /* SuggestionStripViewAccessor */, mDictionaryFacilitator); // We expect to have only one decoder in almost all cases, hence the default capacity of 1. // If it turns out we need several, it will get grown seamlessly. - final SparseArray<HardwareEventDecoder> mHardwareEventDecoders - = new SparseArray<HardwareEventDecoder>(1); + final SparseArray<HardwareEventDecoder> mHardwareEventDecoders = new SparseArray<>(1); private View mExtractArea; private View mKeyPreviewBackingView; @@ -167,6 +166,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private static final int ARG1_DISMISS_GESTURE_FLOATING_PREVIEW_TEXT = 1; private static final int ARG1_SHOW_GESTURE_FLOATING_PREVIEW_TEXT = 2; private static final int ARG2_UNUSED = 0; + private static final int ARG1_FALSE = 0; + private static final int ARG1_TRUE = 1; private int mDelayUpdateSuggestions; private int mDelayUpdateShiftState; @@ -214,7 +215,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen case MSG_RESUME_SUGGESTIONS: latinIme.mInputLogic.restartSuggestionsOnWordTouchedByCursor( latinIme.mSettings.getCurrent(), - false /* includeResumedWordInSuggestions */); + msg.arg1 == ARG1_TRUE /* shouldIncludeResumedWordInSuggestions */); break; case MSG_REOPEN_DICTIONARIES: latinIme.resetSuggest(); @@ -251,16 +252,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen sendMessage(obtainMessage(MSG_REOPEN_DICTIONARIES)); } - public void postResumeSuggestions() { + public void postResumeSuggestions(final boolean shouldIncludeResumedWordInSuggestions) { final LatinIME latinIme = getOwnerInstance(); if (latinIme == null) { return; } - if (!latinIme.mSettings.getCurrent().isSuggestionStripVisible()) { + if (!latinIme.mSettings.getCurrent() + .isCurrentOrientationAllowingSuggestionsPerUserSettings()) { return; } removeMessages(MSG_RESUME_SUGGESTIONS); - sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS), mDelayUpdateSuggestions); + sendMessageDelayed(obtainMessage(MSG_RESUME_SUGGESTIONS, + shouldIncludeResumedWordInSuggestions ? ARG1_TRUE : ARG1_FALSE, + 0 /* ignored */), + mDelayUpdateSuggestions); } public void postResetCaches(final boolean tryResumeSuggestions, final int remainingTries) { @@ -491,12 +496,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen loadSettings(); resetSuggest(); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().init(this, mKeyboardSwitcher); - ResearchLogger.getInstance().initDictionary( - mInputLogic.mSuggest.mDictionaryFacilitator); - } - // Register to receive ringer mode change and network state change. // Also receive installation and removal of a dictionary pack. final IntentFilter filter = new IntentFilter(); @@ -528,7 +527,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen void loadSettings() { final Locale locale = mSubtypeSwitcher.getCurrentSubtypeLocale(); final EditorInfo editorInfo = getCurrentInputEditorInfo(); - final InputAttributes inputAttributes = new InputAttributes(editorInfo, isFullscreenMode()); + final InputAttributes inputAttributes = new InputAttributes( + editorInfo, isFullscreenMode(), getPackageName()); mSettings.loadSettings(this, locale, inputAttributes); final SettingsValues currentSettingsValues = mSettings.getCurrent(); AudioAndHapticFeedbackManager.getInstance().onSettingsChanged(currentSettingsValues); @@ -538,13 +538,13 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!mHandler.hasPendingReopenDictionaries()) { resetSuggestForLocale(locale); } + mDictionaryFacilitator.updateEnabledSubtypes(mRichImm.getMyEnabledInputMethodSubtypeList( + true /* allowsImplicitlySelectedSubtypes */)); refreshPersonalizationDictionarySession(); StatsUtils.onLoadSettings(currentSettingsValues); } private void refreshPersonalizationDictionarySession() { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; final boolean shouldKeepUserHistoryDictionaries; final boolean shouldKeepPersonalizationDictionaries; if (mSettings.getCurrent().mUsePersonalizedDicts) { @@ -559,16 +559,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!shouldKeepUserHistoryDictionaries) { // Remove user history dictionaries. PersonalizationHelper.removeAllUserHistoryDictionaries(this); - dictionaryFacilitator.clearUserHistoryDictionary(); + mDictionaryFacilitator.clearUserHistoryDictionary(); } if (!shouldKeepPersonalizationDictionaries) { // Remove personalization dictionaries. PersonalizationHelper.removeAllPersonalizationDictionaries(this); PersonalizationDictionarySessionRegistrar.resetAll(this); } else { - final DistracterFilter distracterFilter = createDistracterFilter(); - PersonalizationDictionarySessionRegistrar.init( - this, dictionaryFacilitator, distracterFilter); + PersonalizationDictionarySessionRegistrar.init(this, mDictionaryFacilitator); } } @@ -606,13 +604,11 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * @param locale the locale */ private void resetSuggestForLocale(final Locale locale) { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; final SettingsValues settingsValues = mSettings.getCurrent(); - dictionaryFacilitator.resetDictionaries(this /* context */, locale, + mDictionaryFacilitator.resetDictionaries(this /* context */, locale, settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, false /* forceReloadMainDictionary */, this); - if (settingsValues.mCorrectionEnabled) { + if (settingsValues.mAutoCorrectionEnabled) { mInputLogic.mSuggest.setAutoCorrectionThreshold( settingsValues.mAutoCorrectionThreshold); } @@ -622,27 +618,20 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen * Reset suggest by loading the main dictionary of the current locale. */ /* package private */ void resetSuggestMainDict() { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; final SettingsValues settingsValues = mSettings.getCurrent(); - dictionaryFacilitator.resetDictionaries(this /* context */, - dictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict, + mDictionaryFacilitator.resetDictionaries(this /* context */, + mDictionaryFacilitator.getLocale(), settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, true /* forceReloadMainDictionary */, this); } @Override public void onDestroy() { - mInputLogic.mSuggest.mDictionaryFacilitator.closeDictionaries(); + mDictionaryFacilitator.closeDictionaries(); mSettings.onDestroy(); unregisterReceiver(mConnectivityAndRingerModeChangeReceiver); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().onDestroy(); - } unregisterReceiver(mDictionaryPackInstallReceiver); unregisterReceiver(mDictionaryDumpBroadcastReceiver); PersonalizationDictionarySessionRegistrar.close(this); - LatinImeLogger.commit(); - LatinImeLogger.onDestroy(); StatsUtils.onDestroy(); super.onDestroy(); } @@ -657,18 +646,22 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void onConfigurationChanged(final Configuration conf) { - // If orientation changed while predicting, commit the change final SettingsValues settingsValues = mSettings.getCurrent(); if (settingsValues.mDisplayOrientation != conf.orientation) { mHandler.startOrientationChanging(); - mInputLogic.mConnection.beginBatchEdit(); - mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR); - mInputLogic.mConnection.finishComposingText(); - mInputLogic.mConnection.endBatchEdit(); + // If !isComposingWord, #commitTyped() is a no-op, but still, it's better to avoid + // the useless IPC of {begin,end}BatchEdit. + if (mInputLogic.mWordComposer.isComposingWord()) { + mInputLogic.mConnection.beginBatchEdit(); + // If we had a composition in progress, we need to commit the word so that the + // suggestionsSpan will be added. This will allow resuming on the same suggestions + // after rotation is finished. + mInputLogic.commitTyped(mSettings.getCurrent(), LastComposedWord.NOT_A_SEPARATOR); + mInputLogic.mConnection.endBatchEdit(); + } } - final DistracterFilter distracterFilter = createDistracterFilter(); PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf, - mInputLogic.mSuggest.mDictionaryFacilitator, distracterFilter); + mDictionaryFacilitator); super.onConfigurationChanged(conf); } @@ -687,9 +680,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (hasSuggestionStripView()) { mSuggestionStripView.setListener(this, view); } - if (LatinImeLogger.sVISUALDEBUG) { - mKeyPreviewBackingView.setBackgroundColor(0x10FF0000); - } } @Override @@ -762,10 +752,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } Log.i(TAG, "Starting input. Cursor position = " + editorInfo.initialSelStart + "," + editorInfo.initialSelEnd); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - ResearchLogger.latinIME_onStartInputViewInternal(editorInfo, prefs); - } + // TODO: Consolidate these checks with {@link InputAttributes}. if (InputAttributes.inPrivateImeOptions(null, NO_MICROPHONE_COMPAT, editorInfo)) { Log.w(TAG, "Deprecated private IME option specified: " + editorInfo.privateImeOptions); Log.w(TAG, "Use " + getPackageName() + "." + NO_MICROPHONE + " instead"); @@ -775,7 +762,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen Log.w(TAG, "Use EditorInfo.IME_FLAG_FORCE_ASCII flag instead"); } - LatinImeLogger.onStartInputView(editorInfo); // In landscape mode, this method gets called without the input view being created. if (mainKeyboardView == null) { return; @@ -828,7 +814,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // When rotating, initialSelStart and initialSelEnd sometimes are lying. Make a best // effort to work around this bug. mInputLogic.mConnection.tryFixLyingCursorPosition(); - mHandler.postResumeSuggestions(); + mHandler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */); canReachInputConnection = true; } @@ -840,8 +826,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mainKeyboardView.closing(); currentSettingsValues = mSettings.getCurrent(); - if (currentSettingsValues.mCorrectionEnabled) { - suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); + if (currentSettingsValues.mAutoCorrectionEnabled) { + suggest.setAutoCorrectionThreshold( + currentSettingsValues.mAutoCorrectionThreshold); } switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), @@ -870,7 +857,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.cancelUpdateSuggestionStrip(); mainKeyboardView.setMainDictionaryAvailability( - suggest.mDictionaryFacilitator.hasInitializedMainDictionary()); + mDictionaryFacilitator.hasInitializedMainDictionary()); mainKeyboardView.setKeyPreviewPopupEnabled(currentSettingsValues.mKeyPreviewPopupOn, currentSettingsValues.mKeyPreviewPopupDismissDelay); mainKeyboardView.setSlidingKeyInputPreviewEnabled( @@ -895,7 +882,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private void onFinishInputInternal() { super.onFinishInput(); - LatinImeLogger.commit(); final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); if (mainKeyboardView != null) { mainKeyboardView.closing(); @@ -909,10 +895,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mHandler.cancelUpdateSuggestionStrip(); // Should do the following in onFinishInputInternal but until JB MR2 it's not called :( mInputLogic.finishInput(); - // Notify ResearchLogger - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onFinishInputViewInternal(finishingInput); - } } @Override @@ -926,11 +908,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen + ", nss=" + newSelStart + ", nse=" + newSelEnd + ", cs=" + composingSpanStart + ", ce=" + composingSpanEnd); } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onUpdateSelection(oldSelStart, oldSelEnd, - oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart, - composingSpanEnd, mInputLogic.mConnection); - } // If the keyboard is not visible, we don't need to do all the housekeeping work, as it // will be reset when the keyboard shows up anyway. @@ -991,7 +968,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void hideWindow() { - LatinImeLogger.commit(); mKeyboardSwitcher.onHideWindow(); if (TRACE) Debug.stopMethodTracing(); @@ -1017,9 +993,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } if (applicationSpecifiedCompletions == null) { setNeutralSuggestionStrip(); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onDisplayCompletions(null); - } return; } @@ -1030,10 +1003,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen null /* rawSuggestions */, false /* typedWordValid */, false /* willAutoCorrect */, false /* isObsoleteSuggestions */, false /* isPrediction */); // When in fullscreen mode, show completions generated by the application forcibly - setSuggestedWords(suggestedWords, true /* isSuggestionStripVisible */); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onDisplayCompletions(applicationSpecifiedCompletions); - } + setSuggestedWords(suggestedWords); } private int getAdjustedBackingViewHeight() { @@ -1167,8 +1137,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } else { wordToEdit = word; } - mInputLogic.mSuggest.mDictionaryFacilitator.addWordToUserDictionary( - this /* context */, wordToEdit); + mDictionaryFacilitator.addWordToUserDictionary(this /* context */, wordToEdit); } // Callback for the {@link SuggestionStripView}, to call when the important notice strip is @@ -1337,30 +1306,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // Nothing to do so far. } - private boolean isSuggestionStripVisible() { - if (!hasSuggestionStripView()) { - return false; - } - if (mSuggestionStripView.isShowingAddToDictionaryHint()) { - return true; - } - final SettingsValues currentSettings = mSettings.getCurrent(); - if (null == currentSettings) { - return false; - } - if (ImportantNoticeUtils.shouldShowImportantNotice(this, - currentSettings.mInputAttributes)) { - return true; - } - if (!currentSettings.isSuggestionStripVisible()) { - return false; - } - if (currentSettings.isApplicationSpecifiedCompletionsOn()) { - return true; - } - return currentSettings.isSuggestionsRequested(); - } - public boolean hasSuggestionStripView() { return null != mSuggestionStripView; } @@ -1378,9 +1323,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mSuggestionStripView.dismissAddToDictionaryHint(); } - // TODO[IL]: Define a clear interface for this - public void setSuggestedWords(final SuggestedWords suggestedWords, - final boolean isSuggestionStripVisible) { + private void setSuggestedWords(final SuggestedWords suggestedWords) { mInputLogic.setSuggestedWords(suggestedWords); // TODO: Modify this when we support suggestions with hard keyboard if (!hasSuggestionStripView()) { @@ -1389,22 +1332,35 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (!onEvaluateInputViewShown()) { return; } - if (!isSuggestionStripVisible) { - mSuggestionStripView.setVisibility(isFullscreenMode() ? View.GONE : View.INVISIBLE); + + final SettingsValues currentSettingsValues = mSettings.getCurrent(); + final boolean shouldShowImportantNotice = + ImportantNoticeUtils.shouldShowImportantNotice(this); + final boolean shouldShowSuggestionsStripUnlessPassword = shouldShowImportantNotice + || currentSettingsValues.mShowsVoiceInputKey + || currentSettingsValues.isSuggestionsRequested() + || currentSettingsValues.isApplicationSpecifiedCompletionsOn(); + final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword + && !currentSettingsValues.mInputAttributes.mIsPasswordField; + mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, isFullscreenMode()); + if (!shouldShowSuggestionsStrip) { return; } - mSuggestionStripView.setVisibility(View.VISIBLE); - final SettingsValues currentSettings = mSettings.getCurrent(); - final boolean showSuggestions; - if (SuggestedWords.EMPTY == suggestedWords || suggestedWords.isPunctuationSuggestions() - || !currentSettings.isSuggestionsRequested()) { - showSuggestions = !mSuggestionStripView.maybeShowImportantNoticeTitle( - currentSettings.mInputAttributes); + final boolean isEmptyApplicationSpecifiedCompletions = + currentSettingsValues.isApplicationSpecifiedCompletionsOn() + && suggestedWords.isEmpty(); + final boolean noSuggestionsToShow = (SuggestedWords.EMPTY == suggestedWords) + || suggestedWords.isPunctuationSuggestions() + || isEmptyApplicationSpecifiedCompletions; + final boolean isShowingImportantNotice; + if (shouldShowImportantNotice && noSuggestionsToShow) { + isShowingImportantNotice = mSuggestionStripView.maybeShowImportantNoticeTitle(); } else { - showSuggestions = true; + isShowingImportantNotice = false; } - if (showSuggestions) { + + if (currentSettingsValues.isSuggestionsRequested() && !isShowingImportantNotice) { mSuggestionStripView.setSuggestions(suggestedWords, SubtypeLocaleUtils.isRtlLanguage(mSubtypeSwitcher.getCurrentSubtype())); } @@ -1418,36 +1374,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen callback.onGetSuggestedWords(SuggestedWords.EMPTY); return; } - // Get the word on which we should search the bigrams. If we are composing a word, it's - // whatever is *before* the half-committed word in the buffer, hence 2; if we aren't, we - // should just skip whitespace if any, so 1. final SettingsValues currentSettings = mSettings.getCurrent(); final int[] additionalFeaturesOptions = currentSettings.mAdditionalFeaturesSettingValues; - - if (DEBUG) { - if (mInputLogic.mWordComposer.isComposingWord() - || mInputLogic.mWordComposer.isBatchMode()) { - final PrevWordsInfo prevWordsInfo - = mInputLogic.mWordComposer.getPrevWordsInfoForSuggestion(); - // TODO: this is for checking consistency with older versions. Remove this when - // we are confident this is stable. - // We're checking the previous word in the text field against the memorized previous - // word. If we are composing a word we should have the second word before the cursor - // memorized, otherwise we should have the first. - final PrevWordsInfo rereadPrevWordsInfo = - mInputLogic.getPrevWordsInfoFromNthPreviousWordForSuggestion( - currentSettings.mSpacingAndPunctuations, - mInputLogic.mWordComposer.isComposingWord() ? 2 : 1); - if (!TextUtils.equals(prevWordsInfo.mPrevWord, rereadPrevWordsInfo.mPrevWord)) { - throw new RuntimeException("Unexpected previous word: " - + prevWordsInfo.mPrevWord + " <> " + rereadPrevWordsInfo.mPrevWord); - } - } - } mInputLogic.mSuggest.getSuggestedWords(mInputLogic.mWordComposer, - mInputLogic.mWordComposer.getPrevWordsInfoForSuggestion(), + mInputLogic.getPrevWordsInfoFromNthPreviousWordForSuggestion( + currentSettings.mSpacingAndPunctuations, + // Get the word on which we should search the bigrams. If we are composing + // a word, it's whatever is *before* the half-committed word in the buffer, + // hence 2; if we aren't, we should just skip whitespace if any, so 1. + mInputLogic.mWordComposer.isComposingWord() ? 2 : 1), keyboard.getProximityInfo(), currentSettings.mBlockPotentiallyOffensive, - currentSettings.mCorrectionEnabled, additionalFeaturesOptions, sessionId, + currentSettings.mAutoCorrectionEnabled, additionalFeaturesOptions, sessionId, sequenceNumber, callback); } @@ -1467,7 +1404,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen setNeutralSuggestionStrip(); } else { mInputLogic.mWordComposer.setAutoCorrection(autoCorrection); - setSuggestedWords(suggestedWords, isSuggestionStripVisible()); + setSuggestedWords(suggestedWords); } // Cache the auto-correction in accessibility code so we can speak it if the user // touches a key that will insert it. @@ -1500,7 +1437,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final SettingsValues currentSettings = mSettings.getCurrent(); final SuggestedWords neutralSuggestions = currentSettings.mBigramPredictionEnabled ? SuggestedWords.EMPTY : currentSettings.mSpacingAndPunctuations.mSuggestPuncList; - setSuggestedWords(neutralSuggestions, isSuggestionStripVisible()); + setSuggestedWords(neutralSuggestions); } // TODO: Make this private @@ -1725,15 +1662,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @UsedForTesting /* package for test */ void waitForLoadingDictionaries(final long timeout, final TimeUnit unit) throws InterruptedException { - mInputLogic.mSuggest.mDictionaryFacilitator.waitForLoadingDictionariesForTesting( - timeout, unit); + mDictionaryFacilitator.waitForLoadingDictionariesForTesting(timeout, unit); } // DO NOT USE THIS for any other purpose than testing. This can break the keyboard badly. @UsedForTesting /* package for test */ void replaceDictionariesForTest(final Locale locale) { final SettingsValues settingsValues = mSettings.getCurrent(); - mInputLogic.mSuggest.mDictionaryFacilitator.resetDictionaries(this, locale, + mDictionaryFacilitator.resetDictionaries(this, locale, settingsValues.mUseContactsDict, settingsValues.mUsePersonalizedDicts, false /* forceReloadMainDictionary */, this /* listener */); } @@ -1741,24 +1677,21 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen // DO NOT USE THIS for any other purpose than testing. @UsedForTesting /* package for test */ void clearPersonalizedDictionariesForTest() { - mInputLogic.mSuggest.mDictionaryFacilitator.clearUserHistoryDictionary(); - mInputLogic.mSuggest.mDictionaryFacilitator.clearPersonalizationDictionary(); + mDictionaryFacilitator.clearUserHistoryDictionary(); + mDictionaryFacilitator.clearPersonalizationDictionary(); } @UsedForTesting - /* package for test */ DistracterFilter createDistracterFilter() { - return new DistracterFilter(this /* Context */, - mRichImm.getMyEnabledInputMethodSubtypeList( - true /* allowsImplicitlySelectedSubtypes */)); + /* package for test */ List<InputMethodSubtype> getEnabledSubtypesForTest() { + return (mRichImm != null) ? mRichImm.getMyEnabledInputMethodSubtypeList( + true /* allowsImplicitlySelectedSubtypes */) : new ArrayList<InputMethodSubtype>(); } public void dumpDictionaryForDebug(final String dictName) { - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mInputLogic.mSuggest.mDictionaryFacilitator; - if (dictionaryFacilitator.getLocale() == null) { + if (mDictionaryFacilitator.getLocale() == null) { resetSuggest(); } - mInputLogic.mSuggest.mDictionaryFacilitator.dumpDictionaryForDebug(dictName); + mDictionaryFacilitator.dumpDictionaryForDebug(dictName); } public void debugDumpStateAndCrashWithException(final String context) { diff --git a/java/src/com/android/inputmethod/latin/LatinImeLogger.java b/java/src/com/android/inputmethod/latin/LatinImeLogger.java index 3f2b0a3f4..8fd36b937 100644 --- a/java/src/com/android/inputmethod/latin/LatinImeLogger.java +++ b/java/src/com/android/inputmethod/latin/LatinImeLogger.java @@ -16,76 +16,12 @@ package com.android.inputmethod.latin; -import android.content.SharedPreferences; -import android.view.inputmethod.EditorInfo; +import android.content.Context; -import com.android.inputmethod.keyboard.Keyboard; +// TODO: Rename this class name to make it more relevant. +public final class LatinImeLogger { + public static final boolean sDBG = false; -public final class LatinImeLogger implements SharedPreferences.OnSharedPreferenceChangeListener { - - public static boolean sDBG = false; - public static boolean sVISUALDEBUG = false; - public static boolean sUsabilityStudy = false; - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - } - - public static void init(LatinIME context) { - } - - public static void commit() { - } - - public static boolean getUsabilityStudyMode(final SharedPreferences prefs) { - return false; - } - - public static void onDestroy() { - } - - public static void logOnManualSuggestion( - String before, String after, int position, SuggestedWords suggestedWords) { - } - - public static void logOnAutoCorrectionForTyping( - String before, String after, int separatorCode) { - } - - public static void logOnAutoCorrectionForGeometric(String before, String after, - int separatorCode, InputPointers inputPointers) { - } - - public static void logOnAutoCorrectionCancelled() { - } - - public static void logOnDelete(int x, int y) { - } - - public static void logOnInputChar() { - } - - public static void logOnInputSeparator() { - } - - public static void logOnException(String metaData, Throwable e) { - } - - public static void logOnWarning(String warning) { - } - - public static void onStartInputView(EditorInfo editorInfo) { - } - - public static void onStartSuggestion(CharSequence previousWords) { - } - - public static void onAddSuggestedWord(String word, String sourceDictionaryId) { - } - - public static void onSetKeyboard(Keyboard kb) { - } - - public static void onPrintAllUsabilityStudyLogs() { + public static void init(Context context) { } } diff --git a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java index ecc8947db..42b311c69 100644 --- a/java/src/com/android/inputmethod/latin/PrevWordsInfo.java +++ b/java/src/com/android/inputmethod/latin/PrevWordsInfo.java @@ -16,23 +16,32 @@ package com.android.inputmethod.latin; -import android.util.Log; - +/** + * Class to represent information of previous words. This class is used to add n-gram entries + * into binary dictionaries, to get predictions, and to get suggestions. + */ // TODO: Support multiple previous words for n-gram. public class PrevWordsInfo { - // The previous word. May be null after resetting and before starting a new composing word, or - // when there is no context like at the start of text for example. It can also be set to null - // externally when the user enters a separator that does not let bigrams across, like a period - // or a comma. + public static final PrevWordsInfo EMPTY_PREV_WORDS_INFO = new PrevWordsInfo(null); + public static final PrevWordsInfo BEGINNING_OF_SENTENCE = new PrevWordsInfo(); + + // The word immediately before the considered word. null means we don't have any context + // including the "beginning of sentence context" - we just don't know what to predict. + // An example of that is after a comma. + // For simplicity of implementation, this may also be null transiently after the WordComposer + // was reset and before starting a new composing word, but we should never be calling + // getSuggetions* in this situation. + // This is an empty string when mIsBeginningOfSentence is true. public final String mPrevWord; // TODO: Have sentence separator. - // Whether the current context is beginning of sentence or not. + // Whether the current context is beginning of sentence or not. This is true when composing at + // the beginning of an input field or composing a word after a sentence separator. public final boolean mIsBeginningOfSentence; // Beginning of sentence. public PrevWordsInfo() { - mPrevWord = null; + mPrevWord = ""; mIsBeginningOfSentence = true; } @@ -40,4 +49,14 @@ public class PrevWordsInfo { mPrevWord = prevWord; mIsBeginningOfSentence = false; } + + public boolean isValid() { + return mPrevWord != null; + } + + @Override + public String toString() { + return "PrevWord: " + mPrevWord + ", isBeginningOfSentence: " + + mIsBeginningOfSentence + "."; + } } diff --git a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java index 4911bcdf6..0fba37c8a 100644 --- a/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java +++ b/java/src/com/android/inputmethod/latin/PunctuationSuggestions.java @@ -17,8 +17,6 @@ package com.android.inputmethod.latin; import com.android.inputmethod.keyboard.internal.KeySpecParser; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.StringUtils; import java.util.ArrayList; @@ -49,7 +47,7 @@ public final class PunctuationSuggestions extends SuggestedWords { */ public static PunctuationSuggestions newPunctuationSuggestions( final String[] punctuationSpecs) { - final ArrayList<SuggestedWordInfo> puncuationsList = CollectionUtils.newArrayList(); + final ArrayList<SuggestedWordInfo> puncuationsList = new ArrayList<>(); for (final String puncSpec : punctuationSpecs) { puncuationsList.add(newHardCodedWordInfo(puncSpec)); } diff --git a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java index 8f744bef8..e59ef7563 100644 --- a/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ReadOnlyBinaryDictionary.java @@ -66,10 +66,10 @@ public final class ReadOnlyBinaryDictionary extends Dictionary { } @Override - public boolean isValidWord(final String word) { + public boolean isInDictionary(final String word) { if (mLock.readLock().tryLock()) { try { - return mBinaryDictionary.isValidWord(word); + return mBinaryDictionary.isInDictionary(word); } finally { mLock.readLock().unlock(); } @@ -102,6 +102,18 @@ public final class ReadOnlyBinaryDictionary extends Dictionary { } @Override + public int getMaxFrequencyOfExactMatches(final String word) { + if (mLock.readLock().tryLock()) { + try { + return mBinaryDictionary.getMaxFrequencyOfExactMatches(word); + } finally { + mLock.readLock().unlock(); + } + } + return NOT_A_PROBABILITY; + } + + @Override public void close() { mLock.writeLock().lock(); try { diff --git a/java/src/com/android/inputmethod/latin/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 2c54e10aa..96476b2ee 100644 --- a/java/src/com/android/inputmethod/latin/RichInputConnection.java +++ b/java/src/com/android/inputmethod/latin/RichInputConnection.java @@ -26,14 +26,12 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; -import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; import com.android.inputmethod.latin.utils.CapsModeUtils; import com.android.inputmethod.latin.utils.DebugLogUtils; import com.android.inputmethod.latin.utils.SpannableStringUtils; import com.android.inputmethod.latin.utils.StringUtils; import com.android.inputmethod.latin.utils.TextRange; -import com.android.inputmethod.research.ResearchLogger; import java.util.Arrays; import java.util.regex.Pattern; @@ -174,9 +172,6 @@ public final class RichInputConnection { } if (null != mIC && shouldFinishComposition) { mIC.finishComposingText(); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_finishComposingText(); - } } return true; } @@ -223,9 +218,6 @@ public final class RichInputConnection { mComposingText.setLength(0); if (null != mIC) { mIC.finishComposingText(); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_finishComposingText(); - } } } @@ -363,9 +355,6 @@ public final class RichInputConnection { } if (null != mIC) { mIC.deleteSurroundingText(beforeLength, afterLength); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_deleteSurroundingText(beforeLength, afterLength); - } } if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); } @@ -374,9 +363,6 @@ public final class RichInputConnection { mIC = mParent.getCurrentInputConnection(); if (null != mIC) { mIC.performEditorAction(actionId); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_performEditorAction(actionId); - } } } @@ -429,9 +415,6 @@ public final class RichInputConnection { } if (null != mIC) { mIC.sendKeyEvent(keyEvent); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_sendKeyEvent(keyEvent); - } } } @@ -469,9 +452,6 @@ public final class RichInputConnection { // newCursorPosition != 1. if (null != mIC) { mIC.setComposingText(text, newCursorPosition); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_setComposingText(text, newCursorPosition); - } } if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); } @@ -500,9 +480,6 @@ public final class RichInputConnection { if (!isIcValid) { return false; } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_setSelection(start, end); - } } return reloadTextCache(); } @@ -530,9 +507,6 @@ public final class RichInputConnection { mComposingText.setLength(0); if (null != mIC) { mIC.commitCompletion(completionInfo); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_commitCompletion(completionInfo); - } } if (DEBUG_PREVIOUS_TEXT) checkConsistencyForDebug(); } @@ -542,7 +516,7 @@ public final class RichInputConnection { final SpacingAndPunctuations spacingAndPunctuations, final int n) { mIC = mParent.getCurrentInputConnection(); if (null == mIC) { - return new PrevWordsInfo(null); + return PrevWordsInfo.EMPTY_PREV_WORDS_INFO; } final CharSequence prev = getTextBeforeCursor(LOOKBACK_CHARACTER_NUM, 0); if (DEBUG_PREVIOUS_TEXT && null != prev) { @@ -573,14 +547,17 @@ public final class RichInputConnection { // Get information of the nth word before cursor. n = 1 retrieves the word immediately before // the cursor, n = 2 retrieves the word before that, and so on. This splits on whitespace only. // Also, it won't return words that end in a separator (if the nth word before the cursor - // ends in a separator, it returns information represents beginning-of-sentence). + // ends in a separator, it returns information representing beginning-of-sentence). // Example : // (n = 1) "abc def|" -> def // (n = 1) "abc def |" -> def + // (n = 1) "abc 'def|" -> 'def // (n = 1) "abc def. |" -> beginning-of-sentence // (n = 1) "abc def . |" -> beginning-of-sentence // (n = 2) "abc def|" -> abc // (n = 2) "abc def |" -> abc + // (n = 2) "abc 'def|" -> empty. The context is different from "abc def", but we cannot + // represent this situation using PrevWordsInfo. See TODO in the method. // (n = 2) "abc def. |" -> abc // (n = 2) "abc def . |" -> def // (n = 2) "abc|" -> beginning-of-sentence @@ -588,30 +565,43 @@ public final class RichInputConnection { // (n = 2) "abc. def|" -> beginning-of-sentence public static PrevWordsInfo getPrevWordsInfoFromNthPreviousWord(final CharSequence prev, final SpacingAndPunctuations spacingAndPunctuations, final int n) { - if (prev == null) return new PrevWordsInfo(null); + if (prev == null) return PrevWordsInfo.EMPTY_PREV_WORDS_INFO; final String[] w = spaceRegex.split(prev); + // Referring to the word after the nth word. + if ((n - 1) > 0 && (n - 1) <= w.length) { + final String wordFollowingTheNthPrevWord = w[w.length - n + 1]; + if (!wordFollowingTheNthPrevWord.isEmpty()) { + final char firstChar = wordFollowingTheNthPrevWord.charAt(0); + if (spacingAndPunctuations.isWordConnector(firstChar)) { + // The word following the n-th prev word is starting with a word connector. + // TODO: Return meaningful context for this case. + return PrevWordsInfo.EMPTY_PREV_WORDS_INFO; + } + } + } + // If we can't find n words, or we found an empty word, the context is // beginning-of-sentence. if (w.length < n) { - return new PrevWordsInfo(); + return PrevWordsInfo.BEGINNING_OF_SENTENCE; } final String nthPrevWord = w[w.length - n]; final int length = nthPrevWord.length(); if (length <= 0) { - return new PrevWordsInfo(); + return PrevWordsInfo.BEGINNING_OF_SENTENCE; } // If ends in a sentence separator, the context is beginning-of-sentence. final char lastChar = nthPrevWord.charAt(length - 1); if (spacingAndPunctuations.isSentenceSeparator(lastChar)) { - new PrevWordsInfo(); + return PrevWordsInfo.BEGINNING_OF_SENTENCE; } // If ends in a word separator or connector, the context is unclear. // TODO: Return meaningful context for this case. if (spacingAndPunctuations.isWordSeparator(lastChar) || spacingAndPunctuations.isWordConnector(lastChar)) { - return new PrevWordsInfo(null); + return PrevWordsInfo.EMPTY_PREV_WORDS_INFO; } return new PrevWordsInfo(nthPrevWord); } @@ -765,9 +755,6 @@ public final class RichInputConnection { deleteSurroundingText(2, 0); final String singleSpace = " "; commitText(singleSpace, 1); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_revertDoubleSpacePeriod(); - } return true; } @@ -790,9 +777,6 @@ public final class RichInputConnection { deleteSurroundingText(2, 0); final String text = " " + textBeforeCursor.subSequence(0, 1); commitText(text, 1); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.richInputConnection_revertSwapPunctuation(); - } return true; } @@ -907,4 +891,8 @@ public final class RichInputConnection { public boolean hasSelection() { return mExpectedSelEnd != mExpectedSelStart; } + + public boolean isCursorPositionKnown() { + return INVALID_CURSOR_POSITION != mExpectedSelStart; + } } diff --git a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java index 64cc562c8..cbdc4b9eb 100644 --- a/java/src/com/android/inputmethod/latin/RichInputMethodManager.java +++ b/java/src/com/android/inputmethod/latin/RichInputMethodManager.java @@ -31,7 +31,6 @@ import android.view.inputmethod.InputMethodSubtype; import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.latin.settings.Settings; import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; import java.util.Collections; @@ -53,9 +52,9 @@ public final class RichInputMethodManager { private InputMethodManagerCompatWrapper mImmWrapper; private InputMethodInfoCache mInputMethodInfoCache; final HashMap<InputMethodInfo, List<InputMethodSubtype>> - mSubtypeListCacheWithImplicitlySelectedSubtypes = CollectionUtils.newHashMap(); + mSubtypeListCacheWithImplicitlySelectedSubtypes = new HashMap<>(); final HashMap<InputMethodInfo, List<InputMethodSubtype>> - mSubtypeListCacheWithoutImplicitlySelectedSubtypes = CollectionUtils.newHashMap(); + mSubtypeListCacheWithoutImplicitlySelectedSubtypes = new HashMap<>(); private static final int INDEX_NOT_FOUND = -1; diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index c8a2fb2f9..a3d09565c 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -255,8 +255,7 @@ public final class SubtypeSwitcher { public boolean isSystemLocaleSameAsLocaleOfAllEnabledSubtypesOfEnabledImes() { final Locale systemLocale = mResources.getConfiguration().locale; - final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = - new HashSet<InputMethodSubtype>(); + final Set<InputMethodSubtype> enabledSubtypesOfEnabledImes = new HashSet<>(); final InputMethodManager inputMethodManager = mRichImm.getInputMethodManager(); final List<InputMethodInfo> enabledInputMethodInfoList = inputMethodManager.getEnabledInputMethodList(); diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 43daee4d2..1ba5d5ea6 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -23,7 +23,6 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.utils.AutoCorrectionUtils; import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.StringUtils; import com.android.inputmethod.latin.utils.SuggestionResults; @@ -52,11 +51,14 @@ public final class Suggest { private static final int SUPPRESS_SUGGEST_THRESHOLD = -2000000000; private static final boolean DBG = LatinImeLogger.sDBG; - public final DictionaryFacilitatorForSuggest mDictionaryFacilitator = - new DictionaryFacilitatorForSuggest(); + private final DictionaryFacilitator mDictionaryFacilitator; private float mAutoCorrectionThreshold; + public Suggest(final DictionaryFacilitator dictionaryFacilitator) { + mDictionaryFacilitator = dictionaryFacilitator; + } + public Locale getLocale() { return mDictionaryFacilitator.getLocale(); } @@ -74,7 +76,6 @@ public final class Suggest { final boolean blockOffensiveWords, final boolean isCorrectionEnabled, final int[] additionalFeaturesOptions, final int sessionId, final int sequenceNumber, final OnGetSuggestedWordsCallback callback) { - LatinImeLogger.onStartSuggestion(prevWordsInfo.mPrevWord); if (wordComposer.isBatchMode()) { getSuggestedWordsForBatchInput(wordComposer, prevWordsInfo, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId, sequenceNumber, @@ -98,11 +99,10 @@ public final class Suggest { final String consideredWord = trailingSingleQuotesCount > 0 ? typedWord.substring(0, typedWord.length() - trailingSingleQuotesCount) : typedWord; - LatinImeLogger.onAddSuggestedWord(typedWord, Dictionary.TYPE_USER_TYPED); final ArrayList<SuggestedWordInfo> rawSuggestions; if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) { - rawSuggestions = CollectionUtils.newArrayList(); + rawSuggestions = new ArrayList<>(); } else { rawSuggestions = null; } @@ -110,7 +110,7 @@ public final class Suggest { wordComposer, prevWordsInfo, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, SESSION_TYPING, rawSuggestions); - final boolean isFirstCharCapitalized = wordComposer.isFirstCharCapitalized(); + final boolean isOnlyFirstCharCapitalized = wordComposer.isOnlyFirstCharCapitalized(); // If resumed, then we don't want to upcase everything: resuming on a fully-capitalized // words is rarely done to switch to another fully-capitalized word, but usually to a // normal, non-capitalized suggestion. @@ -122,9 +122,9 @@ public final class Suggest { } else { final SuggestedWordInfo firstSuggestedWordInfo = getTransformedSuggestedWordInfo( suggestionResults.first(), suggestionResults.mLocale, isAllUpperCase, - isFirstCharCapitalized, trailingSingleQuotesCount); + isOnlyFirstCharCapitalized, trailingSingleQuotesCount); firstSuggestion = firstSuggestedWordInfo.mWord; - if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) { + if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) { whitelistedWord = null; } else { whitelistedWord = firstSuggestion; @@ -142,7 +142,7 @@ public final class Suggest { final boolean allowsToBeAutoCorrected = (null != whitelistedWord && !whitelistedWord.equals(typedWord)) || (consideredWord.length() > 1 && !mDictionaryFacilitator.isValidWord( - consideredWord, wordComposer.isFirstCharCapitalized()) + consideredWord, isOnlyFirstCharCapitalized) && !typedWord.equals(firstSuggestion)); final boolean hasAutoCorrection; @@ -155,7 +155,7 @@ public final class Suggest { || suggestionResults.isEmpty() || wordComposer.hasDigits() || wordComposer.isMostlyCaps() || wordComposer.isResumed() || !mDictionaryFacilitator.hasInitializedMainDictionary() - || SuggestedWordInfo.KIND_SHORTCUT == suggestionResults.first().mKind) { + || suggestionResults.first().isKindOf(SuggestedWordInfo.KIND_SHORTCUT)) { // If we don't have a main dictionary, we never want to auto-correct. The reason for // this is, the user may have a contact whose name happens to match a valid word in // their language, and it will unexpectedly auto-correct. For example, if the user @@ -171,24 +171,18 @@ public final class Suggest { } final ArrayList<SuggestedWordInfo> suggestionsContainer = - CollectionUtils.newArrayList(suggestionResults); + new ArrayList<>(suggestionResults); final int suggestionsCount = suggestionsContainer.size(); - if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) { + if (isOnlyFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) { for (int i = 0; i < suggestionsCount; ++i) { final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo( - wordInfo, suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized, - trailingSingleQuotesCount); + wordInfo, suggestionResults.mLocale, isAllUpperCase, + isOnlyFirstCharCapitalized, trailingSingleQuotesCount); suggestionsContainer.set(i, transformedWordInfo); } } - for (int i = 0; i < suggestionsCount; ++i) { - final SuggestedWordInfo wordInfo = suggestionsContainer.get(i); - LatinImeLogger.onAddSuggestedWord(wordInfo.mWord.toString(), - wordInfo.mSourceDict.mDictType); - } - if (!TextUtils.isEmpty(typedWord)) { suggestionsContainer.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED, @@ -223,19 +217,15 @@ public final class Suggest { final OnGetSuggestedWordsCallback callback) { final ArrayList<SuggestedWordInfo> rawSuggestions; if (ProductionFlag.INCLUDE_RAW_SUGGESTIONS) { - rawSuggestions = CollectionUtils.newArrayList(); + rawSuggestions = new ArrayList<>(); } else { rawSuggestions = null; } final SuggestionResults suggestionResults = mDictionaryFacilitator.getSuggestionResults( wordComposer, prevWordsInfo, proximityInfo, blockOffensiveWords, additionalFeaturesOptions, sessionId, rawSuggestions); - for (SuggestedWordInfo wordInfo : suggestionResults) { - LatinImeLogger.onAddSuggestedWord(wordInfo.mWord, wordInfo.mSourceDict.mDictType); - } - final ArrayList<SuggestedWordInfo> suggestionsContainer = - CollectionUtils.newArrayList(suggestionResults); + new ArrayList<>(suggestionResults); final int suggestionsCount = suggestionsContainer.size(); final boolean isFirstCharCapitalized = wordComposer.wasShiftedNoLock(); final boolean isAllUpperCase = wordComposer.isAllUpperCase(); @@ -278,8 +268,7 @@ public final class Suggest { final SuggestedWordInfo typedWordInfo = suggestions.get(0); typedWordInfo.setDebugString("+"); final int suggestionsSize = suggestions.size(); - final ArrayList<SuggestedWordInfo> suggestionsList = - CollectionUtils.newArrayList(suggestionsSize); + final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(suggestionsSize); suggestionsList.add(typedWordInfo); // Note: i here is the index in mScores[], but the index in mSuggestions is one more // than i because we added the typed word to mSuggestions without touching mScores. @@ -303,11 +292,11 @@ public final class Suggest { /* package for test */ static SuggestedWordInfo getTransformedSuggestedWordInfo( final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase, - final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) { + final boolean isOnlyFirstCharCapitalized, final int trailingSingleQuotesCount) { final StringBuilder sb = new StringBuilder(wordInfo.mWord.length()); if (isAllUpperCase) { sb.append(wordInfo.mWord.toUpperCase(locale)); - } else if (isFirstCharCapitalized) { + } else if (isOnlyFirstCharCapitalized) { sb.append(StringUtils.capitalizeFirstCodePoint(wordInfo.mWord, locale)); } else { sb.append(wordInfo.mWord); @@ -320,7 +309,7 @@ public final class Suggest { for (int i = quotesToAppend - 1; i >= 0; --i) { sb.appendCodePoint(Constants.CODE_SINGLE_QUOTE); } - return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKind, + return new SuggestedWordInfo(sb.toString(), wordInfo.mScore, wordInfo.mKindAndFlags, wordInfo.mSourceDict, wordInfo.mIndexOfTouchPointOfSecondWord, wordInfo.mAutoCommitFirstWordConfidence); } diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index dc2c9fd0e..72461e17a 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -19,7 +19,6 @@ package com.android.inputmethod.latin; import android.text.TextUtils; import android.view.inputmethod.CompletionInfo; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.StringUtils; import java.util.ArrayList; @@ -34,8 +33,7 @@ public class SuggestedWords { // The maximum number of suggestions available. public static final int MAX_SUGGESTIONS = 18; - private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = - CollectionUtils.newArrayList(0); + private static final ArrayList<SuggestedWordInfo> EMPTY_WORD_INFO_LIST = new ArrayList<>(0); public static final SuggestedWords EMPTY = new SuggestedWords( EMPTY_WORD_INFO_LIST, null /* rawSuggestions */, false, false, false, false); @@ -165,7 +163,7 @@ public class SuggestedWords { public static ArrayList<SuggestedWordInfo> getFromApplicationSpecifiedCompletions( final CompletionInfo[] infos) { - final ArrayList<SuggestedWordInfo> result = CollectionUtils.newArrayList(); + final ArrayList<SuggestedWordInfo> result = new ArrayList<>(); for (final CompletionInfo info : infos) { if (null == info || null == info.getText()) { continue; @@ -179,8 +177,8 @@ public class SuggestedWords { // and replace it with what the user currently typed. public static ArrayList<SuggestedWordInfo> getTypedWordAndPreviousSuggestions( final String typedWord, final SuggestedWords previousSuggestions) { - final ArrayList<SuggestedWordInfo> suggestionsList = CollectionUtils.newArrayList(); - final HashSet<String> alreadySeen = CollectionUtils.newHashSet(); + final ArrayList<SuggestedWordInfo> suggestionsList = new ArrayList<>(); + final HashSet<String> alreadySeen = new HashSet<>(); suggestionsList.add(new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED, SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, @@ -209,7 +207,8 @@ public class SuggestedWords { public static final int NOT_AN_INDEX = -1; public static final int NOT_A_CONFIDENCE = -1; public static final int MAX_SCORE = Integer.MAX_VALUE; - public static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind + + private static final int KIND_MASK_KIND = 0xFF; // Mask to get only the kind public static final int KIND_TYPED = 0; // What user typed public static final int KIND_CORRECTION = 1; // Simple correction/suggestion public static final int KIND_COMPLETION = 2; // Completion (suggestion with appended chars) @@ -224,16 +223,16 @@ public class SuggestedWords { public static final int KIND_RESUMED = 9; public static final int KIND_OOV_CORRECTION = 10; // Most probable string correction - public static final int KIND_MASK_FLAGS = 0xFFFFFF00; // Mask to get the flags public static final int KIND_FLAG_POSSIBLY_OFFENSIVE = 0x80000000; public static final int KIND_FLAG_EXACT_MATCH = 0x40000000; + public static final int KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION = 0x20000000; public final String mWord; // The completion info from the application. Null for suggestions that don't come from // the application (including keyboard-computed ones, so this is almost always null) public final CompletionInfo mApplicationSpecifiedCompletionInfo; public final int mScore; - public final int mKind; // one of the KIND_* constants above + public final int mKindAndFlags; public final int mCodePointCount; public final Dictionary mSourceDict; // For auto-commit. This keeps track of the index inside the touch coordinates array @@ -249,18 +248,19 @@ public class SuggestedWords { * Create a new suggested word info. * @param word The string to suggest. * @param score A measure of how likely this suggestion is. - * @param kind The kind of suggestion, as one of the above KIND_* constants. + * @param kindAndFlags The kind of suggestion, as one of the above KIND_* constants with + * flags. * @param sourceDict What instance of Dictionary produced this suggestion. * @param indexOfTouchPointOfSecondWord See mIndexOfTouchPointOfSecondWord. * @param autoCommitFirstWordConfidence See mAutoCommitFirstWordConfidence. */ - public SuggestedWordInfo(final String word, final int score, final int kind, + public SuggestedWordInfo(final String word, final int score, final int kindAndFlags, final Dictionary sourceDict, final int indexOfTouchPointOfSecondWord, final int autoCommitFirstWordConfidence) { mWord = word; mApplicationSpecifiedCompletionInfo = null; mScore = score; - mKind = kind; + mKindAndFlags = kindAndFlags; mSourceDict = sourceDict; mCodePointCount = StringUtils.codePointCount(mWord); mIndexOfTouchPointOfSecondWord = indexOfTouchPointOfSecondWord; @@ -276,7 +276,7 @@ public class SuggestedWords { mWord = applicationSpecifiedCompletion.getText().toString(); mApplicationSpecifiedCompletionInfo = applicationSpecifiedCompletion; mScore = SuggestedWordInfo.MAX_SCORE; - mKind = SuggestedWordInfo.KIND_APP_DEFINED; + mKindAndFlags = SuggestedWordInfo.KIND_APP_DEFINED; mSourceDict = Dictionary.DICTIONARY_APPLICATION_DEFINED; mCodePointCount = StringUtils.codePointCount(mWord); mIndexOfTouchPointOfSecondWord = SuggestedWordInfo.NOT_AN_INDEX; @@ -284,7 +284,27 @@ public class SuggestedWords { } public boolean isEligibleForAutoCommit() { - return (KIND_CORRECTION == mKind && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord); + return (isKindOf(KIND_CORRECTION) && NOT_AN_INDEX != mIndexOfTouchPointOfSecondWord); + } + + public int getKind() { + return (mKindAndFlags & KIND_MASK_KIND); + } + + public boolean isKindOf(final int kind) { + return getKind() == kind; + } + + public boolean isPossiblyOffensive() { + return (mKindAndFlags & KIND_FLAG_POSSIBLY_OFFENSIVE) != 0; + } + + public boolean isExactMatch() { + return (mKindAndFlags & KIND_FLAG_EXACT_MATCH) != 0; + } + + public boolean isExactMatchWithIntentionalOmission() { + return (mKindAndFlags & KIND_FLAG_EXACT_MATCH_WITH_INTENTIONAL_OMISSION) != 0; } public void setDebugString(final String str) { @@ -337,11 +357,11 @@ public class SuggestedWords { // SuggestedWords is an immutable object, as much as possible. We must not just remove // words from the member ArrayList as some other parties may expect the object to never change. public SuggestedWords getSuggestedWordsExcludingTypedWord() { - final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList(); + final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>(); String typedWord = null; for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) { final SuggestedWordInfo info = mSuggestedWordInfoList.get(i); - if (SuggestedWordInfo.KIND_TYPED != info.mKind) { + if (!info.isKindOf(SuggestedWordInfo.KIND_TYPED)) { newSuggestions.add(info); } else { assert(null == typedWord); @@ -361,12 +381,12 @@ public class SuggestedWords { // we should only suggest replacements for this last word. // TODO: make this work with languages without spaces. public SuggestedWords getSuggestedWordsForLastWordOfPhraseGesture() { - final ArrayList<SuggestedWordInfo> newSuggestions = CollectionUtils.newArrayList(); + final ArrayList<SuggestedWordInfo> newSuggestions = new ArrayList<>(); for (int i = 0; i < mSuggestedWordInfoList.size(); ++i) { final SuggestedWordInfo info = mSuggestedWordInfoList.get(i); final int indexOfLastSpace = info.mWord.lastIndexOf(Constants.CODE_SPACE) + 1; final String lastWord = info.mWord.substring(indexOfLastSpace); - newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKind, + newSuggestions.add(new SuggestedWordInfo(lastWord, info.mScore, info.mKindAndFlags, info.mSourceDict, SuggestedWordInfo.NOT_AN_INDEX, SuggestedWordInfo.NOT_A_CONFIDENCE)); } diff --git a/java/src/com/android/inputmethod/latin/WordComposer.java b/java/src/com/android/inputmethod/latin/WordComposer.java index 6ecb37346..6ce1f85c5 100644 --- a/java/src/com/android/inputmethod/latin/WordComposer.java +++ b/java/src/com/android/inputmethod/latin/WordComposer.java @@ -18,7 +18,6 @@ package com.android.inputmethod.latin; import com.android.inputmethod.event.CombinerChain; import com.android.inputmethod.event.Event; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CoordinateUtils; import com.android.inputmethod.latin.utils.StringUtils; @@ -46,9 +45,6 @@ public final class WordComposer { // The list of events that served to compose this string. private final ArrayList<Event> mEvents; private final InputPointers mInputPointers = new InputPointers(MAX_WORD_LENGTH); - // The information of previous words (before the composing word). Must not be null. Used as - // context for suggestions. - private PrevWordsInfo mPrevWordsInfo; private String mAutoCorrection; private boolean mIsResumed; private boolean mIsBatchMode; @@ -73,19 +69,18 @@ public final class WordComposer { private int mCursorPositionWithinWord; /** - * Whether the user chose to capitalize the first char of the word. + * Whether the composing word has the only first char capitalized. */ - private boolean mIsFirstCharCapitalized; + private boolean mIsOnlyFirstCharCapitalized; public WordComposer() { mCombinerChain = new CombinerChain(""); - mEvents = CollectionUtils.newArrayList(); + mEvents = new ArrayList<>(); mAutoCorrection = null; mIsResumed = false; mIsBatchMode = false; mCursorPositionWithinWord = 0; mRejectedBatchModeSuggestion = null; - mPrevWordsInfo = new PrevWordsInfo(null); refreshTypedWordCache(); } @@ -112,12 +107,11 @@ public final class WordComposer { mAutoCorrection = null; mCapsCount = 0; mDigitsCount = 0; - mIsFirstCharCapitalized = false; + mIsOnlyFirstCharCapitalized = false; mIsResumed = false; mIsBatchMode = false; mCursorPositionWithinWord = 0; mRejectedBatchModeSuggestion = null; - mPrevWordsInfo = new PrevWordsInfo(null); refreshTypedWordCache(); } @@ -146,9 +140,12 @@ public final class WordComposer { */ public int copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount( final int[] destination) { + // This method can be called on a separate thread and mTypedWordCache can change while we + // are executing this method. + final String typedWord = mTypedWordCache.toString(); // lastIndex is exclusive - final int lastIndex = mTypedWordCache.length() - - StringUtils.getTrailingSingleQuotesCount(mTypedWordCache); + final int lastIndex = typedWord.length() + - StringUtils.getTrailingSingleQuotesCount(typedWord); if (lastIndex <= 0) { // The string is empty or contains only single quotes. return 0; @@ -156,11 +153,11 @@ public final class WordComposer { // The following function counts the number of code points in the text range which begins // at index 0 and extends to the character at lastIndex. - final int codePointSize = Character.codePointCount(mTypedWordCache, 0, lastIndex); + final int codePointSize = Character.codePointCount(typedWord, 0, lastIndex); if (codePointSize > destination.length) { return -1; } - return StringUtils.copyCodePointsAndReturnCodePointCount(destination, mTypedWordCache, 0, + return StringUtils.copyCodePointsAndReturnCodePointCount(destination, typedWord, 0, lastIndex, true /* downCase */); } @@ -176,12 +173,6 @@ public final class WordComposer { return mInputPointers; } - private static boolean isFirstCharCapitalized(final int index, final int codePoint, - final boolean previous) { - if (index == 0) return Character.isUpperCase(codePoint); - return previous && !Character.isUpperCase(codePoint); - } - /** * Process an input event. * @@ -201,7 +192,7 @@ public final class WordComposer { mCursorPositionWithinWord = mCodePointSize; // We may have deleted the last one. if (0 == mCodePointSize) { - mIsFirstCharCapitalized = false; + mIsOnlyFirstCharCapitalized = false; } if (Constants.CODE_DELETE != event.mKeyCode) { if (newIndex < MAX_WORD_LENGTH) { @@ -213,8 +204,12 @@ public final class WordComposer { mInputPointers.addPointerAt(newIndex, keyX, keyY, 0, 0); } } - mIsFirstCharCapitalized = isFirstCharCapitalized( - newIndex, primaryCode, mIsFirstCharCapitalized); + if (0 == newIndex) { + mIsOnlyFirstCharCapitalized = Character.isUpperCase(primaryCode); + } else { + mIsOnlyFirstCharCapitalized = mIsOnlyFirstCharCapitalized + && !Character.isUpperCase(primaryCode); + } if (Character.isUpperCase(primaryCode)) mCapsCount++; if (Character.isDigit(primaryCode)) mDigitsCount++; } @@ -294,10 +289,8 @@ public final class WordComposer { * This will register NOT_A_COORDINATE for X and Ys, and use the passed keyboard for proximity. * @param codePoints the code points to set as the composing word. * @param coordinates the x, y coordinates of the key in the CoordinateUtils format - * @param prevWordsInfo the information of previous words, to use as context for suggestions */ - public void setComposingWord(final int[] codePoints, final int[] coordinates, - final PrevWordsInfo prevWordsInfo) { + public void setComposingWord(final int[] codePoints, final int[] coordinates) { reset(); final int length = codePoints.length; for (int i = 0; i < length; ++i) { @@ -306,7 +299,6 @@ public final class WordComposer { CoordinateUtils.yFromArray(coordinates, i))); } mIsResumed = true; - mPrevWordsInfo = prevWordsInfo; } /** @@ -317,16 +309,13 @@ public final class WordComposer { return mTypedWordCache.toString(); } - public PrevWordsInfo getPrevWordsInfoForSuggestion() { - return mPrevWordsInfo; - } - /** - * Whether or not the user typed a capital letter as the first letter in the word + * Whether or not the user typed a capital letter as the first letter in the word, and no + * other letter is capitalized * @return capitalization preference */ - public boolean isFirstCharCapitalized() { - return mIsFirstCharCapitalized; + public boolean isOnlyFirstCharCapitalized() { + return mIsOnlyFirstCharCapitalized; } /** @@ -362,7 +351,7 @@ public final class WordComposer { } /** - * Saves the caps mode and the previous word at the start of composing. + * Saves the caps mode at the start of composing. * * WordComposer needs to know about the caps mode for several reasons. The first is, we need * to know after the fact what the reason was, to register the correct form into the user @@ -371,12 +360,9 @@ public final class WordComposer { * Also, batch input needs to know about the current caps mode to display correctly * capitalized suggestions. * @param mode the mode at the time of start - * @param prevWordsInfo the information of previous words */ - public void setCapitalizedModeAndPreviousWordAtStartComposingTime(final int mode, - final PrevWordsInfo prevWordsInfo) { + public void setCapitalizedModeAtStartComposingTime(final int mode) { mCapitalizedMode = mode; - mPrevWordsInfo = prevWordsInfo; } /** @@ -427,11 +413,10 @@ public final class WordComposer { mCapsCount = 0; mDigitsCount = 0; mIsBatchMode = false; - mPrevWordsInfo = new PrevWordsInfo(committedWord.toString()); mCombinerChain.reset(); mEvents.clear(); mCodePointSize = 0; - mIsFirstCharCapitalized = false; + mIsOnlyFirstCharCapitalized = false; mCapitalizedMode = CAPS_MODE_OFF; refreshTypedWordCache(); mAutoCorrection = null; @@ -441,15 +426,7 @@ public final class WordComposer { return lastComposedWord; } - // Call this when the recorded previous word should be discarded. This is typically called - // when the user inputs a separator that's not whitespace (including the case of the - // double-space-to-period feature). - public void discardPreviousWordForSuggestion() { - mPrevWordsInfo = new PrevWordsInfo(null); - } - - public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord, - final PrevWordsInfo prevWordsInfo) { + public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord) { mEvents.clear(); Collections.copy(mEvents, lastComposedWord.mEvents); mInputPointers.set(lastComposedWord.mInputPointers); @@ -460,7 +437,6 @@ public final class WordComposer { mCursorPositionWithinWord = mCodePointSize; mRejectedBatchModeSuggestion = null; mIsResumed = true; - mPrevWordsInfo = prevWordsInfo; } public boolean isBatchMode() { diff --git a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java index 139e73aa4..7071d8689 100644 --- a/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java +++ b/java/src/com/android/inputmethod/latin/debug/ExternalDictionaryGetterForDebug.java @@ -27,7 +27,6 @@ import com.android.inputmethod.latin.BinaryDictionaryFileDumper; import com.android.inputmethod.latin.BinaryDictionaryGetter; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.makedict.DictionaryHeader; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.DialogUtils; import com.android.inputmethod.latin.utils.DictionaryInfoUtils; import com.android.inputmethod.latin.utils.LocaleUtils; @@ -50,7 +49,7 @@ public class ExternalDictionaryGetterForDebug { private static String[] findDictionariesInTheDownloadedFolder() { final File[] files = new File(SOURCE_FOLDER).listFiles(); - final ArrayList<String> eligibleList = CollectionUtils.newArrayList(); + final ArrayList<String> eligibleList = new ArrayList<>(); for (File f : files) { final DictionaryHeader header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(f); if (null == header) continue; diff --git a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java index 761f457ea..972580298 100644 --- a/java/src/com/android/inputmethod/latin/define/ProductionFlag.java +++ b/java/src/com/android/inputmethod/latin/define/ProductionFlag.java @@ -21,13 +21,6 @@ public final class ProductionFlag { // This class is not publicly instantiable. } - public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS = false; - - // When false, USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG suggests that all guarded - // class-private DEBUG flags should be false, and any privacy controls should be enforced. - // USES_DEVELOPMENT_ONLY_DIAGNOSTICS must be false for any production build. - public static final boolean USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG = false; - public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = false; // When true, enable {@link InputMethodService#onUpdateCursor} callback with diff --git a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java index 7536ff94c..24cc1ef0d 100644 --- a/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java +++ b/java/src/com/android/inputmethod/latin/inputlogic/InputLogic.java @@ -32,7 +32,7 @@ import com.android.inputmethod.event.InputTransaction; import com.android.inputmethod.keyboard.KeyboardSwitcher; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; +import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.InputPointers; import com.android.inputmethod.latin.LastComposedWord; import com.android.inputmethod.latin.LatinIME; @@ -44,18 +44,14 @@ import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; -import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.settings.SettingsValues; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor; import com.android.inputmethod.latin.utils.AsyncResultHolder; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.InputTypeUtils; -import com.android.inputmethod.latin.utils.LatinImeLoggerUtils; import com.android.inputmethod.latin.utils.RecapitalizeStatus; import com.android.inputmethod.latin.utils.StringUtils; import com.android.inputmethod.latin.utils.TextRange; -import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayList; import java.util.TreeSet; @@ -79,7 +75,8 @@ public final class InputLogic { private int mSpaceState; // Never null public SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; - public final Suggest mSuggest = new Suggest(); + public final Suggest mSuggest; + private final DictionaryFacilitator mDictionaryFacilitator; public LastComposedWord mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; public final WordComposer mWordComposer; @@ -88,7 +85,7 @@ public final class InputLogic { private int mDeleteCount; private long mLastKeyTime; - public final TreeSet<Long> mCurrentlyPressedHardwareKeys = CollectionUtils.newTreeSet(); + public final TreeSet<Long> mCurrentlyPressedHardwareKeys = new TreeSet<>(); // Keeps track of most recently inserted text (multi-character key) for reverting private String mEnteredText; @@ -102,14 +99,19 @@ public final class InputLogic { * Create a new instance of the input logic. * @param latinIME the instance of the parent LatinIME. We should remove this when we can. * @param suggestionStripViewAccessor an object to access the suggestion strip view. + * @param dictionaryFacilitator facilitator for getting suggestions and updating user history + * dictionary. */ public InputLogic(final LatinIME latinIME, - final SuggestionStripViewAccessor suggestionStripViewAccessor) { + final SuggestionStripViewAccessor suggestionStripViewAccessor, + final DictionaryFacilitator dictionaryFacilitator) { mLatinIME = latinIME; mSuggestionStripViewAccessor = suggestionStripViewAccessor; mWordComposer = new WordComposer(); mConnection = new RichInputConnection(latinIME); mInputLogicHandler = InputLogicHandler.NULL_HANDLER; + mSuggest = new Suggest(dictionaryFacilitator); + mDictionaryFacilitator = dictionaryFacilitator; } /** @@ -132,7 +134,7 @@ public final class InputLogic { resetComposingState(true /* alsoResetLastComposedWord */); mDeleteCount = 0; mSpaceState = SpaceState.NONE; - mRecapitalizeStatus.deactivate(); + mRecapitalizeStatus.disable(); // Do not perform recapitalize until the cursor is moved once mCurrentlyPressedHardwareKeys.clear(); mSuggestedWords = SuggestedWords.EMPTY; // In some cases (namely, after rotation of the device) editorInfo.initialSelStart is lying @@ -173,7 +175,7 @@ public final class InputLogic { final InputLogicHandler inputLogicHandler = mInputLogicHandler; mInputLogicHandler = InputLogicHandler.NULL_HANDLER; inputLogicHandler.destroy(); - mSuggest.mDictionaryFacilitator.closeDictionaries(); + mDictionaryFacilitator.closeDictionaries(); } /** @@ -196,19 +198,11 @@ public final class InputLogic { resetComposingState(true /* alsoResetLastComposedWord */); } handler.postUpdateSuggestionStrip(); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS - && ResearchLogger.RESEARCH_KEY_OUTPUT_TEXT.equals(rawText)) { - ResearchLogger.getInstance().onResearchKeySelected(mLatinIME); - return; - } final String text = performSpecificTldProcessingOnTextInput(rawText); if (SpaceState.PHANTOM == mSpaceState) { promotePhantomSpace(settingsValues); } mConnection.commitText(text, 1); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onTextInput(text, false /* isBatchMode */); - } mConnection.endBatchEdit(); // Space state must be updated before calling updateShiftState mSpaceState = SpaceState.NONE; @@ -235,14 +229,8 @@ public final class InputLogic { // If this is a punctuation picked from the suggestion strip, pass it to onCodeInput if (suggestion.length() == 1 && suggestedWords.isPunctuationSuggestions()) { // Word separators are suggested before the user inputs something. - // So, LatinImeLogger logs "" as a user's input. - LatinImeLogger.logOnManualSuggestion("", suggestion, index, suggestedWords); // Rely on onCodeInput to do the complicated swapping/stripping logic consistently. final Event event = Event.createPunctuationSuggestionPickedEvent(suggestionInfo); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_punctuationSuggestion(index, suggestion, - false /* isBatchMode */, suggestedWords.mIsPrediction); - } return onCodeInput(settingsValues, event, keyboardShiftState, handler); } @@ -265,7 +253,7 @@ public final class InputLogic { // code path as for other kinds, use commitChosenWord, and do everything normally. We will // however need to reset the suggestion strip right away, because we know we can't take // the risk of calling commitCompletion twice because we don't know how the app will react. - if (SuggestedWordInfo.KIND_APP_DEFINED == suggestionInfo.mKind) { + if (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_APP_DEFINED)) { mSuggestedWords = SuggestedWords.EMPTY; mSuggestionStripViewAccessor.setNeutralSuggestionStrip(); inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); @@ -278,14 +266,8 @@ public final class InputLogic { // We need to log before we commit, because the word composer will store away the user // typed word. final String replacedWord = mWordComposer.getTypedWord(); - LatinImeLogger.logOnManualSuggestion(replacedWord, suggestion, index, suggestedWords); commitChosenWord(settingsValues, suggestion, LastComposedWord.COMMIT_TYPE_MANUAL_PICK, LastComposedWord.NOT_A_SEPARATOR); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_pickSuggestionManually(replacedWord, index, suggestion, - mWordComposer.isBatchMode(), suggestionInfo.mScore, - suggestionInfo.mKind, suggestionInfo.mSourceDict.mDictType); - } mConnection.endBatchEdit(); // Don't allow cancellation of manual pick mLastComposedWord.deactivate(); @@ -295,18 +277,12 @@ public final class InputLogic { // We should show the "Touch again to save" hint if the user pressed the first entry // AND it's in none of our current dictionaries (main, user or otherwise). - final DictionaryFacilitatorForSuggest dictionaryFacilitator = - mSuggest.mDictionaryFacilitator; final boolean showingAddToDictionaryHint = - (SuggestedWordInfo.KIND_TYPED == suggestionInfo.mKind - || SuggestedWordInfo.KIND_OOV_CORRECTION == suggestionInfo.mKind) - && !dictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */); + (suggestionInfo.isKindOf(SuggestedWordInfo.KIND_TYPED) + || suggestionInfo.isKindOf(SuggestedWordInfo.KIND_OOV_CORRECTION)) + && !mDictionaryFacilitator.isValidWord(suggestion, true /* ignoreCase */); - if (settingsValues.mIsInternal) { - LatinImeLoggerUtils.onSeparator((char)Constants.CODE_SPACE, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } - if (showingAddToDictionaryHint && dictionaryFacilitator.isUserDictionaryEnabled()) { + if (showingAddToDictionaryHint && mDictionaryFacilitator.isUserDictionaryEnabled()) { mSuggestionStripViewAccessor.showAddToDictionaryHint(suggestion); } else { // If we're not showing the "Touch again to save", then update the suggestion strip. @@ -343,8 +319,16 @@ public final class InputLogic { || !mWordComposer.isComposingWord(); // safe to reset final boolean hasOrHadSelection = (oldSelStart != oldSelEnd || newSelStart != newSelEnd); final int moveAmount = newSelStart - oldSelStart; - if (selectionChangedOrSafeToReset && (hasOrHadSelection - || !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { + // As an added small gift from the framework, it happens upon rotation when there + // is a selection that we get a wrong cursor position delivered to startInput() that + // does not get reflected in the oldSel{Start,End} parameters to the next call to + // onUpdateSelection. In this case, we may have set a composition, and when we're here + // we realize we shouldn't have. In theory, in this case, selectionChangedOrSafeToReset + // should be true, but that is if the framework had taken that wrong cursor position + // into account, which means we have to reset the entire composing state whenever there + // is or was a selection regardless of whether it changed or not. + if (hasOrHadSelection || (selectionChangedOrSafeToReset + && !mWordComposer.moveCursorByAndReturnIfInsideComposingWord(moveAmount))) { // If we are composing a word and moving the cursor, we would want to set a // suggestion span for recorrection to work correctly. Unfortunately, that // would involve the keyboard committing some new text, which would move the @@ -369,10 +353,12 @@ public final class InputLogic { newSelStart, newSelEnd, false /* shouldFinishComposition */); } + // The cursor has been moved : we now accept to perform recapitalization + mRecapitalizeStatus.enable(); // We moved the cursor. If we are touching a word, we need to resume suggestion. - mLatinIME.mHandler.postResumeSuggestions(); - // Reset the last recapitalization. - mRecapitalizeStatus.deactivate(); + mLatinIME.mHandler.postResumeSuggestions(false /* shouldIncludeResumedWordInSuggestions */); + // Stop the last recapitalization, if started. + mRecapitalizeStatus.stop(); return true; } @@ -393,16 +379,9 @@ public final class InputLogic { final int keyboardShiftMode, // TODO: remove this argument final LatinIME.UIHandler handler) { - // TODO: rework the following to not squash the keycode and the code point into the same - // var because it's confusing. Instead the switch() should handle this in a readable manner. - final int code = - Event.NOT_A_CODE_POINT == event.mCodePoint ? event.mKeyCode : event.mCodePoint; final InputTransaction inputTransaction = new InputTransaction(settingsValues, event, SystemClock.uptimeMillis(), mSpaceState, getActualCapsMode(settingsValues, keyboardShiftMode)); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onCodeInput(code, event.mX, event.mY); - } if (event.mKeyCode != Constants.CODE_DELETE || inputTransaction.mTimestamp > mLastKeyTime + Constants.LONG_PRESS_MILLISECONDS) { mDeleteCount = 0; @@ -424,7 +403,6 @@ public final class InputLogic { switch (event.mKeyCode) { case Constants.CODE_DELETE: handleBackspace(inputTransaction); - LatinImeLogger.logOnDelete(event.mX, event.mY); break; case Constants.CODE_SHIFT: performRecapitalization(inputTransaction.mSettingsValues); @@ -530,12 +508,6 @@ public final class InputLogic { ++mAutoCommitSequenceNumber; mConnection.beginBatchEdit(); if (mWordComposer.isComposingWord()) { - if (settingsValues.mIsInternal) { - if (mWordComposer.isBatchMode()) { - LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ", - mWordComposer); - } - } if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the batch input at the current cursor position. @@ -572,11 +544,8 @@ public final class InputLogic { } } mConnection.endBatchEdit(); - mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime( - getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode()), - // Prev word is 1st word before cursor - getPrevWordsInfoFromNthPreviousWordForSuggestion( - settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */)); + mWordComposer.setCapitalizedModeAtStartComposingTime( + getActualCapsMode(settingsValues, keyboardSwitcher.getKeyboardShiftMode())); } /* The sequence number member is only used in onUpdateBatchInput. It is increased each time @@ -612,10 +581,8 @@ public final class InputLogic { mSpaceState = SpaceState.PHANTOM; keyboardSwitcher.requestUpdatingShiftState( getCurrentAutoCapsState(settingsValues), getCurrentRecapitalizeState()); - mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime( - getActualCapsMode(settingsValues, - keyboardSwitcher.getKeyboardShiftMode()), - new PrevWordsInfo(commitParts[0])); + mWordComposer.setCapitalizedModeAtStartComposingTime(getActualCapsMode( + settingsValues, keyboardSwitcher.getKeyboardShiftMode())); ++mAutoCommitSequenceNumber; } } @@ -675,19 +642,9 @@ public final class InputLogic { || Character.getType(codePoint) == Character.OTHER_SYMBOL) { didAutoCorrect = handleSeparator(inputTransaction, inputTransaction.mEvent.isSuggestionStripPress(), handler); - if (inputTransaction.mSettingsValues.mIsInternal) { - LatinImeLoggerUtils.onSeparator((char)codePoint, - inputTransaction.mEvent.mX, inputTransaction.mEvent.mY); - } } else { didAutoCorrect = false; if (SpaceState.PHANTOM == inputTransaction.mSpaceState) { - if (inputTransaction.mSettingsValues.mIsInternal) { - if (mWordComposer.isComposingWord() && mWordComposer.isBatchMode()) { - LatinImeLoggerUtils.onAutoCorrection("", mWordComposer.getTypedWord(), " ", - mWordComposer); - } - } if (mWordComposer.isCursorFrontOrMiddleOfComposingWord()) { // If we are in the middle of a recorrection, we need to commit the recorrection // first so that we can insert the character at the current cursor position. @@ -748,11 +705,10 @@ public final class InputLogic { (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations) || !settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces)) { // Reset entirely the composing state anyway, then start composing a new word unless - // the character is a single quote or a dash. The idea here is, single quote and dash - // are not separators and they should be treated as normal characters, except in the - // first position where they should not start composing a word. - isComposingWord = (Constants.CODE_SINGLE_QUOTE != codePoint - && Constants.CODE_DASH != codePoint); + // the character is a word connector. The idea here is, word connectors are not + // separators and they should be treated as normal characters, except in the first + // position where they should not start composing a word. + isComposingWord = !settingsValues.mSpacingAndPunctuations.isWordConnector(codePoint); // Here we don't need to reset the last composed word. It will be reset // when we commit this one, if we ever do; if on the other hand we backspace // it entirely and resume suggestions on the previous word, we'd like to still @@ -763,12 +719,7 @@ public final class InputLogic { mWordComposer.processEvent(inputTransaction.mEvent); // If it's the first letter, make note of auto-caps state if (mWordComposer.isSingleLetter()) { - // We pass 1 to getPreviousWordForSuggestion because we were not composing a word - // yet, so the word we want is the 1st word before the cursor. - mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime( - inputTransaction.mShiftState, - getPrevWordsInfoFromNthPreviousWordForSuggestion( - settingsValues.mSpacingAndPunctuations, 1 /* nthPreviousWord */)); + mWordComposer.setCapitalizedModeAtStartComposingTime(inputTransaction.mShiftState); } mConnection.setComposingText(getTextWithUnderline( mWordComposer.getTypedWord()), 1); @@ -786,10 +737,6 @@ public final class InputLogic { mSuggestionStripViewAccessor.dismissAddToDictionaryHint(); } inputTransaction.setRequiresUpdateSuggestions(); - if (settingsValues.mIsInternal) { - LatinImeLoggerUtils.onNonSeparator((char)codePoint, inputTransaction.mEvent.mX, - inputTransaction.mEvent.mY); - } } /** @@ -818,7 +765,7 @@ public final class InputLogic { } // isComposingWord() may have changed since we stored wasComposing if (mWordComposer.isComposingWord()) { - if (settingsValues.mCorrectionEnabled) { + if (settingsValues.mAutoCorrectionEnabled) { final String separator = shouldAvoidSendingCode ? LastComposedWord.NOT_A_SEPARATOR : StringUtils.newSingleCodePointString(codePoint); commitCurrentAutoCorrection(settingsValues, separator, handler); @@ -852,9 +799,6 @@ public final class InputLogic { if (needsPrecedingSpace) { promotePhantomSpace(settingsValues); } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleSeparator(codePoint, wasComposingWord); - } if (!shouldAvoidSendingCode) { sendKeyCodePoint(settingsValues, codePoint); @@ -863,6 +807,7 @@ public final class InputLogic { if (Constants.CODE_SPACE == codePoint) { if (maybeDoubleSpacePeriod(inputTransaction)) { inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); + inputTransaction.setRequiresUpdateSuggestions(); mSpaceState = SpaceState.DOUBLE; } else if (!mSuggestedWords.isPunctuationSuggestions()) { mSpaceState = SpaceState.WEAK; @@ -932,10 +877,6 @@ public final class InputLogic { } if (mWordComposer.isComposingWord()) { if (mWordComposer.isBatchMode()) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - final String word = mWordComposer.getTypedWord(); - ResearchLogger.latinIME_handleBackspace_batch(word, 1); - } final String rejectedSuggestion = mWordComposer.getTypedWord(); mWordComposer.reset(); mWordComposer.setRejectedBatchModeSuggestion(rejectedSuggestion); @@ -950,9 +891,6 @@ public final class InputLogic { inputTransaction.setRequiresUpdateSuggestions(); } else { if (mLastComposedWord.canRevertCommit()) { - if (inputTransaction.mSettingsValues.mIsInternal) { - LatinImeLoggerUtils.onAutoCorrectionCancellation(); - } revertCommit(inputTransaction); return; } @@ -961,9 +899,6 @@ public final class InputLogic { // This is triggered on backspace after a key that inputs multiple characters, // like the smiley key or the .com key. mConnection.deleteSurroundingText(mEnteredText.length(), 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace_cancelTextInput(mEnteredText); - } mEnteredText = null; // If we have mEnteredText, then we know that mHasUncommittedTypedChars == false. // In addition we know that spaceState is false, and that we should not be @@ -975,6 +910,9 @@ public final class InputLogic { if (mConnection.revertDoubleSpacePeriod()) { // No need to reset mSpaceState, it has already be done (that's why we // receive it as a parameter) + inputTransaction.setRequiresUpdateSuggestions(); + mWordComposer.setCapitalizedModeAtStartComposingTime( + WordComposer.CAPS_MODE_OFF); return; } } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) { @@ -993,10 +931,6 @@ public final class InputLogic { mConnection.setSelection(mConnection.getExpectedSelectionEnd(), mConnection.getExpectedSelectionEnd()); mConnection.deleteSurroundingText(numCharsDeleted, 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(numCharsDeleted, - false /* shouldUncommitLogUnit */); - } } else { // There is no selection, just delete one character. if (Constants.NOT_A_CURSOR_POSITION == mConnection.getExpectedSelectionEnd()) { @@ -1031,10 +965,6 @@ public final class InputLogic { final int lengthToDelete = Character.isSupplementaryCodePoint(codePointBeforeCursor) ? 2 : 1; mConnection.deleteSurroundingText(lengthToDelete, 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(lengthToDelete, - true /* shouldUncommitLogUnit */); - } if (mDeleteCount > Constants.DELETE_ACCELERATE_AT) { final int codePointBeforeCursorToDeleteAgain = mConnection.getCodePointBeforeCursor(); @@ -1042,21 +972,18 @@ public final class InputLogic { final int lengthToDeleteAgain = Character.isSupplementaryCodePoint( codePointBeforeCursorToDeleteAgain) ? 2 : 1; mConnection.deleteSurroundingText(lengthToDeleteAgain, 0); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_handleBackspace(lengthToDeleteAgain, - true /* shouldUncommitLogUnit */); - } } } } } - if (inputTransaction.mSettingsValues.isSuggestionStripVisible() + if (inputTransaction.mSettingsValues + .isCurrentOrientationAllowingSuggestionsPerUserSettings() && inputTransaction.mSettingsValues.mSpacingAndPunctuations .mCurrentLanguageHasSpaces && !mConnection.isCursorFollowedByWordCharacter( inputTransaction.mSettingsValues.mSpacingAndPunctuations)) { restartSuggestionsOnWordTouchedByCursor(inputTransaction.mSettingsValues, - true /* includeResumedWordInSuggestions */); + true /* shouldIncludeResumedWordInSuggestions */); } } } @@ -1082,9 +1009,6 @@ public final class InputLogic { mConnection.deleteSurroundingText(2, 0); final String text = lastTwo.charAt(1) + " "; mConnection.commitText(text, 1); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_swapSwapperAndSpace(lastTwo, text); - } inputTransaction.requireShiftUpdate(InputTransaction.SHIFT_UPDATE_NOW); } } @@ -1168,11 +1092,6 @@ public final class InputLogic { final String textToInsert = inputTransaction.mSettingsValues.mSpacingAndPunctuations .mSentenceSeparatorAndSpace; mConnection.commitText(textToInsert, 1); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_maybeDoubleSpacePeriod(textToInsert, - false /* isBatchMode */); - } - mWordComposer.discardPreviousWordForSuggestion(); return true; } return false; @@ -1209,18 +1128,24 @@ public final class InputLogic { * @param settingsValues The current settings values. */ private void performRecapitalization(final SettingsValues settingsValues) { - if (!mConnection.hasSelection()) { - return; // No selection + if (!mConnection.hasSelection() || !mRecapitalizeStatus.mIsEnabled()) { + return; // No selection or recapitalize is disabled for now + } + final int selectionStart = mConnection.getExpectedSelectionStart(); + final int selectionEnd = mConnection.getExpectedSelectionEnd(); + final int numCharsSelected = selectionEnd - selectionStart; + if (numCharsSelected > Constants.MAX_CHARACTERS_FOR_RECAPITALIZATION) { + // We bail out if we have too many characters for performance reasons. We don't want + // to suck possibly multiple-megabyte data. + return; } - // If we have a recapitalize in progress, use it; otherwise, create a new one. - if (!mRecapitalizeStatus.isActive() - || !mRecapitalizeStatus.isSetAt(mConnection.getExpectedSelectionStart(), - mConnection.getExpectedSelectionEnd())) { + // If we have a recapitalize in progress, use it; otherwise, start a new one. + if (!mRecapitalizeStatus.isStarted() + || !mRecapitalizeStatus.isSetAt(selectionStart, selectionEnd)) { final CharSequence selectedText = mConnection.getSelectedText(0 /* flags, 0 for no styles */); if (TextUtils.isEmpty(selectedText)) return; // Race condition with the input connection - mRecapitalizeStatus.initialize(mConnection.getExpectedSelectionStart(), - mConnection.getExpectedSelectionEnd(), selectedText.toString(), + mRecapitalizeStatus.start(selectionStart, selectionEnd, selectedText.toString(), settingsValues.mLocale, settingsValues.mSpacingAndPunctuations.mSortedWordSeparators); // We trim leading and trailing whitespace. @@ -1228,11 +1153,8 @@ public final class InputLogic { } mConnection.finishComposingText(); mRecapitalizeStatus.rotate(); - final int numCharsDeleted = mConnection.getExpectedSelectionEnd() - - mConnection.getExpectedSelectionStart(); - mConnection.setSelection(mConnection.getExpectedSelectionEnd(), - mConnection.getExpectedSelectionEnd()); - mConnection.deleteSurroundingText(numCharsDeleted, 0); + mConnection.setSelection(selectionEnd, selectionEnd); + mConnection.deleteSurroundingText(numCharsSelected, 0); mConnection.commitText(mRecapitalizeStatus.getRecapitalizedString(), 0); mConnection.setSelection(mRecapitalizeStatus.getNewCursorStart(), mRecapitalizeStatus.getNewCursorEnd()); @@ -1243,14 +1165,14 @@ public final class InputLogic { // If correction is not enabled, we don't add words to the user history dictionary. // That's to avoid unintended additions in some sensitive fields, or fields that // expect to receive non-words. - if (!settingsValues.mCorrectionEnabled) return; + if (!settingsValues.mAutoCorrectionEnabled) return; if (TextUtils.isEmpty(suggestion)) return; final boolean wasAutoCapitalized = mWordComposer.wasAutoCapitalized() && !mWordComposer.isMostlyCaps(); final int timeStampInSeconds = (int)TimeUnit.MILLISECONDS.toSeconds( System.currentTimeMillis()); - mSuggest.mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, + mDictionaryFacilitator.addToUserHistory(suggestion, wasAutoCapitalized, prevWordsInfo, timeStampInSeconds, settingsValues.mBlockPotentiallyOffensive); } @@ -1269,7 +1191,7 @@ public final class InputLogic { return; } - final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<SuggestedWords>(); + final AsyncResultHolder<SuggestedWords> holder = new AsyncResultHolder<>(); mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING, SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { @Override @@ -1300,12 +1222,12 @@ public final class InputLogic { * do nothing. * * @param settingsValues the current values of the settings. - * @param includeResumedWordInSuggestions whether to include the word on which we resume + * @param shouldIncludeResumedWordInSuggestions whether to include the word on which we resume * suggestions in the suggestion list. */ // TODO: make this private. public void restartSuggestionsOnWordTouchedByCursor(final SettingsValues settingsValues, - final boolean includeResumedWordInSuggestions) { + final boolean shouldIncludeResumedWordInSuggestions) { // HACK: We may want to special-case some apps that exhibit bad behavior in case of // recorrection. This is a temporary, stopgap measure that will be removed later. // TODO: remove this. @@ -1329,10 +1251,7 @@ public final class InputLogic { final int expectedCursorPosition = mConnection.getExpectedSelectionStart(); if (!mConnection.isCursorTouchingWord(settingsValues.mSpacingAndPunctuations)) { // Show predictions. - mWordComposer.setCapitalizedModeAndPreviousWordAtStartComposingTime( - WordComposer.CAPS_MODE_OFF, - getPrevWordsInfoFromNthPreviousWordForSuggestion( - settingsValues.mSpacingAndPunctuations, 1)); + mWordComposer.setCapitalizedModeAtStartComposingTime(WordComposer.CAPS_MODE_OFF); mLatinIME.mHandler.postUpdateSuggestionStrip(); return; } @@ -1349,9 +1268,9 @@ public final class InputLogic { // we just do not resume because it's safer. final int numberOfCharsInWordBeforeCursor = range.getNumberOfCharsInWordBeforeCursor(); if (numberOfCharsInWordBeforeCursor > expectedCursorPosition) return; - final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); + final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>(); final String typedWord = range.mWord.toString(); - if (includeResumedWordInSuggestions) { + if (shouldIncludeResumedWordInSuggestions) { suggestions.add(new SuggestedWordInfo(typedWord, SuggestedWords.MAX_SUGGESTIONS + 1, SuggestedWordInfo.KIND_TYPED, Dictionary.DICTIONARY_USER_TYPED, @@ -1384,14 +1303,15 @@ public final class InputLogic { settingsValues.mSpacingAndPunctuations, 0 == numberOfCharsInWordBeforeCursor ? 1 : 2); mWordComposer.setComposingWord(codePoints, - mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo); + mLatinIME.getCoordinatesForCurrentKeyboard(codePoints)); mWordComposer.setCursorPositionWithinWord( typedWord.codePointCount(0, numberOfCharsInWordBeforeCursor)); mConnection.setComposingRegion(expectedCursorPosition - numberOfCharsInWordBeforeCursor, expectedCursorPosition + range.getNumberOfCharsInWordAfterCursor()); - if (suggestions.isEmpty()) { - // We come here if there weren't any suggestion spans on this word. We will try to - // compute suggestions for it instead. + if (suggestions.size() <= (shouldIncludeResumedWordInSuggestions ? 1 : 0)) { + // If there weren't any suggestion spans on this word, suggestions#size() will be 1 + // if shouldIncludeResumedWordInSuggestions is true, 0 otherwise. In this case, we + // have no useful suggestions, so we will try to compute some for it instead. mInputLogicHandler.getSuggestedWords(Suggest.SESSION_TYPING, SuggestedWords.NOT_A_SEQUENCE_NUMBER, new OnGetSuggestedWordsCallback() { @Override @@ -1399,7 +1319,7 @@ public final class InputLogic { final SuggestedWords suggestedWordsIncludingTypedWord) { final SuggestedWords suggestedWords; if (suggestedWordsIncludingTypedWord.size() > 1 - && !includeResumedWordInSuggestions) { + && !shouldIncludeResumedWordInSuggestions) { // We were able to compute new suggestions for this word. // Remove the typed word, since we don't want to display it in this // case. The #getSuggestedWordsExcludingTypedWord() method sets @@ -1462,8 +1382,7 @@ public final class InputLogic { } mConnection.deleteSurroundingText(deleteLength, 0); if (!TextUtils.isEmpty(prevWordsInfo.mPrevWord) && !TextUtils.isEmpty(committedWord)) { - mSuggest.mDictionaryFacilitator.cancelAddingUserHistory( - prevWordsInfo, committedWordString); + mDictionaryFacilitator.cancelAddingUserHistory(prevWordsInfo, committedWordString); } final String stringToCommit = originallyTypedWord + mLastComposedWord.mSeparatorString; final SpannableString textToCommit = new SpannableString(stringToCommit); @@ -1473,7 +1392,7 @@ public final class InputLogic { committedWord.length(), Object.class); final int lastCharIndex = textToCommit.length() - 1; // We will collect all suggestions in the following array. - final ArrayList<String> suggestions = CollectionUtils.newArrayList(); + final ArrayList<String> suggestions = new ArrayList<>(); // First, add the committed word to the list of suggestions. suggestions.add(committedWordString); for (final Object span : spans) { @@ -1512,20 +1431,10 @@ public final class InputLogic { // with the typed word, so we need to resume suggestions right away. final int[] codePoints = StringUtils.toCodePointArray(stringToCommit); mWordComposer.setComposingWord(codePoints, - mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo); + mLatinIME.getCoordinatesForCurrentKeyboard(codePoints)); mConnection.setComposingText(textToCommit, 1); } - if (inputTransaction.mSettingsValues.mIsInternal) { - LatinImeLoggerUtils.onSeparator(mLastComposedWord.mSeparatorString, - Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); - } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_revertCommit(committedWord.toString(), - originallyTypedWord.toString(), - mWordComposer.isBatchMode(), mLastComposedWord.mSeparatorString); - } - // Don't restart suggestion yet. We'll restart if the user deletes the - // separator. + // Don't restart suggestion yet. We'll restart if the user deletes the separator. mLastComposedWord = LastComposedWord.NOT_A_COMPOSED_WORD; // We have a separator between the word and the cursor: we should show predictions. inputTransaction.setRequiresUpdateSuggestions(); @@ -1577,7 +1486,7 @@ public final class InputLogic { } public int getCurrentRecapitalizeState() { - if (!mRecapitalizeStatus.isActive() + if (!mRecapitalizeStatus.isStarted() || !mRecapitalizeStatus.isSetAt(mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd())) { // Not recapitalizing at the moment @@ -1609,8 +1518,9 @@ public final class InputLogic { return mConnection.getPrevWordsInfoFromNthPreviousWord( spacingAndPunctuations, nthPreviousWord); } else { - return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? new PrevWordsInfo() - : new PrevWordsInfo(mLastComposedWord.mCommittedWord.toString()); + return LastComposedWord.NOT_A_COMPOSED_WORD == mLastComposedWord ? + PrevWordsInfo.BEGINNING_OF_SENTENCE : + new PrevWordsInfo(mLastComposedWord.mCommittedWord.toString()); } } @@ -1788,9 +1698,6 @@ public final class InputLogic { */ // TODO: replace these two parameters with an InputTransaction private void sendKeyCodePoint(final SettingsValues settingsValues, final int codePoint) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_sendKeyCodePoint(codePoint); - } // TODO: Remove this special handling of digit letters. // For backward compatibility. See {@link InputMethodService#sendKeyChar(char)}. if (codePoint >= '0' && codePoint <= '9') { @@ -1822,9 +1729,6 @@ public final class InputLogic { if (settingsValues.shouldInsertSpacesAutomatically() && settingsValues.mSpacingAndPunctuations.mCurrentLanguageHasSpaces && !mConnection.textBeforeCursorLooksLikeURL()) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_promotePhantomSpace(); - } sendKeyCodePoint(settingsValues, Constants.CODE_SPACE); } } @@ -1866,9 +1770,6 @@ public final class InputLogic { mConnection.setComposingText(batchInputText, 1); } mConnection.endBatchEdit(); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onEndBatchInput(batchInputText, 0, suggestedWords); - } // Space state must be updated before calling updateShiftState mSpaceState = SpaceState.PHANTOM; keyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(settingsValues), @@ -1895,9 +1796,6 @@ public final class InputLogic { if (!mWordComposer.isComposingWord()) return; final String typedWord = mWordComposer.getTypedWord(); if (typedWord.length() > 0) { - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.getInstance().onWordFinished(typedWord, mWordComposer.isBatchMode()); - } commitChosenWord(settingsValues, typedWord, LastComposedWord.COMMIT_TYPE_USER_TYPED_WORD, separatorString); } @@ -1937,15 +1835,6 @@ public final class InputLogic { throw new RuntimeException("We have an auto-correction but the typed word " + "is empty? Impossible! I must commit suicide."); } - if (settingsValues.mIsInternal) { - LatinImeLoggerUtils.onAutoCorrection( - typedWord, autoCorrection, separator, mWordComposer); - } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - final SuggestedWords suggestedWords = mSuggestedWords; - ResearchLogger.latinIme_commitCurrentAutoCorrection(typedWord, autoCorrection, - separator, mWordComposer.isBatchMode(), suggestedWords); - } commitChosenWord(settingsValues, autoCorrection, LastComposedWord.COMMIT_TYPE_DECIDED_WORD, separator); if (!typedWord.equals(autoCorrection)) { @@ -1989,21 +1878,6 @@ public final class InputLogic { // strings. mLastComposedWord = mWordComposer.commitWord(commitType, chosenWordWithSuggestions, separatorString, prevWordsInfo); - final boolean shouldDiscardPreviousWordForSuggestion; - if (0 == StringUtils.codePointCount(separatorString)) { - // Separator is 0-length, we can keep the previous word for suggestion. Either this - // was a manual pick or the language has no spaces in which case we want to keep the - // previous word, or it was the keyboard closing or the cursor moving in which case it - // will be reset anyway. - shouldDiscardPreviousWordForSuggestion = false; - } else { - // Otherwise, we discard if the separator contains any non-whitespace. - shouldDiscardPreviousWordForSuggestion = - !StringUtils.containsOnlyWhitespace(separatorString); - } - if (shouldDiscardPreviousWordForSuggestion) { - mWordComposer.discardPreviousWordForSuggestion(); - } } /** @@ -2023,9 +1897,11 @@ public final class InputLogic { final boolean tryResumeSuggestions, final int remainingTries, // TODO: remove these arguments final LatinIME.UIHandler handler) { + final boolean shouldFinishComposition = mConnection.hasSelection() + || !mConnection.isCursorPositionKnown(); if (!mConnection.resetCachesUponCursorMoveAndReturnSuccess( mConnection.getExpectedSelectionStart(), mConnection.getExpectedSelectionEnd(), - false /* shouldFinishComposition */)) { + shouldFinishComposition)) { if (0 < remainingTries) { handler.postResetCaches(tryResumeSuggestions, remainingTries - 1); return false; @@ -2035,7 +1911,9 @@ public final class InputLogic { } mConnection.tryFixLyingCursorPosition(); if (tryResumeSuggestions) { - handler.postResumeSuggestions(); + // This is triggered when starting input anew, so we want to include the resumed + // word in suggestions. + handler.postResumeSuggestions(true /* shouldIncludeResumedWordInSuggestions */); } return true; } diff --git a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java index f5f072b7a..a2ae74b20 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java +++ b/java/src/com/android/inputmethod/latin/makedict/FormatSpec.java @@ -192,8 +192,9 @@ public final class FormatSpec { public static final int VERSION2 = 2; // Dictionary version used for testing. public static final int VERSION4_ONLY_FOR_TESTING = 399; - public static final int VERSION4 = 401; - public static final int VERSION4_DEV = 402; + public static final int VERSION401 = 401; + public static final int VERSION4 = 402; + public static final int VERSION4_DEV = 403; static final int MINIMUM_SUPPORTED_VERSION = VERSION2; static final int MAXIMUM_SUPPORTED_VERSION = VERSION4_DEV; diff --git a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java index 853392200..31cb59756 100644 --- a/java/src/com/android/inputmethod/latin/makedict/WordProperty.java +++ b/java/src/com/android/inputmethod/latin/makedict/WordProperty.java @@ -18,7 +18,6 @@ package com.android.inputmethod.latin.makedict; import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.latin.BinaryDictionary; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.CombinedFormatUtils; import com.android.inputmethod.latin.utils.StringUtils; @@ -35,6 +34,8 @@ public final class WordProperty implements Comparable<WordProperty> { public final ProbabilityInfo mProbabilityInfo; public final ArrayList<WeightedString> mShortcutTargets; public final ArrayList<WeightedString> mBigrams; + // TODO: Support mIsBeginningOfSentence. + public final boolean mIsBeginningOfSentence; public final boolean mIsNotAWord; public final boolean mIsBlacklistEntry; public final boolean mHasShortcuts; @@ -51,6 +52,7 @@ public final class WordProperty implements Comparable<WordProperty> { mProbabilityInfo = probabilityInfo; mShortcutTargets = shortcutTargets; mBigrams = bigrams; + mIsBeginningOfSentence = false; mIsNotAWord = isNotAWord; mIsBlacklistEntry = isBlacklistEntry; mHasBigrams = bigrams != null && !bigrams.isEmpty(); @@ -75,8 +77,9 @@ public final class WordProperty implements Comparable<WordProperty> { final ArrayList<Integer> shortcutProbabilities) { mWord = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints); mProbabilityInfo = createProbabilityInfoFromArray(probabilityInfo); - mShortcutTargets = CollectionUtils.newArrayList(); - mBigrams = CollectionUtils.newArrayList(); + mShortcutTargets = new ArrayList<>(); + mBigrams = new ArrayList<>(); + mIsBeginningOfSentence = false; mIsNotAWord = isNotAWord; mIsBlacklistEntry = isBlacklisted; mHasShortcuts = hasShortcuts; diff --git a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java index a446672cb..ab3ef964e 100644 --- a/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java +++ b/java/src/com/android/inputmethod/latin/personalization/AccountUtils.java @@ -35,7 +35,7 @@ public class AccountUtils { } public static List<String> getDeviceAccountsEmailAddresses(final Context context) { - final ArrayList<String> retval = new ArrayList<String>(); + final ArrayList<String> retval = new ArrayList<>(); for (final Account account : getAccounts(context)) { final String name = account.name; if (Patterns.EMAIL_ADDRESS.matcher(name).matches()) { @@ -54,7 +54,7 @@ public class AccountUtils { */ public static List<String> getDeviceAccountsWithDomain( final Context context, final String domain) { - final ArrayList<String> retval = new ArrayList<String>(); + final ArrayList<String> retval = new ArrayList<>(); final String atDomain = "@" + domain.toLowerCase(Locale.ROOT); for (final Account account : getAccounts(context)) { if (account.name.toLowerCase(Locale.ROOT).endsWith(atDomain)) { diff --git a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java index 06bdba054..1ba7b366f 100644 --- a/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java +++ b/java/src/com/android/inputmethod/latin/personalization/DecayingExpandableBinaryDictionaryBase.java @@ -31,7 +31,6 @@ import java.util.Map; * model. */ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableBinaryDictionary { - private static final String TAG = DecayingExpandableBinaryDictionaryBase.class.getSimpleName(); private static final boolean DBG_DUMP_ON_CLOSE = false; /** Any pair being typed or picked */ @@ -81,4 +80,10 @@ public abstract class DecayingExpandableBinaryDictionaryBase extends ExpandableB /* package */ void runGCIfRequired() { runGCIfRequired(false /* mindsBlockByGC */); } + + @Override + public boolean isValidWord(final String word) { + // Strings out of this dictionary should not be considered existing words. + return false; + } } diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java new file mode 100644 index 000000000..9d72de8c5 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDataChunk.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2014 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 java.util.Collections; +import java.util.List; +import java.util.Locale; + +public class PersonalizationDataChunk { + public final boolean mInputByUser; + public final List<String> mTokens; + public final int mTimestampInSeconds; + public final String mPackageName; + public final Locale mlocale = null; + + public PersonalizationDataChunk(boolean inputByUser, final List<String> tokens, + final int timestampInSeconds, final String packageName) { + mInputByUser = inputByUser; + mTokens = Collections.unmodifiableList(tokens); + mTimestampInSeconds = timestampInSeconds; + mPackageName = packageName; + } +} diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java index 1423fceff..19fa29e5f 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionary.java @@ -38,10 +38,4 @@ public class PersonalizationDictionary extends DecayingExpandableBinaryDictionar final Locale locale, final File dictFile) { return PersonalizationHelper.getPersonalizationDictionary(context, locale); } - - @Override - public boolean isValidWord(final String word) { - // Strings out of this dictionary should not be considered existing words. - return false; - } } diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java index 9bef7a198..450644032 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationDictionarySessionRegistrar.java @@ -19,18 +19,15 @@ package com.android.inputmethod.latin.personalization; import android.content.Context; import android.content.res.Configuration; -import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; -import com.android.inputmethod.latin.utils.DistracterFilter; +import com.android.inputmethod.latin.DictionaryFacilitator; public class PersonalizationDictionarySessionRegistrar { public static void init(final Context context, - final DictionaryFacilitatorForSuggest dictionaryFacilitator, - final DistracterFilter distracterFilter) { + final DictionaryFacilitator dictionaryFacilitator) { } public static void onConfigurationChanged(final Context context, final Configuration conf, - final DictionaryFacilitatorForSuggest dictionaryFacilitator, - final DistracterFilter distracterFilter) { + final DictionaryFacilitator dictionaryFacilitator) { } public static void onUpdateData(final Context context, final String type) { diff --git a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java index 6ef505e76..aac40940b 100644 --- a/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java +++ b/java/src/com/android/inputmethod/latin/personalization/PersonalizationHelper.java @@ -16,12 +16,11 @@ package com.android.inputmethod.latin.personalization; -import com.android.inputmethod.latin.utils.CollectionUtils; -import com.android.inputmethod.latin.utils.FileUtils; - import android.content.Context; import android.util.Log; +import com.android.inputmethod.latin.utils.FileUtils; + import java.io.File; import java.io.FilenameFilter; import java.lang.ref.SoftReference; @@ -33,9 +32,9 @@ public class PersonalizationHelper { private static final String TAG = PersonalizationHelper.class.getSimpleName(); private static final boolean DEBUG = false; private static final ConcurrentHashMap<String, SoftReference<UserHistoryDictionary>> - sLangUserHistoryDictCache = CollectionUtils.newConcurrentHashMap(); + sLangUserHistoryDictCache = new ConcurrentHashMap<>(); private static final ConcurrentHashMap<String, SoftReference<PersonalizationDictionary>> - sLangPersonalizationDictCache = CollectionUtils.newConcurrentHashMap(); + sLangPersonalizationDictCache = new ConcurrentHashMap<>(); public static UserHistoryDictionary getUserHistoryDictionary( final Context context, final Locale locale) { @@ -54,8 +53,7 @@ public class PersonalizationHelper { } } final UserHistoryDictionary dict = new UserHistoryDictionary(context, locale); - sLangUserHistoryDictCache.put(localeStr, - new SoftReference<UserHistoryDictionary>(dict)); + sLangUserHistoryDictCache.put(localeStr, new SoftReference<>(dict)); return dict; } } @@ -108,8 +106,7 @@ public class PersonalizationHelper { } } final PersonalizationDictionary dict = new PersonalizationDictionary(context, locale); - sLangPersonalizationDictCache.put( - localeStr, new SoftReference<PersonalizationDictionary>(dict)); + sLangPersonalizationDictCache.put(localeStr, new SoftReference<>(dict)); return dict; } } diff --git a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java index f89caf921..ea1035612 100644 --- a/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java +++ b/java/src/com/android/inputmethod/latin/personalization/UserHistoryDictionary.java @@ -23,6 +23,7 @@ import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.ExpandableBinaryDictionary; import com.android.inputmethod.latin.PrevWordsInfo; +import com.android.inputmethod.latin.utils.DistracterFilter; import java.io.File; import java.util.Locale; @@ -46,12 +47,6 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas return PersonalizationHelper.getUserHistoryDictionary(context, locale); } - @Override - public boolean isValidWord(final String word) { - // Strings out of this dictionary should not be considered existing words. - return false; - } - /** * Add a word to the user history dictionary. * @@ -60,10 +55,11 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas * @param word the word the user inputted * @param isValid whether the word is valid or not * @param timestamp the timestamp when the word has been inputted + * @param distracterFilter the filter to check whether the word is a distracter */ public static void addToDictionary(final ExpandableBinaryDictionary userHistoryDictionary, final PrevWordsInfo prevWordsInfo, final String word, final boolean isValid, - final int timestamp) { + final int timestamp, final DistracterFilter distracterFilter) { final String prevWord = prevWordsInfo.mPrevWord; if (word.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH || (prevWord != null && prevWord.length() >= Constants.DICTIONARY_MAX_WORD_LENGTH)) { @@ -71,8 +67,9 @@ public class UserHistoryDictionary extends DecayingExpandableBinaryDictionaryBas } final int frequency = isValid ? FREQUENCY_FOR_WORDS_IN_DICTS : FREQUENCY_FOR_WORDS_NOT_IN_DICTS; - userHistoryDictionary.addUnigramEntry(word, frequency, null /* shortcutTarget */, - 0 /* shortcutFreq */, false /* isNotAWord */, false /* isBlacklisted */, timestamp); + userHistoryDictionary.addUnigramEntryWithCheckingDistracter(word, frequency, + null /* shortcutTarget */, 0 /* shortcutFreq */, false /* isNotAWord */, + false /* isBlacklisted */, timestamp, distracterFilter); // Do not insert a word as a bigram of itself if (word.equals(prevWord)) { return; diff --git a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java index 39977e76f..31fa86774 100644 --- a/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java +++ b/java/src/com/android/inputmethod/latin/settings/AdditionalSubtypeSettings.java @@ -47,7 +47,6 @@ import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.DialogUtils; import com.android.inputmethod.latin.utils.IntentUtils; import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; @@ -101,7 +100,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { super(context, android.R.layout.simple_spinner_item); setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - final TreeSet<SubtypeLocaleItem> items = CollectionUtils.newTreeSet(); + final TreeSet<SubtypeLocaleItem> items = new TreeSet<>(); final InputMethodInfo imi = RichInputMethodManager.getInstance() .getInputMethodInfoOfThisIme(); final int count = imi.getSubtypeCount(); @@ -369,7 +368,6 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { mSubtype = (InputMethodSubtype)source.readParcelable(null); } - @SuppressWarnings("hiding") public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { @Override @@ -516,8 +514,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { localeString, keyboardLayoutSetName); } - private AlertDialog createDialog( - @SuppressWarnings("unused") final SubtypePreference subtypePref) { + private AlertDialog createDialog(final SubtypePreference subtypePref) { final AlertDialog.Builder builder = new AlertDialog.Builder( DialogUtils.getPlatformDialogThemeContext(getActivity())); builder.setTitle(R.string.custom_input_styles_title) @@ -555,7 +552,7 @@ public final class AdditionalSubtypeSettings extends PreferenceFragment { private InputMethodSubtype[] getSubtypes() { final PreferenceGroup group = getPreferenceScreen(); - final ArrayList<InputMethodSubtype> subtypes = CollectionUtils.newArrayList(); + final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); final int count = group.getPreferenceCount(); for (int i = 0; i < count; i++) { final Preference pref = group.getPreference(i); diff --git a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java index c4c1234fc..845ddb377 100644 --- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java @@ -25,11 +25,12 @@ import android.preference.CheckBoxPreference; import android.preference.Preference; import android.preference.Preference.OnPreferenceClickListener; import android.preference.PreferenceFragment; +import android.preference.PreferenceGroup; import android.preference.PreferenceScreen; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.DictionaryDumpBroadcastReceiver; -import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.debug.ExternalDictionaryGetterForDebug; import com.android.inputmethod.latin.utils.ApplicationUtils; @@ -40,8 +41,6 @@ public final class DebugSettings extends PreferenceFragment public static final String PREF_DEBUG_MODE = "debug_mode"; public static final String PREF_FORCE_NON_DISTINCT_MULTITOUCH = "force_non_distinct_multitouch"; - public static final String PREF_USABILITY_STUDY_MODE = "usability_study_mode"; - public static final String PREF_STATISTICS_LOGGING = "enable_logging"; public static final String PREF_KEY_PREVIEW_SHOW_UP_START_SCALE = "pref_key_preview_show_up_start_scale"; public static final String PREF_KEY_PREVIEW_DISMISS_END_SCALE = @@ -51,18 +50,14 @@ public final class DebugSettings extends PreferenceFragment public static final String PREF_KEY_PREVIEW_DISMISS_DURATION = "pref_key_preview_dismiss_duration"; private static final String PREF_READ_EXTERNAL_DICTIONARY = "read_external_dictionary"; - private static final String PREF_DUMP_CONTACTS_DICT = "dump_contacts_dict"; - private static final String PREF_DUMP_USER_DICT = "dump_user_dict"; - private static final String PREF_DUMP_USER_HISTORY_DICT = "dump_user_history_dict"; - private static final String PREF_DUMP_PERSONALIZATION_DICT = "dump_personalization_dict"; + private static final String PREF_KEY_DUMP_DICTS = "pref_key_dump_dictionaries"; + private static final String PREF_KEY_DUMP_DICT_PREFIX = "pref_key_dump_dictionaries"; + private static final String DICT_NAME_KEY_FOR_EXTRAS = "dict_name"; public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "pref_sliding_key_input_preview"; public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout"; - private static final boolean SHOW_STATISTICS_LOGGING = false; - private boolean mServiceNeedsRestart = false; private CheckBoxPreference mDebugMode; - private CheckBoxPreference mStatisticsLoggingPref; @Override public void onCreate(Bundle icicle) { @@ -71,21 +66,6 @@ public final class DebugSettings extends PreferenceFragment SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); prefs.registerOnSharedPreferenceChangeListener(this); - final Preference usabilityStudyPref = findPreference(PREF_USABILITY_STUDY_MODE); - if (usabilityStudyPref instanceof CheckBoxPreference) { - final CheckBoxPreference checkbox = (CheckBoxPreference)usabilityStudyPref; - checkbox.setChecked(prefs.getBoolean(PREF_USABILITY_STUDY_MODE, - LatinImeLogger.getUsabilityStudyMode(prefs))); - checkbox.setSummary(R.string.settings_warning_researcher_mode); - } - final Preference statisticsLoggingPref = findPreference(PREF_STATISTICS_LOGGING); - if (statisticsLoggingPref instanceof CheckBoxPreference) { - mStatisticsLoggingPref = (CheckBoxPreference) statisticsLoggingPref; - if (!SHOW_STATISTICS_LOGGING) { - getPreferenceScreen().removePreference(statisticsLoggingPref); - } - } - final PreferenceScreen readExternalDictionary = (PreferenceScreen) findPreference(PREF_READ_EXTERNAL_DICTIONARY); if (null != readExternalDictionary) { @@ -101,16 +81,18 @@ public final class DebugSettings extends PreferenceFragment }); } + final PreferenceGroup dictDumpPreferenceGroup = + (PreferenceGroup)findPreference(PREF_KEY_DUMP_DICTS); final OnPreferenceClickListener dictDumpPrefClickListener = new DictDumpPrefClickListener(this); - findPreference(PREF_DUMP_CONTACTS_DICT).setOnPreferenceClickListener( - dictDumpPrefClickListener); - findPreference(PREF_DUMP_USER_DICT).setOnPreferenceClickListener( - dictDumpPrefClickListener); - findPreference(PREF_DUMP_USER_HISTORY_DICT).setOnPreferenceClickListener( - dictDumpPrefClickListener); - findPreference(PREF_DUMP_PERSONALIZATION_DICT).setOnPreferenceClickListener( - dictDumpPrefClickListener); + for (final String dictName : DictionaryFacilitator.DICT_TYPE_TO_CLASS.keySet()) { + final Preference preference = new Preference(getActivity()); + preference.setKey(PREF_KEY_DUMP_DICT_PREFIX + dictName); + preference.setTitle("Dump " + dictName + " dictionary"); + preference.setOnPreferenceClickListener(dictDumpPrefClickListener); + preference.getExtras().putString(DICT_NAME_KEY_FOR_EXTRAS, dictName); + dictDumpPreferenceGroup.addPreference(preference); + } final Resources res = getResources(); setupKeyLongpressTimeoutSettings(prefs, res); setupKeyPreviewAnimationDuration(prefs, res, PREF_KEY_PREVIEW_SHOW_UP_DURATION, @@ -138,18 +120,7 @@ public final class DebugSettings extends PreferenceFragment @Override public boolean onPreferenceClick(final Preference arg0) { - final String dictName; - if (arg0.getKey().equals(PREF_DUMP_CONTACTS_DICT)) { - dictName = Dictionary.TYPE_CONTACTS; - } else if (arg0.getKey().equals(PREF_DUMP_USER_DICT)) { - dictName = Dictionary.TYPE_USER; - } else if (arg0.getKey().equals(PREF_DUMP_USER_HISTORY_DICT)) { - dictName = Dictionary.TYPE_USER_HISTORY; - } else if (arg0.getKey().equals(PREF_DUMP_PERSONALIZATION_DICT)) { - dictName = Dictionary.TYPE_PERSONALIZATION; - } else { - dictName = null; - } + final String dictName = arg0.getExtras().getString(DICT_NAME_KEY_FOR_EXTRAS); if (dictName != null) { final Intent intent = new Intent(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION); @@ -163,27 +134,22 @@ public final class DebugSettings extends PreferenceFragment @Override public void onStop() { super.onStop(); - if (mServiceNeedsRestart) Process.killProcess(Process.myPid()); + if (mServiceNeedsRestart) { + Process.killProcess(Process.myPid()); + } } @Override public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { - if (key.equals(PREF_DEBUG_MODE)) { - if (mDebugMode != null) { - mDebugMode.setChecked(prefs.getBoolean(PREF_DEBUG_MODE, false)); - final boolean checked = mDebugMode.isChecked(); - if (mStatisticsLoggingPref != null) { - if (checked) { - getPreferenceScreen().addPreference(mStatisticsLoggingPref); - } else { - getPreferenceScreen().removePreference(mStatisticsLoggingPref); - } - } - updateDebugMode(); - mServiceNeedsRestart = true; - } - } else if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)) { + if (key.equals(PREF_DEBUG_MODE) && mDebugMode != null) { + mDebugMode.setChecked(prefs.getBoolean(PREF_DEBUG_MODE, false)); + updateDebugMode(); mServiceNeedsRestart = true; + return; + } + if (key.equals(PREF_FORCE_NON_DISTINCT_MULTITOUCH)) { + mServiceNeedsRestart = true; + return; } } diff --git a/java/src/com/android/inputmethod/latin/settings/Settings.java b/java/src/com/android/inputmethod/latin/settings/Settings.java index d4f6bcd10..235847799 100644 --- a/java/src/com/android/inputmethod/latin/settings/Settings.java +++ b/java/src/com/android/inputmethod/latin/settings/Settings.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.res.Resources; +import android.os.Build; import android.preference.PreferenceManager; import android.util.Log; @@ -60,6 +61,10 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang "pref_key_use_double_space_period"; public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE = "pref_key_block_potentially_offensive"; + public static final boolean ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS = + (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + || (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT + && Build.VERSION.CODENAME.equals("REL")); public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY = "pref_show_language_switch_key"; public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST = @@ -327,10 +332,6 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang R.array.keypress_vibration_durations, DEFAULT_KEYPRESS_VIBRATION_DURATION)); } - public static boolean readUsabilityStudyMode(final SharedPreferences prefs) { - return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true); - } - public static float readKeyPreviewAnimationScale(final SharedPreferences prefs, final String prefKey, final float defaultValue) { final float fraction = prefs.getFloat(prefKey, UNDEFINED_PREFERENCE_VALUE_FLOAT); diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java index e1d38e7c4..5eb0377c7 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java @@ -59,7 +59,7 @@ public final class SettingsFragment extends InputMethodSettingsFragment private static final boolean DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = false; private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS = DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS - || Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */; + || Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2; private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) { final Preference preference = findPreference(preferenceKey); @@ -152,10 +152,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment miscSettings.removePreference(aboutSettings); } } - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - // The about screen contains items that may be confusing in development-only versions. - miscSettings.removePreference(aboutSettings); - } final boolean showVoiceKeyOption = res.getBoolean( R.bool.config_enable_show_voice_key_option); @@ -169,6 +165,13 @@ public final class SettingsFragment extends InputMethodSettingsFragment removePreference(Settings.PREF_VIBRATE_ON, generalSettings); removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings); } + if (!Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS) { + removePreference( + Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, advancedSettings); + removePreference( + Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, advancedSettings); + } + // TODO: consolidate key preview dismiss delay with the key preview animation parameters. if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) { @@ -199,9 +202,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON, advancedSettings); } - setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, - Settings.readShowsLanguageSwitchKey(prefs)); - final PreferenceGroup textCorrectionGroup = (PreferenceGroup) findPreference(Settings.PREF_CORRECTION_SETTINGS); final PreferenceScreen dictionaryLink = @@ -213,6 +213,20 @@ public final class SettingsFragment extends InputMethodSettingsFragment textCorrectionGroup.removePreference(dictionaryLink); } + if (ProductionFlag.IS_METRICS_LOGGING_SUPPORTED) { + final Preference enableMetricsLogging = + findPreference(Settings.PREF_ENABLE_METRICS_LOGGING); + if (enableMetricsLogging != null) { + final int applicationLabelRes = context.getApplicationInfo().labelRes; + final String applicationName = res.getString(applicationLabelRes); + final String enableMetricsLoggingTitle = res.getString( + R.string.enable_metrics_logging, applicationName); + enableMetricsLogging.setTitle(enableMetricsLoggingTitle); + } + } else { + removePreference(Settings.PREF_ENABLE_METRICS_LOGGING, textCorrectionGroup); + } + final Preference editPersonalDictionary = findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY); final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent(); @@ -299,9 +313,6 @@ public final class SettingsFragment extends InputMethodSettingsFragment if (key.equals(Settings.PREF_POPUP_ON)) { setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, Settings.readKeyPreviewPopupEnabled(prefs, res)); - } else if (key.equals(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY)) { - setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, - Settings.readShowsLanguageSwitchKey(prefs)); } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) { LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity()); } diff --git a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java index 16fd05877..389d9a869 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -28,6 +28,7 @@ import com.android.inputmethod.compat.AppWorkaroundsUtils; import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; +import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.ResourceUtils; import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; @@ -84,7 +85,7 @@ public final class SettingsValues { public final int mKeyPreviewPopupDismissDelay; private final boolean mAutoCorrectEnabled; public final float mAutoCorrectionThreshold; - public final boolean mCorrectionEnabled; + public final boolean mAutoCorrectionEnabled; public final int mSuggestionVisibility; public final int mDisplayOrientation; private final AsyncResultHolder<AppWorkaroundsUtils> mAppWorkarounds; @@ -109,7 +110,8 @@ public final class SettingsValues { // Store the input attributes if (null == inputAttributes) { - mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */); + mInputAttributes = new InputAttributes( + null, false /* isFullscreenMode */, context.getPackageName()); } else { mInputAttributes = inputAttributes; } @@ -121,13 +123,18 @@ public final class SettingsValues { mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res); mSlidingKeyInputPreviewEnabled = prefs.getBoolean( DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true); - mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res); + mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res) + && !mInputAttributes.mIsPasswordField + && !mInputAttributes.hasNoMicrophoneKeyOption() + && SubtypeSwitcher.getInstance().isShortcutImeEnabled(); final String autoCorrectionThresholdRawValue = prefs.getString( Settings.PREF_AUTO_CORRECTION_THRESHOLD, res.getString(R.string.auto_correction_threshold_mode_index_modest)); - mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean( - Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false); - mShowsLanguageSwitchKey = Settings.readShowsLanguageSwitchKey(prefs); + mIncludesOtherImesInLanguageSwitchList = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS + ? prefs.getBoolean(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false) + : true /* forcibly */; + mShowsLanguageSwitchKey = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS + ? Settings.readShowsLanguageSwitchKey(prefs) : true /* forcibly */; mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true); mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true); @@ -148,7 +155,7 @@ public final class SettingsValues { mGestureFloatingPreviewTextEnabled = prefs.getBoolean( Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true); mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res); - mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect; + mAutoCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect; final String showSuggestionsSetting = prefs.getString( Settings.PREF_SHOW_SUGGESTIONS_SETTING, res.getString(R.string.prefs_suggestion_visibility_default_value)); @@ -171,7 +178,7 @@ public final class SettingsValues { ResourceUtils.getFloatFromFraction( res, R.fraction.config_key_preview_dismiss_end_scale)); mDisplayOrientation = res.getConfiguration().orientation; - mAppWorkarounds = new AsyncResultHolder<AppWorkaroundsUtils>(); + mAppWorkarounds = new AsyncResultHolder<>(); final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo( mInputAttributes.mTargetApplicationPackageName); if (null != packageInfo) { @@ -187,11 +194,12 @@ public final class SettingsValues { } public boolean isSuggestionsRequested() { - return mInputAttributes.mIsSettingsSuggestionStripOn - && (mCorrectionEnabled || isSuggestionStripVisible()); + return mInputAttributes.mShouldShowSuggestions + && (mAutoCorrectionEnabled + || isCurrentOrientationAllowingSuggestionsPerUserSettings()); } - public boolean isSuggestionStripVisible() { + public boolean isCurrentOrientationAllowingSuggestionsPerUserSettings() { return (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_VALUE) || (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE && mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT); @@ -315,18 +323,18 @@ public final class SettingsValues { private static boolean needsToShowVoiceInputKey(final SharedPreferences prefs, final Resources res) { - if (!prefs.contains(Settings.PREF_VOICE_INPUT_KEY)) { - // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to - // {@link Settings#PREF_VOICE_INPUT_KEY}. + // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to + // {@link Settings#PREF_VOICE_INPUT_KEY}. + if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) { final String voiceModeMain = res.getString(R.string.voice_mode_main); final String voiceMode = prefs.getString( Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain); final boolean shouldShowVoiceInputKey = voiceModeMain.equals(voiceMode); - prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey).apply(); - } - // Remove the obsolete preference if exists. - if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) { - prefs.edit().remove(Settings.PREF_VOICE_MODE_OBSOLETE).apply(); + prefs.edit() + .putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey) + // Remove the obsolete preference if exists. + .remove(Settings.PREF_VOICE_MODE_OBSOLETE) + .apply(); } return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true); } @@ -387,8 +395,8 @@ public final class SettingsValues { sb.append("" + mAutoCorrectEnabled); sb.append("\n mAutoCorrectionThreshold = "); sb.append("" + mAutoCorrectionThreshold); - sb.append("\n mCorrectionEnabled = "); - sb.append("" + mCorrectionEnabled); + sb.append("\n mAutoCorrectionEnabled = "); + sb.append("" + mAutoCorrectionEnabled); sb.append("\n mSuggestionVisibility = "); sb.append("" + mSuggestionVisibility); sb.append("\n mDisplayOrientation = "); diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java index 974dfddd3..73d25f6aa 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupStartIndicatorView.java @@ -21,13 +21,13 @@ import android.content.res.ColorStateList; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; +import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; -import com.android.inputmethod.compat.ViewCompatUtils; import com.android.inputmethod.latin.R; public final class SetupStartIndicatorView extends LinearLayout { @@ -96,13 +96,13 @@ public final class SetupStartIndicatorView extends LinearLayout { @Override protected void onDraw(final Canvas canvas) { super.onDraw(canvas); - final int layoutDirection = ViewCompatUtils.getLayoutDirection(this); + final int layoutDirection = ViewCompat.getLayoutDirection(this); final int width = getWidth(); final int height = getHeight(); final float halfHeight = height / 2.0f; final Path path = mIndicatorPath; path.rewind(); - if (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) { + if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) { // Left arrow path.moveTo(width, 0.0f); path.lineTo(0.0f, halfHeight); diff --git a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java index c909507c6..6734e61b8 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupStepIndicatorView.java @@ -20,10 +20,10 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Path; +import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.view.View; -import com.android.inputmethod.compat.ViewCompatUtils; import com.android.inputmethod.latin.R; public final class SetupStepIndicatorView extends View { @@ -38,12 +38,12 @@ public final class SetupStepIndicatorView extends View { } public void setIndicatorPosition(final int stepPos, final int totalStepNum) { - final int layoutDirection = ViewCompatUtils.getLayoutDirection(this); + final int layoutDirection = ViewCompat.getLayoutDirection(this); // The indicator position is the center of the partition that is equally divided into // the total step number. final float partionWidth = 1.0f / totalStepNum; final float pos = stepPos * partionWidth + partionWidth / 2.0f; - mXRatio = (layoutDirection == ViewCompatUtils.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos; + mXRatio = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL) ? 1.0f - pos : pos; invalidate(); } diff --git a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java index 5072fabd6..bcac05a6a 100644 --- a/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java +++ b/java/src/com/android/inputmethod/latin/setup/SetupWizardActivity.java @@ -37,7 +37,6 @@ import com.android.inputmethod.compat.TextViewCompatUtils; import com.android.inputmethod.compat.ViewCompatUtils; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.settings.SettingsActivity; -import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.LeakGuardHandlerWrapper; import java.util.ArrayList; @@ -482,7 +481,7 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL static final class SetupStepGroup { private final SetupStepIndicatorView mIndicatorView; - private final ArrayList<SetupStep> mGroup = CollectionUtils.newArrayList(); + private final ArrayList<SetupStep> mGroup = new ArrayList<>(); public SetupStepGroup(final SetupStepIndicatorView indicatorView) { mIndicatorView = indicatorView; diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 65ebcf5f1..8d495646d 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -27,7 +27,6 @@ import android.view.inputmethod.InputMethodSubtype; import android.view.textservice.SuggestionsInfo; import com.android.inputmethod.keyboard.KeyboardLayoutSet; -import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.ContactsBinaryDictionary; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.DictionaryCollection; @@ -77,7 +76,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService private final Object mUseContactsLock = new Object(); private final HashSet<WeakReference<DictionaryCollection>> mDictionaryCollectionsList = - CollectionUtils.newHashSet(); + new HashSet<>(); public static final int SCRIPT_LATIN = 0; public static final int SCRIPT_CYRILLIC = 1; @@ -94,7 +93,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService // proximity to pass to the dictionary descent algorithm. // IMPORTANT: this only contains languages - do not write countries in there. // Only the language is searched from the map. - mLanguageToScript = CollectionUtils.newTreeMap(); + mLanguageToScript = new TreeMap<>(); mLanguageToScript.put("cs", SCRIPT_LATIN); mLanguageToScript.put("da", SCRIPT_LATIN); mLanguageToScript.put("de", SCRIPT_LATIN); @@ -255,7 +254,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService mOriginalText = originalText; mRecommendedThreshold = recommendedThreshold; mMaxLength = maxLength; - mSuggestions = CollectionUtils.newArrayList(maxLength + 1); + mSuggestions = new ArrayList<>(maxLength + 1); mScores = new int[mMaxLength]; } @@ -441,8 +440,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService } } dictionaryCollection.addDictionary(mContactsDictionary); - mDictionaryCollectionsList.add( - new WeakReference<DictionaryCollection>(dictionaryCollection)); + mDictionaryCollectionsList.add(new WeakReference<>(dictionaryCollection)); } return new DictAndKeyboard(dictionaryCollection, keyboardLayoutSet); } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java index e951f5a89..55274cfe2 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java @@ -16,6 +16,7 @@ package com.android.inputmethod.latin.spellcheck; +import android.content.res.Resources; import android.os.Binder; import android.text.TextUtils; import android.util.Log; @@ -24,17 +25,20 @@ import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; import com.android.inputmethod.latin.PrevWordsInfo; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; +import java.util.Locale; public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheckerSession { private static final String TAG = AndroidSpellCheckerSession.class.getSimpleName(); private static final boolean DBG = false; private final static String[] EMPTY_STRING_ARRAY = new String[0]; + private final Resources mResources; + private SentenceLevelAdapter mSentenceLevelAdapter; public AndroidSpellCheckerSession(AndroidSpellCheckerService service) { super(service); + mResources = service.getResources(); } private SentenceSuggestionsInfo fixWronglyInvalidatedWordWithSingleQuote(TextInfo ti, @@ -44,10 +48,9 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck return null; } final int N = ssi.getSuggestionsCount(); - final ArrayList<Integer> additionalOffsets = CollectionUtils.newArrayList(); - final ArrayList<Integer> additionalLengths = CollectionUtils.newArrayList(); - final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = - CollectionUtils.newArrayList(); + final ArrayList<Integer> additionalOffsets = new ArrayList<>(); + final ArrayList<Integer> additionalLengths = new ArrayList<>(); + final ArrayList<SuggestionsInfo> additionalSuggestionsInfos = new ArrayList<>(); String currentWord = null; for (int i = 0; i < N; ++i) { final SuggestionsInfo si = ssi.getSuggestionsInfoAt(i); @@ -117,8 +120,7 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck @Override public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) { - final SentenceSuggestionsInfo[] retval = - super.onGetSentenceSuggestionsMultiple(textInfos, suggestionsLimit); + final SentenceSuggestionsInfo[] retval = splitAndSuggest(textInfos, suggestionsLimit); if (retval == null || retval.length != textInfos.length) { return retval; } @@ -132,6 +134,58 @@ public final class AndroidSpellCheckerSession extends AndroidWordLevelSpellCheck return retval; } + /** + * Get sentence suggestions for specified texts in an array of TextInfo. This is taken from + * SpellCheckerService#onGetSentenceSuggestionsMultiple that we can't use because it's + * using private variables. + * The default implementation splits the input text to words and returns + * {@link SentenceSuggestionsInfo} which contains suggestions for each word. + * This function will run on the incoming IPC thread. + * So, this is not called on the main thread, + * but will be called in series on another thread. + * @param textInfos an array of the text metadata + * @param suggestionsLimit the maximum number of suggestions to be returned + * @return an array of {@link SentenceSuggestionsInfo} returned by + * {@link SpellCheckerService.Session#onGetSuggestions(TextInfo, int)} + */ + private SentenceSuggestionsInfo[] splitAndSuggest(TextInfo[] textInfos, int suggestionsLimit) { + if (textInfos == null || textInfos.length == 0) { + return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS; + } + SentenceLevelAdapter sentenceLevelAdapter; + synchronized(this) { + sentenceLevelAdapter = mSentenceLevelAdapter; + if (sentenceLevelAdapter == null) { + final String localeStr = getLocale(); + if (!TextUtils.isEmpty(localeStr)) { + sentenceLevelAdapter = new SentenceLevelAdapter(mResources, + new Locale(localeStr)); + mSentenceLevelAdapter = sentenceLevelAdapter; + } + } + } + if (sentenceLevelAdapter == null) { + return SentenceLevelAdapter.EMPTY_SENTENCE_SUGGESTIONS_INFOS; + } + final int infosSize = textInfos.length; + final SentenceSuggestionsInfo[] retval = new SentenceSuggestionsInfo[infosSize]; + for (int i = 0; i < infosSize; ++i) { + final SentenceLevelAdapter.SentenceTextInfoParams textInfoParams = + sentenceLevelAdapter.getSplitWords(textInfos[i]); + final ArrayList<SentenceLevelAdapter.SentenceWordItem> mItems = + textInfoParams.mItems; + final int itemsSize = mItems.size(); + final TextInfo[] splitTextInfos = new TextInfo[itemsSize]; + for (int j = 0; j < itemsSize; ++j) { + splitTextInfos[j] = mItems.get(j).mTextInfo; + } + retval[i] = SentenceLevelAdapter.reconstructSuggestions( + textInfoParams, onGetSuggestionsMultiple( + splitTextInfos, suggestionsLimit, true)); + } + return retval; + } + @Override public SuggestionsInfo[] onGetSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) { diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index cf26000d5..0032fcb88 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java @@ -28,7 +28,6 @@ import android.view.textservice.SuggestionsInfo; import android.view.textservice.TextInfo; import com.android.inputmethod.compat.SuggestionsInfoCompatUtils; -import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.PrevWordsInfo; @@ -69,7 +68,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { private static final char CHAR_DELIMITER = '\uFFFC'; private static final int MAX_CACHE_SIZE = 50; private final LruCache<String, SuggestionsParams> mUnigramSuggestionsInfoCache = - new LruCache<String, SuggestionsParams>(MAX_CACHE_SIZE); + new LruCache<>(MAX_CACHE_SIZE); // TODO: Support n-gram input private static String generateKey(final String query, final PrevWordsInfo prevWordsInfo) { @@ -324,7 +323,7 @@ public abstract class AndroidWordLevelSpellCheckerSession extends Session { } else { coordinates = dictInfo.mKeyboard.getCoordinates(codePoints); } - composer.setComposingWord(codePoints, coordinates, null /* previousWord */); + composer.setComposingWord(codePoints, coordinates); // TODO: make a spell checker option to block offensive words or not final ArrayList<SuggestedWordInfo> suggestions = dictInfo.mDictionary.getSuggestions(composer, prevWordsInfo, diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java index 1ffe50681..b33739fc1 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictAndKeyboard.java @@ -16,11 +16,11 @@ 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; +import com.android.inputmethod.latin.Dictionary; /** * A container for a Dictionary and a Keyboard. @@ -28,19 +28,15 @@ import com.android.inputmethod.keyboard.ProximityInfo; public final class DictAndKeyboard { public final Dictionary mDictionary; public 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 ProximityInfo getProximityInfo() { diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java index ba2e0c309..cc52a3e0f 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java @@ -23,7 +23,6 @@ import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.PrevWordsInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.WordComposer; -import com.android.inputmethod.latin.utils.CollectionUtils; import java.util.ArrayList; import java.util.Locale; @@ -47,7 +46,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> { private final Locale mLocale; private int mSize; private volatile boolean mClosed; - final static ArrayList<SuggestedWordInfo> noSuggestions = CollectionUtils.newArrayList(); + final static ArrayList<SuggestedWordInfo> noSuggestions = new ArrayList<>(); private final static DictAndKeyboard dummyDict = new DictAndKeyboard( new Dictionary(Dictionary.TYPE_MAIN) { // TODO: this dummy dictionary should be a singleton in the Dictionary class. @@ -59,7 +58,7 @@ public final class DictionaryPool extends LinkedBlockingQueue<DictAndKeyboard> { return noSuggestions; } @Override - public boolean isValidWord(final String word) { + public boolean isInDictionary(final String word) { // This is never called. However if for some strange reason it ever gets // called, returning true is less destructive (it will not underline the // word in red). diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java new file mode 100644 index 000000000..13352f39e --- /dev/null +++ b/java/src/com/android/inputmethod/latin/spellcheck/SentenceLevelAdapter.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2014 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.content.res.Resources; +import android.view.textservice.SentenceSuggestionsInfo; +import android.view.textservice.SuggestionsInfo; +import android.view.textservice.TextInfo; + +import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.settings.SpacingAndPunctuations; +import com.android.inputmethod.latin.utils.RunInLocale; + +import java.util.ArrayList; +import java.util.Locale; + +/** + * This code is mostly lifted directly from android.service.textservice.SpellCheckerService in + * the framework; maybe that should be protected instead, so that implementers don't have to + * rewrite everything for any small change. + */ +public class SentenceLevelAdapter { + public static final SentenceSuggestionsInfo[] EMPTY_SENTENCE_SUGGESTIONS_INFOS = + new SentenceSuggestionsInfo[] {}; + private static final SuggestionsInfo EMPTY_SUGGESTIONS_INFO = new SuggestionsInfo(0, null); + /** + * Container for split TextInfo parameters + */ + public static class SentenceWordItem { + public final TextInfo mTextInfo; + public final int mStart; + public final int mLength; + public SentenceWordItem(TextInfo ti, int start, int end) { + mTextInfo = ti; + mStart = start; + mLength = end - start; + } + } + + /** + * Container for originally queried TextInfo and parameters + */ + public static class SentenceTextInfoParams { + final TextInfo mOriginalTextInfo; + final ArrayList<SentenceWordItem> mItems; + final int mSize; + public SentenceTextInfoParams(TextInfo ti, ArrayList<SentenceWordItem> items) { + mOriginalTextInfo = ti; + mItems = items; + mSize = items.size(); + } + } + + private static class WordIterator { + private final SpacingAndPunctuations mSpacingAndPunctuations; + public WordIterator(final Resources res, final Locale locale) { + final RunInLocale<SpacingAndPunctuations> job + = new RunInLocale<SpacingAndPunctuations>() { + @Override + protected SpacingAndPunctuations job(final Resources res) { + return new SpacingAndPunctuations(res); + } + }; + mSpacingAndPunctuations = job.runInLocale(res, locale); + } + + public int getEndOfWord(final CharSequence sequence, int index) { + final int length = sequence.length(); + index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1); + while (index < length) { + final int codePoint = Character.codePointAt(sequence, index); + if (mSpacingAndPunctuations.isWordSeparator(codePoint)) { + // If it's a period, we want to stop here only if it's followed by another + // word separator. In all other cases we stop here. + if (Constants.CODE_PERIOD == codePoint) { + final int indexOfNextCodePoint = + index + Character.charCount(Constants.CODE_PERIOD); + if (indexOfNextCodePoint < length + && mSpacingAndPunctuations.isWordSeparator( + Character.codePointAt(sequence, indexOfNextCodePoint))) { + return index; + } + } else { + return index; + } + } + index += Character.charCount(codePoint); + } + return index; + } + + public int getBeginningOfNextWord(final CharSequence sequence, int index) { + final int length = sequence.length(); + if (index >= length) { + return -1; + } + index = index < 0 ? 0 : Character.offsetByCodePoints(sequence, index, 1); + while (index < length) { + final int codePoint = Character.codePointAt(sequence, index); + if (!mSpacingAndPunctuations.isWordSeparator(codePoint)) { + return index; + } + index += Character.charCount(codePoint); + } + return -1; + } + } + + private final WordIterator mWordIterator; + public SentenceLevelAdapter(final Resources res, final Locale locale) { + mWordIterator = new WordIterator(res, locale); + } + + public SentenceTextInfoParams getSplitWords(TextInfo originalTextInfo) { + final WordIterator wordIterator = mWordIterator; + final CharSequence originalText = originalTextInfo.getText(); + final int cookie = originalTextInfo.getCookie(); + final int start = -1; + final int end = originalText.length(); + final ArrayList<SentenceWordItem> wordItems = new ArrayList<SentenceWordItem>(); + int wordStart = wordIterator.getBeginningOfNextWord(originalText, start); + int wordEnd = wordIterator.getEndOfWord(originalText, wordStart); + while (wordStart <= end && wordEnd != -1 && wordStart != -1) { + if (wordEnd >= start && wordEnd > wordStart) { + final String query = originalText.subSequence(wordStart, wordEnd).toString(); + final TextInfo ti = new TextInfo(query, cookie, query.hashCode()); + wordItems.add(new SentenceWordItem(ti, wordStart, wordEnd)); + } + wordStart = wordIterator.getBeginningOfNextWord(originalText, wordEnd); + if (wordStart == -1) { + break; + } + wordEnd = wordIterator.getEndOfWord(originalText, wordStart); + } + return new SentenceTextInfoParams(originalTextInfo, wordItems); + } + + public static SentenceSuggestionsInfo reconstructSuggestions( + SentenceTextInfoParams originalTextInfoParams, SuggestionsInfo[] results) { + if (results == null || results.length == 0) { + return null; + } + if (originalTextInfoParams == null) { + return null; + } + final int originalCookie = originalTextInfoParams.mOriginalTextInfo.getCookie(); + final int originalSequence = + originalTextInfoParams.mOriginalTextInfo.getSequence(); + + final int querySize = originalTextInfoParams.mSize; + final int[] offsets = new int[querySize]; + final int[] lengths = new int[querySize]; + final SuggestionsInfo[] reconstructedSuggestions = new SuggestionsInfo[querySize]; + for (int i = 0; i < querySize; ++i) { + final SentenceWordItem item = originalTextInfoParams.mItems.get(i); + SuggestionsInfo result = null; + for (int j = 0; j < results.length; ++j) { + final SuggestionsInfo cur = results[j]; + if (cur != null && cur.getSequence() == item.mTextInfo.getSequence()) { + result = cur; + result.setCookieAndSequence(originalCookie, originalSequence); + break; + } + } + offsets[i] = item.mStart; + lengths[i] = item.mLength; + reconstructedSuggestions[i] = result != null ? result : EMPTY_SUGGESTIONS_INFO; + } + return new SentenceSuggestionsInfo(reconstructedSuggestions, offsets, lengths); + } +} diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java index 75075664f..a6437bac3 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedContactsBinaryDictionary.java @@ -47,9 +47,9 @@ public final class SynchronouslyLoadedContactsBinaryDictionary extends ContactsB } @Override - public boolean isValidWord(final String word) { + public boolean isInDictionary(final String word) { synchronized (mLock) { - return super.isValidWord(word); + return super.isInDictionary(word); } } } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java index f2d981a9d..8c9d5d681 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/SynchronouslyLoadedUserBinaryDictionary.java @@ -52,9 +52,9 @@ public final class SynchronouslyLoadedUserBinaryDictionary extends UserBinaryDic } @Override - public boolean isValidWord(final String word) { + public boolean isInDictionary(final String word) { synchronized (mLock) { - return super.isValidWord(word); + return super.isInDictionary(word); } } } diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java index e90b15ca5..346aea34a 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestions.java @@ -23,23 +23,17 @@ import android.graphics.drawable.Drawable; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.internal.KeyboardBuilder; import com.android.inputmethod.keyboard.internal.KeyboardIconsSet; import com.android.inputmethod.keyboard.internal.KeyboardParams; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.utils.TypefaceUtils; public final class MoreSuggestions extends Keyboard { public final SuggestedWords mSuggestedWords; - public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter { - public abstract void onSuggestionSelected(final int index, final SuggestedWordInfo info); - } - MoreSuggestions(final MoreSuggestionsParam params, final SuggestedWords suggestedWords) { super(params); mSuggestedWords = suggestedWords; diff --git a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java index 7fd64c4bf..528d500d2 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java @@ -20,13 +20,16 @@ import android.content.Context; import android.util.AttributeSet; import android.util.Log; +import com.android.inputmethod.accessibility.AccessibilityUtils; +import com.android.inputmethod.accessibility.MoreSuggestionsAccessibilityDelegate; import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardActionListener; import com.android.inputmethod.keyboard.MoreKeysKeyboardView; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; +import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionKey; -import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener; /** * A view that renders a virtual {@link MoreSuggestions}. It handles rendering of keys and detecting @@ -35,6 +38,10 @@ import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestions public final class MoreSuggestionsView extends MoreKeysKeyboardView { private static final String TAG = MoreSuggestionsView.class.getSimpleName(); + public static abstract class MoreSuggestionsListener extends KeyboardActionListener.Adapter { + public abstract void onSuggestionSelected(final int index, final SuggestedWordInfo info); + } + public MoreSuggestionsView(final Context context, final AttributeSet attrs) { this(context, attrs, R.attr.moreKeysKeyboardViewStyle); } @@ -45,6 +52,26 @@ public final class MoreSuggestionsView extends MoreKeysKeyboardView { } @Override + public void setKeyboard(final Keyboard keyboard) { + super.setKeyboard(keyboard); + // With accessibility mode off, {@link #mAccessibilityDelegate} is set to null at the + // above {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call. + // With accessibility mode on, {@link #mAccessibilityDelegate} is set to a + // {@link MoreKeysKeyboardAccessibilityDelegate} object at the above + // {@link MoreKeysKeyboardView#setKeyboard(Keyboard)} call. And the object has to be + // overwritten by a {@link MoreSuggestionsAccessibilityDelegate} object here. + if (AccessibilityUtils.getInstance().isAccessibilityEnabled()) { + if (!(mAccessibilityDelegate instanceof MoreSuggestionsAccessibilityDelegate)) { + mAccessibilityDelegate = new MoreSuggestionsAccessibilityDelegate( + this, mKeyDetector); + mAccessibilityDelegate.setOpenAnnounce(R.string.spoken_open_more_suggestions); + mAccessibilityDelegate.setCloseAnnounce(R.string.spoken_close_more_suggestions); + } + mAccessibilityDelegate.setKeyboard(keyboard); + } + } + + @Override protected int getDefaultCoordX() { final MoreSuggestions pane = (MoreSuggestions)getKeyboard(); return pane.mOccupiedWidth / 2; diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java index 810bda758..19b48f081 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripLayoutHelper.java @@ -379,10 +379,9 @@ final class SuggestionStripLayoutHelper { } else { wordView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); } - - // Disable this suggestion if the suggestion is null or empty. - // TODO: Fix disabled {@link TextView}'s content description. - wordView.setEnabled(!TextUtils.isEmpty(word)); + // {@link StyleSpan} in a content description may cause an issue of TTS/TalkBack. + // Use a simple {@link String} to avoid the issue. + wordView.setContentDescription(TextUtils.isEmpty(word) ? null : word.toString()); final CharSequence text = getEllipsizedText(word, width, wordView.getPaint()); final float scaleX = getTextScaleX(word, width, wordView.getPaint()); wordView.setText(text); // TextView.setText() resets text scale x to 1.0. @@ -461,14 +460,15 @@ final class SuggestionStripLayoutHelper { } final TextView wordView = mWordViews.get(positionInStrip); - wordView.setEnabled(true); - wordView.setTextColor(mColorAutoCorrect); + final String punctuation = punctuationSuggestions.getLabel(positionInStrip); // {@link TextView#getTag()} is used to get the index in suggestedWords at // {@link SuggestionStripView#onClick(View)}. wordView.setTag(positionInStrip); - wordView.setText(punctuationSuggestions.getLabel(positionInStrip)); + wordView.setText(punctuation); + wordView.setContentDescription(punctuation); wordView.setTextScaleX(1.0f); wordView.setCompoundDrawables(null, null, null, null); + wordView.setTextColor(mColorAutoCorrect); stripView.addView(wordView); setLayoutWeight(wordView, 1.0f, mSuggestionsStripHeight); } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index 619804afa..3be933ff7 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -42,17 +42,14 @@ import com.android.inputmethod.keyboard.MainKeyboardView; import com.android.inputmethod.keyboard.MoreKeysPanel; import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SuggestedWords; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.settings.Settings; -import com.android.inputmethod.latin.suggestions.MoreSuggestions.MoreSuggestionsListener; -import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.settings.SettingsValues; +import com.android.inputmethod.latin.suggestions.MoreSuggestionsView.MoreSuggestionsListener; import com.android.inputmethod.latin.utils.ImportantNoticeUtils; -import com.android.inputmethod.research.ResearchLogger; import java.util.ArrayList; @@ -78,9 +75,9 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private final MoreSuggestionsView mMoreSuggestionsView; private final MoreSuggestions.Builder mMoreSuggestionsBuilder; - private final ArrayList<TextView> mWordViews = CollectionUtils.newArrayList(); - private final ArrayList<TextView> mDebugInfoViews = CollectionUtils.newArrayList(); - private final ArrayList<View> mDividerViews = CollectionUtils.newArrayList(); + private final ArrayList<TextView> mWordViews = new ArrayList<>(); + private final ArrayList<TextView> mDebugInfoViews = new ArrayList<>(); + private final ArrayList<View> mDividerViews = new ArrayList<>(); Listener mListener; private SuggestedWords mSuggestedWords = SuggestedWords.EMPTY; @@ -90,43 +87,44 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick private final StripVisibilityGroup mStripVisibilityGroup; private static class StripVisibilityGroup { + private final View mSuggestionStripView; private final View mSuggestionsStrip; - private final View mVoiceKey; private final View mAddToDictionaryStrip; private final View mImportantNoticeStrip; - public StripVisibilityGroup(final View suggestionsStrip, final View voiceKey, - final View addToDictionaryStrip, final View importantNoticeStrip) { + public StripVisibilityGroup(final View suggestionStripView, + final ViewGroup suggestionsStrip, final ViewGroup addToDictionaryStrip, + final View importantNoticeStrip) { + mSuggestionStripView = suggestionStripView; mSuggestionsStrip = suggestionsStrip; - mVoiceKey = voiceKey; mAddToDictionaryStrip = addToDictionaryStrip; mImportantNoticeStrip = importantNoticeStrip; - showSuggestionsStrip(false /* voiceKeyEnabled */); + showSuggestionsStrip(); } - public void setLayoutDirection(final int layoutDirection) { + public void setLayoutDirection(final boolean isRtlLanguage) { + final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL + : ViewCompat.LAYOUT_DIRECTION_LTR; + ViewCompat.setLayoutDirection(mSuggestionStripView, layoutDirection); ViewCompat.setLayoutDirection(mSuggestionsStrip, layoutDirection); ViewCompat.setLayoutDirection(mAddToDictionaryStrip, layoutDirection); ViewCompat.setLayoutDirection(mImportantNoticeStrip, layoutDirection); } - public void showSuggestionsStrip(final boolean enableVoiceKey) { + public void showSuggestionsStrip() { mSuggestionsStrip.setVisibility(VISIBLE); - mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE); mAddToDictionaryStrip.setVisibility(INVISIBLE); mImportantNoticeStrip.setVisibility(INVISIBLE); } public void showAddToDictionaryStrip() { mSuggestionsStrip.setVisibility(INVISIBLE); - mVoiceKey.setVisibility(INVISIBLE); mAddToDictionaryStrip.setVisibility(VISIBLE); mImportantNoticeStrip.setVisibility(INVISIBLE); } - public void showImportantNoticeStrip(final boolean enableVoiceKey) { + public void showImportantNoticeStrip() { mSuggestionsStrip.setVisibility(INVISIBLE); - mVoiceKey.setVisibility(enableVoiceKey ? VISIBLE : INVISIBLE); mAddToDictionaryStrip.setVisibility(INVISIBLE); mImportantNoticeStrip.setVisibility(VISIBLE); } @@ -156,7 +154,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick mVoiceKey = (ImageButton)findViewById(R.id.suggestions_strip_voice_key); mAddToDictionaryStrip = (ViewGroup)findViewById(R.id.add_to_dictionary_strip); mImportantNoticeStrip = findViewById(R.id.important_notice_strip); - mStripVisibilityGroup = new StripVisibilityGroup(mSuggestionsStrip, mVoiceKey, + mStripVisibilityGroup = new StripVisibilityGroup(this, mSuggestionsStrip, mAddToDictionaryStrip, mImportantNoticeStrip); for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) { @@ -165,7 +163,6 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick word.setOnLongClickListener(this); mWordViews.add(word); final View divider = inflater.inflate(R.layout.suggestion_divider, null); - divider.setOnClickListener(this); mDividerViews.add(divider); final TextView info = new TextView(context, null, R.attr.suggestionWordStyle); info.setTextColor(Color.WHITE); @@ -204,30 +201,20 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick mMainKeyboardView = (MainKeyboardView)inputView.findViewById(R.id.keyboard_view); } - private boolean isVoiceKeyEnabled() { - if (mMainKeyboardView == null) { - return false; - } - final Keyboard keyboard = mMainKeyboardView.getKeyboard(); - if (keyboard == null) { - return false; - } - return keyboard.mId.mHasShortcutKey; + public void updateVisibility(final boolean shouldBeVisible, final boolean isFullscreenMode) { + final int visibility = shouldBeVisible ? VISIBLE : (isFullscreenMode ? GONE : INVISIBLE); + setVisibility(visibility); + final SettingsValues currentSettingsValues = Settings.getInstance().getCurrent(); + mVoiceKey.setVisibility(currentSettingsValues.mShowsVoiceInputKey ? VISIBLE : INVISIBLE); } public void setSuggestions(final SuggestedWords suggestedWords, final boolean isRtlLanguage) { clear(); - final int layoutDirection = isRtlLanguage ? ViewCompat.LAYOUT_DIRECTION_RTL - : ViewCompat.LAYOUT_DIRECTION_LTR; - setLayoutDirection(layoutDirection); - mStripVisibilityGroup.setLayoutDirection(layoutDirection); + mStripVisibilityGroup.setLayoutDirection(isRtlLanguage); mSuggestedWords = suggestedWords; mSuggestionsCountInStrip = mLayoutHelper.layoutAndReturnSuggestionCountInStrip( mSuggestedWords, mSuggestionsStrip, this); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.suggestionStripView_setSuggestions(mSuggestedWords); - } - mStripVisibilityGroup.showSuggestionsStrip(isVoiceKeyEnabled()); + mStripVisibilityGroup.showSuggestionsStrip(); } public int setMoreSuggestionsHeight(final int remainingHeight) { @@ -258,8 +245,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick // This method checks if we should show the important notice (checks on permanent storage if // it has been shown once already or not, and if in the setup wizard). If applicable, it shows // the notice. In all cases, it returns true if it was shown, false otherwise. - public boolean maybeShowImportantNoticeTitle(final InputAttributes inputAttributes) { - if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext(), inputAttributes)) { + public boolean maybeShowImportantNoticeTitle() { + if (!ImportantNoticeUtils.shouldShowImportantNotice(getContext())) { return false; } if (getWidth() <= 0) { @@ -274,7 +261,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick dismissMoreSuggestionsPanel(); } mLayoutHelper.layoutImportantNotice(mImportantNoticeStrip, importantNoticeTitle); - mStripVisibilityGroup.showImportantNoticeStrip(isVoiceKeyEnabled()); + mStripVisibilityGroup.showImportantNoticeStrip(); mImportantNoticeStrip.setOnClickListener(this); return true; } @@ -282,7 +269,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick public void clear() { mSuggestionsStrip.removeAllViews(); removeAllDebugInfoViews(); - mStripVisibilityGroup.showSuggestionsStrip(false /* enableVoiceKey */); + mStripVisibilityGroup.showSuggestionsStrip(); dismissMoreSuggestionsPanel(); } @@ -441,6 +428,8 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick @Override public void onClick(final View view) { + AudioAndHapticFeedbackManager.getInstance().performHapticAndAudioFeedback( + Constants.CODE_UNSPECIFIED, this); if (view == mImportantNoticeStrip) { mListener.showImportantNoticeContents(); return; @@ -484,7 +473,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick // Called by the framework when the size is known. Show the important notice if applicable. // This may be overriden by showing suggestions later, if applicable. if (oldw <= 0 && w > 0) { - maybeShowImportantNoticeTitle(Settings.getInstance().getCurrent().mInputAttributes); + maybeShowImportantNoticeTitle(); } } } diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java index 21426d1eb..eda81940f 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java @@ -167,7 +167,9 @@ public class UserDictionaryAddWordContents { // should not insert, because either A. the word exists with no shortcut, in which // case the exact same thing we want to insert is already there, or B. the word // exists with at least one shortcut, in which case it has priority on our word. - if (hasWord(newWord, context)) return CODE_ALREADY_PRESENT; + if (TextUtils.isEmpty(newShortcut) && hasWord(newWord, context)) { + return CODE_ALREADY_PRESENT; + } // Disallow duplicates. If the same word with no shortcut is defined, remove it; if // the same word with the same shortcut is defined, remove it; but we don't mind if @@ -256,7 +258,7 @@ public class UserDictionaryAddWordContents { // The system locale should be inside. We want it at the 2nd spot. locales.remove(systemLocale); // system locale may not be null locales.remove(""); // Remove the empty string if it's there - final ArrayList<LocaleRenderer> localesList = new ArrayList<LocaleRenderer>(); + final ArrayList<LocaleRenderer> localesList = new ArrayList<>(); // Add the passed locale, then the system locale at the top of the list. Add an // "all languages" entry at the bottom of the list. addLocaleDisplayNameToList(activity, localesList, mLocale); diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java index 4fc132f68..163443036 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordFragment.java @@ -134,8 +134,8 @@ public class UserDictionaryAddWordFragment extends Fragment final Spinner localeSpinner = (Spinner)mRootView.findViewById(R.id.user_dictionary_add_locale); - final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<LocaleRenderer>(getActivity(), - android.R.layout.simple_spinner_item, localesList); + final ArrayAdapter<LocaleRenderer> adapter = new ArrayAdapter<>( + getActivity(), android.R.layout.simple_spinner_item, localesList); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); localeSpinner.setAdapter(adapter); localeSpinner.setOnItemSelectedListener(this); diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java index 97a924d7b..624783a70 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryList.java @@ -56,7 +56,7 @@ public class UserDictionaryList extends PreferenceFragment { final Cursor cursor = activity.getContentResolver().query(UserDictionary.Words.CONTENT_URI, new String[] { UserDictionary.Words.LOCALE }, null, null, null); - final TreeSet<String> localeSet = new TreeSet<String>(); + final TreeSet<String> localeSet = new TreeSet<>(); if (null == cursor) { // The user dictionary service is not present or disabled. Return null. return null; diff --git a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java index 2bb30a2ba..3ca7c7e1c 100644 --- a/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/AdditionalSubtypeUtils.java @@ -95,8 +95,7 @@ public final class AdditionalSubtypeUtils { return EMPTY_SUBTYPE_ARRAY; } final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR); - final ArrayList<InputMethodSubtype> subtypesList = - CollectionUtils.newArrayList(prefSubtypeArray.length); + final ArrayList<InputMethodSubtype> subtypesList = new ArrayList<>(prefSubtypeArray.length); for (final String prefSubtype : prefSubtypeArray) { final String elems[] = prefSubtype.split(LOCALE_AND_LAYOUT_SEPARATOR); if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE diff --git a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java index 22b9b77d2..34ee2152a 100644 --- a/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/AutoCorrectionUtils.java @@ -16,12 +16,11 @@ package com.android.inputmethod.latin.utils; -import com.android.inputmethod.latin.BinaryDictionary; +import android.util.Log; + import com.android.inputmethod.latin.LatinImeLogger; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import android.util.Log; - public final class AutoCorrectionUtils { private static final boolean DBG = LatinImeLogger.sDBG; private static final String TAG = AutoCorrectionUtils.class.getSimpleName(); @@ -36,7 +35,9 @@ public final class AutoCorrectionUtils { final float autoCorrectionThreshold) { if (null != suggestion) { // Shortlist a whitelisted word - if (suggestion.mKind == SuggestedWordInfo.KIND_WHITELIST) return true; + if (suggestion.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) { + return true; + } final int autoCorrectionSuggestionScore = suggestion.mScore; // TODO: when the normalized score of the first suggestion is nearly equals to // the normalized score of the second suggestion, behave less aggressive. diff --git a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java index 702688f93..936219332 100644 --- a/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CapsModeUtils.java @@ -62,6 +62,22 @@ public final class CapsModeUtils { } /** + * Helper method to find out if a code point is starting punctuation. + * + * This include the Unicode START_PUNCTUATION category, but also some other symbols that are + * starting, like the inverted question mark or the double quote. + * + * @param codePoint the code point + * @return true if it's starting punctuation, false otherwise. + */ + private static boolean isStartPunctuation(final int codePoint) { + return (codePoint == Constants.CODE_DOUBLE_QUOTE || codePoint == Constants.CODE_SINGLE_QUOTE + || codePoint == Constants.CODE_INVERTED_QUESTION_MARK + || codePoint == Constants.CODE_INVERTED_EXCLAMATION_MARK + || Character.getType(codePoint) == Character.START_PUNCTUATION); + } + + /** * Determine what caps mode should be in effect at the current offset in * the text. Only the mode bits set in <var>reqModes</var> will be * checked. Note that the caps mode flags here are explicitly defined @@ -115,8 +131,7 @@ public final class CapsModeUtils { } else { for (i = cs.length(); i > 0; i--) { final char c = cs.charAt(i - 1); - if (c != Constants.CODE_DOUBLE_QUOTE && c != Constants.CODE_SINGLE_QUOTE - && Character.getType(c) != Character.START_PUNCTUATION) { + if (!isStartPunctuation(c)) { break; } } @@ -210,11 +225,14 @@ public final class CapsModeUtils { // We found out that we have a period. We need to determine if this is a full stop or // otherwise sentence-ending period, or an abbreviation like "e.g.". An abbreviation - // looks like (\w\.){2,} + // looks like (\w\.){2,}. Moreover, in German, you put periods after digits for dates + // and some other things, and in German specifically we need to not go into autocaps after + // a whitespace-digits-period sequence. // To find out, we will have a simple state machine with the following states : - // START, WORD, PERIOD, ABBREVIATION + // START, WORD, PERIOD, ABBREVIATION, NUMBER // On START : (just before the first period) // letter => WORD + // digit => NUMBER if German; end with caps otherwise // whitespace => end with no caps (it was a stand-alone period) // otherwise => end with caps (several periods/symbols in a row) // On WORD : (within the word just before the first period) @@ -228,6 +246,11 @@ public final class CapsModeUtils { // letter => LETTER // period => PERIOD // otherwise => end with no caps (it was an abbreviation) + // On NUMBER : (period immediately preceded by one or more digits) + // digit => NUMBER + // letter => LETTER (promote to word) + // otherwise => end with no caps (it was a whitespace-digits-period sequence, + // or a punctuation-digits-period sequence like "11.11.") // "Not an abbreviation" in the above chart essentially covers cases like "...yes.". This // should capitalize. @@ -235,6 +258,7 @@ public final class CapsModeUtils { final int WORD = 1; final int PERIOD = 2; final int LETTER = 3; + final int NUMBER = 4; final int caps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES) & reqModes; final int noCaps = (TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS) & reqModes; @@ -247,6 +271,8 @@ public final class CapsModeUtils { state = WORD; } else if (Character.isWhitespace(c)) { return noCaps; + } else if (Character.isDigit(c) && spacingAndPunctuations.mUsesGermanRules) { + state = NUMBER; } else { return caps; } @@ -275,6 +301,15 @@ public final class CapsModeUtils { } else { return noCaps; } + break; + case NUMBER: + if (Character.isLetter(c)) { + state = WORD; + } else if (Character.isDigit(c)) { + state = NUMBER; + } else { + return noCaps; + } } } // Here we arrived at the start of the line. This should behave exactly like whitespace. diff --git a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java index bbfa0f091..e3aef29ba 100644 --- a/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CollectionUtils.java @@ -16,93 +16,21 @@ package com.android.inputmethod.latin.utils; -import android.util.SparseArray; - -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; import java.util.Map; import java.util.TreeMap; -import java.util.TreeSet; -import java.util.WeakHashMap; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; public final class CollectionUtils { private CollectionUtils() { // This utility class is not publicly instantiable. } - public static <K,V> HashMap<K,V> newHashMap() { - return new HashMap<K,V>(); - } - - public static <K, V> WeakHashMap<K, V> newWeakHashMap() { - return new WeakHashMap<K, V>(); - } - - public static <K,V> TreeMap<K,V> newTreeMap() { - return new TreeMap<K,V>(); - } - public static <K, V> Map<K,V> newSynchronizedTreeMap() { - final TreeMap<K,V> treeMap = newTreeMap(); + final TreeMap<K,V> treeMap = new TreeMap<>(); return Collections.synchronizedMap(treeMap); } - public static <K,V> ConcurrentHashMap<K,V> newConcurrentHashMap() { - return new ConcurrentHashMap<K,V>(); - } - - public static <E> HashSet<E> newHashSet() { - return new HashSet<E>(); - } - - public static <E> TreeSet<E> newTreeSet() { - return new TreeSet<E>(); - } - - public static <E> ArrayList<E> newArrayList() { - return new ArrayList<E>(); - } - - public static <E> ArrayList<E> newArrayList(final int initialCapacity) { - return new ArrayList<E>(initialCapacity); - } - - public static <E> ArrayList<E> newArrayList(final Collection<E> collection) { - return new ArrayList<E>(collection); - } - - public static <E> LinkedList<E> newLinkedList() { - return new LinkedList<E>(); - } - - public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList() { - return new CopyOnWriteArrayList<E>(); - } - - public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList( - final Collection<E> collection) { - return new CopyOnWriteArrayList<E>(collection); - } - - public static <E> CopyOnWriteArrayList<E> newCopyOnWriteArrayList(final E[] array) { - return new CopyOnWriteArrayList<E>(array); - } - - public static <E> ArrayDeque<E> newArrayDeque() { - return new ArrayDeque<E>(); - } - - public static <E> SparseArray<E> newSparseArray() { - return new SparseArray<E>(); - } - public static <E> ArrayList<E> arrayAsList(final E[] array, final int start, final int end) { if (array == null) { throw new NullPointerException(); @@ -111,7 +39,7 @@ public final class CollectionUtils { throw new IllegalArgumentException(); } - final ArrayList<E> list = newArrayList(end - start); + final ArrayList<E> list = new ArrayList<>(end - start); for (int i = start; i < end; i++) { list.add(array[i]); } diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java index b18a1d83b..a21a1373b 100644 --- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java @@ -209,7 +209,7 @@ public final class CsvUtils { @UsedForTesting public static String[] split(final int splitFlags, final String line) throws CsvParseException { final boolean trimSpaces = (splitFlags & SPLIT_FLAGS_TRIM_SPACES) != 0; - final ArrayList<String> fields = CollectionUtils.newArrayList(); + final ArrayList<String> fields = new ArrayList<>(); final int length = line.length(); int start = 0; do { diff --git a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java index 315913e2f..d76ea10c0 100644 --- a/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/DictionaryInfoUtils.java @@ -336,7 +336,7 @@ public class DictionaryInfoUtils { public static ArrayList<DictionaryInfo> getCurrentDictionaryFileNameAndVersionInfo( final Context context) { - final ArrayList<DictionaryInfo> dictList = CollectionUtils.newArrayList(); + final ArrayList<DictionaryInfo> dictList = new ArrayList<>(); // Retrieve downloaded dictionaries final File[] directoryList = getCachedDirectoryList(context); diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java index f1057da0b..787e4a59d 100644 --- a/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilter.java @@ -16,129 +16,14 @@ package com.android.inputmethod.latin.utils; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import android.content.Context; -import android.content.res.Resources; -import android.text.InputType; -import android.util.Log; -import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.KeyboardLayoutSet; -import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.PrevWordsInfo; -import com.android.inputmethod.latin.Suggest; -import com.android.inputmethod.latin.Suggest.OnGetSuggestedWordsCallback; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.WordComposer; - -/** - * This class is used to prevent distracters being added to personalization - * or user history dictionaries - */ -public class DistracterFilter { - private static final String TAG = DistracterFilter.class.getSimpleName(); - - private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120; - - private final Context mContext; - private final Map<Locale, InputMethodSubtype> mLocaleToSubtypeMap; - private final Map<Locale, Keyboard> mLocaleToKeyboardMap; - private final Suggest mSuggest; - private Keyboard mKeyboard; - - // If the score of the top suggestion exceeds this value, the tested word (e.g., - // an OOV, a misspelling, or an in-vocabulary word) would be considered as a distracter to - // words in dictionary. The greater the threshold is, the less likely the tested word would - // become a distracter, which means the tested word will be more likely to be added to - // the dictionary. - private static final float DISTRACTER_WORD_SCORE_THRESHOLD = 2.0f; - - // Create empty distracter filter. - public DistracterFilter() { - this(null, new ArrayList<InputMethodSubtype>()); - } - - /** - * Create a DistracterFilter instance. - * - * @param context the context. - * @param enabledSubtypes the enabled subtypes. - */ - public DistracterFilter(final Context context, final List<InputMethodSubtype> enabledSubtypes) { - mContext = context; - mLocaleToSubtypeMap = new HashMap<>(); - if (enabledSubtypes != null) { - for (final InputMethodSubtype subtype : enabledSubtypes) { - final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype); - if (mLocaleToSubtypeMap.containsKey(locale)) { - // Multiple subtypes are enabled for one locale. - // TODO: Investigate what we should do for this case. - continue; - } - mLocaleToSubtypeMap.put(locale, subtype); - } - } - mLocaleToKeyboardMap = new HashMap<>(); - mSuggest = new Suggest(); - mKeyboard = null; - } - - private static boolean suggestionExceedsDistracterThreshold( - final SuggestedWordInfo suggestion, final String consideredWord, - final float distracterThreshold) { - if (null != suggestion) { - final int suggestionScore = suggestion.mScore; - final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore( - consideredWord, suggestion.mWord, suggestionScore); - if (normalizedScore > distracterThreshold) { - return true; - } - } - return false; - } - - private void loadKeyboardForLocale(final Locale newLocale) { - final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale); - if (cachedKeyboard != null) { - mKeyboard = cachedKeyboard; - return; - } - final InputMethodSubtype subtype = mLocaleToSubtypeMap.get(newLocale); - if (subtype == null) { - return; - } - final EditorInfo editorInfo = new EditorInfo(); - editorInfo.inputType = InputType.TYPE_CLASS_TEXT; - final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( - mContext, editorInfo); - final Resources res = mContext.getResources(); - final int keyboardWidth = ResourceUtils.getDefaultKeyboardWidth(res); - final int keyboardHeight = ResourceUtils.getDefaultKeyboardHeight(res); - builder.setKeyboardGeometry(keyboardWidth, keyboardHeight); - builder.setSubtype(subtype); - builder.setIsSpellChecker(false /* isSpellChecker */); - final KeyboardLayoutSet layoutSet = builder.build(); - mKeyboard = layoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); - } - - private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException { - mSuggest.mDictionaryFacilitator.resetDictionaries(mContext, newlocale, - false /* useContactsDict */, false /* usePersonalizedDicts */, - false /* forceReloadMainDictionary */, null /* listener */); - mSuggest.mDictionaryFacilitator.waitForLoadingMainDictionary( - TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS); - } +public interface DistracterFilter { /** * Determine whether a word is a distracter to words in dictionaries. * @@ -149,56 +34,25 @@ public class DistracterFilter { * @return true if testedWord is a distracter, otherwise false. */ public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo, - final String testedWord, final Locale locale) { - if (locale == null) { - return false; - } - if (!locale.equals(mSuggest.mDictionaryFacilitator.getLocale())) { - if (!mLocaleToSubtypeMap.containsKey(locale)) { - Log.e(TAG, "Locale " + locale + " is not enabled."); - // TODO: Investigate what we should do for disabled locales. - return false; - } - loadKeyboardForLocale(locale); - // Reset dictionaries for the locale. - try { - loadDictionariesForLocale(locale); - } catch (final InterruptedException e) { - Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter", e); - return false; - } - } - if (mKeyboard == null) { + final String testedWord, final Locale locale); + + public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes); + + public void close(); + + public static final DistracterFilter EMPTY_DISTRACTER_FILTER = new DistracterFilter() { + @Override + public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo, + String testedWord, Locale locale) { return false; } - final WordComposer composer = new WordComposer(); - final int[] codePoints = StringUtils.toCodePointArray(testedWord); - final int[] coordinates = mKeyboard.getCoordinates(codePoints); - composer.setComposingWord(codePoints, coordinates, prevWordsInfo); - final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord); - final String consideredWord = trailingSingleQuotesCount > 0 ? - testedWord.substring(0, testedWord.length() - trailingSingleQuotesCount) : - testedWord; - final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<Boolean>(); - final OnGetSuggestedWordsCallback callback = new OnGetSuggestedWordsCallback() { - @Override - public void onGetSuggestedWords(final SuggestedWords suggestedWords) { - if (suggestedWords != null && suggestedWords.size() > 1) { - // The suggestedWordInfo at 0 is the typed word. The 1st suggestion from - // the decoder is at index 1. - final SuggestedWordInfo firstSuggestion = suggestedWords.getInfo(1); - final boolean hasStrongDistractor = suggestionExceedsDistracterThreshold( - firstSuggestion, consideredWord, DISTRACTER_WORD_SCORE_THRESHOLD); - holder.set(hasStrongDistractor); - } - } - }; - mSuggest.getSuggestedWords(composer, prevWordsInfo, mKeyboard.getProximityInfo(), - true /* blockOffensiveWords */, true /* isCorrectionEnbaled */, - null /* additionalFeaturesOptions */, 0 /* sessionId */, - SuggestedWords.NOT_A_SEQUENCE_NUMBER, callback); + @Override + public void close() { + } - return holder.get(false /* defaultValue */, Constants.GET_SUGGESTED_WORDS_TIMEOUT); - } + @Override + public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) { + } + }; } diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java new file mode 100644 index 000000000..0ee6236b1 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingExactMatches.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2014 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.utils; + +import java.util.List; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +import android.content.Context; +import android.util.Log; +import android.util.LruCache; +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.latin.DictionaryFacilitator; +import com.android.inputmethod.latin.PrevWordsInfo; + +/** + * This class is used to prevent distracters being added to personalization + * or user history dictionaries + */ +public class DistracterFilterCheckingExactMatches implements DistracterFilter { + private static final String TAG = DistracterFilterCheckingExactMatches.class.getSimpleName(); + private static final boolean DEBUG = false; + + private static final long TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS = 120; + private static final int MAX_DISTRACTERS_CACHE_SIZE = 512; + + private final Context mContext; + private final DictionaryFacilitator mDictionaryFacilitator; + private final LruCache<String, Boolean> mDistractersCache; + private final Object mLock = new Object(); + + /** + * Create a DistracterFilter instance. + * + * @param context the context. + */ + public DistracterFilterCheckingExactMatches(final Context context) { + mContext = context; + mDictionaryFacilitator = new DictionaryFacilitator(); + mDistractersCache = new LruCache<>(MAX_DISTRACTERS_CACHE_SIZE); + } + + @Override + public void close() { + mDictionaryFacilitator.closeDictionaries(); + } + + @Override + public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) { + } + + private void loadDictionariesForLocale(final Locale newlocale) throws InterruptedException { + mDictionaryFacilitator.resetDictionaries(mContext, newlocale, + false /* useContactsDict */, false /* usePersonalizedDicts */, + false /* forceReloadMainDictionary */, null /* listener */); + mDictionaryFacilitator.waitForLoadingMainDictionary( + TIMEOUT_TO_WAIT_LOADING_DICTIONARIES_IN_SECONDS, TimeUnit.SECONDS); + } + + /** + * Determine whether a word is a distracter to words in dictionaries. + * + * @param prevWordsInfo the information of previous words. Not used for now. + * @param testedWord the word that will be tested to see whether it is a distracter to words + * in dictionaries. + * @param locale the locale of word. + * @return true if testedWord is a distracter, otherwise false. + */ + @Override + public boolean isDistracterToWordsInDictionaries(final PrevWordsInfo prevWordsInfo, + final String testedWord, final Locale locale) { + if (locale == null) { + return false; + } + if (!locale.equals(mDictionaryFacilitator.getLocale())) { + synchronized (mLock) { + // Reset dictionaries for the locale. + try { + mDistractersCache.evictAll(); + loadDictionariesForLocale(locale); + } catch (final InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for loading dicts in DistracterFilter", + e); + return false; + } + } + } + + final Boolean isCachedDistracter = mDistractersCache.get(testedWord); + if (isCachedDistracter != null && isCachedDistracter) { + if (DEBUG) { + Log.d(TAG, "testedWord: " + testedWord); + Log.d(TAG, "isDistracter: true (cache hit)"); + } + return true; + } + // The tested word is a distracter when there is a word that is exact matched to the tested + // word and its probability is higher than the tested word's probability. + final int perfectMatchFreq = mDictionaryFacilitator.getFrequency(testedWord); + final int exactMatchFreq = mDictionaryFacilitator.getMaxFrequencyOfExactMatches(testedWord); + final boolean isDistracter = perfectMatchFreq < exactMatchFreq; + if (DEBUG) { + Log.d(TAG, "testedWord: " + testedWord); + Log.d(TAG, "perfectMatchFreq: " + perfectMatchFreq); + Log.d(TAG, "exactMatchFreq: " + exactMatchFreq); + Log.d(TAG, "isDistracter: " + isDistracter); + } + if (isDistracter) { + // Add the word to the cache. + mDistractersCache.put(testedWord, Boolean.TRUE); + } + return isDistracter; + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java new file mode 100644 index 000000000..4ad4ba784 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterCheckingIsInDictionary.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 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.utils; + +import java.util.List; +import java.util.Locale; + +import android.view.inputmethod.InputMethodSubtype; + +import com.android.inputmethod.latin.Dictionary; +import com.android.inputmethod.latin.PrevWordsInfo; + +public class DistracterFilterCheckingIsInDictionary implements DistracterFilter { + private final DistracterFilter mDistracterFilter; + private final Dictionary mDictionary; + + public DistracterFilterCheckingIsInDictionary(final DistracterFilter distracterFilter, + final Dictionary dictionary) { + mDistracterFilter = distracterFilter; + mDictionary = dictionary; + } + + @Override + public boolean isDistracterToWordsInDictionaries(PrevWordsInfo prevWordsInfo, + String testedWord, Locale locale) { + if (mDictionary.isInDictionary(testedWord)) { + // This filter treats entries that are already in the dictionary as non-distracters + // because they have passed the filtering in the past. + return false; + } else { + return mDistracterFilter.isDistracterToWordsInDictionaries( + prevWordsInfo, testedWord, locale); + } + } + + @Override + public void updateEnabledSubtypes(List<InputMethodSubtype> enabledSubtypes) { + // Do nothing. + } + + @Override + public void close() { + // Do nothing. + } +} diff --git a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java index ed502ed3d..61da1b789 100644 --- a/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/ExecutorUtils.java @@ -19,22 +19,42 @@ package com.android.inputmethod.latin.utils; import com.android.inputmethod.annotations.UsedForTesting; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; /** * Utilities to manage executors. */ public class ExecutorUtils { - private static final ConcurrentHashMap<String, PrioritizedSerialExecutor> - sExecutorMap = CollectionUtils.newConcurrentHashMap(); + private static final ConcurrentHashMap<String, ExecutorService> sExecutorMap = + new ConcurrentHashMap<>(); + + private static class ThreadFactoryWithId implements ThreadFactory { + private final String mId; + + public ThreadFactoryWithId(final String id) { + mId = id; + } + + @Override + public Thread newThread(final Runnable r) { + return new Thread(r, "Executor - " + mId); + } + } + /** - * Gets the executor for the given dictionary name. + * Gets the executor for the given id. */ - public static PrioritizedSerialExecutor getExecutor(final String dictName) { - PrioritizedSerialExecutor executor = sExecutorMap.get(dictName); + public static ExecutorService getExecutor(final String id) { + ExecutorService executor = sExecutorMap.get(id); if (executor == null) { synchronized(sExecutorMap) { - executor = new PrioritizedSerialExecutor(); - sExecutorMap.put(dictName, executor); + executor = sExecutorMap.get(id); + if (executor == null) { + executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithId(id)); + sExecutorMap.put(id, executor); + } } } return executor; @@ -46,7 +66,7 @@ public class ExecutorUtils { @UsedForTesting public static void shutdownAllExecutors() { synchronized(sExecutorMap) { - for (final PrioritizedSerialExecutor executor : sExecutorMap.values()) { + for (final ExecutorService executor : sExecutorMap.values()) { executor.execute(new Runnable() { @Override public void run() { diff --git a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java index ee2b97b2a..e300bd1d3 100644 --- a/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/FragmentUtils.java @@ -26,12 +26,11 @@ import com.android.inputmethod.latin.userdictionary.UserDictionaryAddWordFragmen import com.android.inputmethod.latin.userdictionary.UserDictionaryList; import com.android.inputmethod.latin.userdictionary.UserDictionaryLocalePicker; import com.android.inputmethod.latin.userdictionary.UserDictionarySettings; -import com.android.inputmethod.research.FeedbackFragment; import java.util.HashSet; public class FragmentUtils { - private static final HashSet<String> sLatinImeFragments = new HashSet<String>(); + private static final HashSet<String> sLatinImeFragments = new HashSet<>(); static { sLatinImeFragments.add(DictionarySettingsFragment.class.getName()); sLatinImeFragments.add(AboutPreferences.class.getName()); @@ -43,7 +42,6 @@ public class FragmentUtils { sLatinImeFragments.add(UserDictionaryList.class.getName()); sLatinImeFragments.add(UserDictionaryLocalePicker.class.getName()); sLatinImeFragments.add(UserDictionarySettings.class.getName()); - sLatinImeFragments.add(FeedbackFragment.class.getName()); } public static boolean isValidFragment(String fragmentName) { diff --git a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java index 7d937a9d2..8b7077879 100644 --- a/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/ImportantNoticeUtils.java @@ -23,7 +23,6 @@ import android.provider.Settings.SettingNotFoundException; import android.text.TextUtils; import android.util.Log; -import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.R; public final class ImportantNoticeUtils { @@ -78,14 +77,7 @@ public final class ImportantNoticeUtils { return getCurrentImportantNoticeVersion(context) > lastVersion; } - public static boolean shouldShowImportantNotice(final Context context, - final InputAttributes inputAttributes) { - if (inputAttributes == null || inputAttributes.mIsPasswordField) { - return false; - } - if (isInSystemSetupWizard(context)) { - return false; - } + public static boolean shouldShowImportantNotice(final Context context) { if (!hasNewImportantNotice(context)) { return false; } @@ -93,6 +85,9 @@ public final class ImportantNoticeUtils { if (TextUtils.isEmpty(importantNoticeTitle)) { return false; } + if (isInSystemSetupWizard(context)) { + return false; + } return true; } diff --git a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java index 764ef72ce..6dd8d9711 100644 --- a/java/src/com/android/inputmethod/latin/utils/JsonUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/JsonUtils.java @@ -37,7 +37,7 @@ public final class JsonUtils { private static final String EMPTY_STRING = ""; public static List<Object> jsonStrToList(final String s) { - final ArrayList<Object> list = CollectionUtils.newArrayList(); + final ArrayList<Object> list = new ArrayList<>(); final JsonReader reader = new JsonReader(new StringReader(s)); try { reader.beginArray(); diff --git a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java index aaf4a4064..4248bebf6 100644 --- a/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java +++ b/java/src/com/android/inputmethod/latin/utils/LanguageModelParam.java @@ -19,11 +19,12 @@ package com.android.inputmethod.latin.utils; import android.util.Log; import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; +import com.android.inputmethod.latin.DictionaryFacilitator; import com.android.inputmethod.latin.PrevWordsInfo; import com.android.inputmethod.latin.settings.SpacingAndPunctuations; import java.util.ArrayList; +import java.util.List; import java.util.Locale; // Note: this class is used as a parameter type of a native method. You should be careful when you @@ -79,14 +80,13 @@ public final class LanguageModelParam { // Process a list of words and return a list of {@link LanguageModelParam} objects. public static ArrayList<LanguageModelParam> createLanguageModelParamsFrom( - final ArrayList<String> tokens, final int timestamp, - final DictionaryFacilitatorForSuggest dictionaryFacilitator, + final List<String> tokens, final int timestamp, + final DictionaryFacilitator dictionaryFacilitator, final SpacingAndPunctuations spacingAndPunctuations, final DistracterFilter distracterFilter) { - final ArrayList<LanguageModelParam> languageModelParams = - CollectionUtils.newArrayList(); + final ArrayList<LanguageModelParam> languageModelParams = new ArrayList<>(); final int N = tokens.size(); - PrevWordsInfo prevWordsInfo = new PrevWordsInfo(null); + PrevWordsInfo prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; for (int i = 0; i < N; ++i) { final String tempWord = tokens.get(i); if (StringUtils.isEmptyStringOrWhiteSpaces(tempWord)) { @@ -103,7 +103,7 @@ public final class LanguageModelParam { + tempWord + "\""); } // Sentence terminator found. Split. - prevWordsInfo = new PrevWordsInfo(null); + prevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; continue; } if (DEBUG_TOKEN) { @@ -124,43 +124,33 @@ public final class LanguageModelParam { private static LanguageModelParam detectWhetherVaildWordOrNotAndGetLanguageModelParam( final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp, - final DictionaryFacilitatorForSuggest dictionaryFacilitator, + final DictionaryFacilitator dictionaryFacilitator, final DistracterFilter distracterFilter) { final Locale locale = dictionaryFacilitator.getLocale(); if (locale == null) { return null; } - // TODO: Though targetWord is an IV (in-vocabulary) word, we should still apply - // distracterFilter in the following code. If targetWord is a distracter, - // it should be filtered out. if (dictionaryFacilitator.isValidWord(targetWord, false /* ignoreCase */)) { return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp, - true /* isValidWord */, locale); + true /* isValidWord */, locale, distracterFilter); } final String lowerCaseTargetWord = targetWord.toLowerCase(locale); if (dictionaryFacilitator.isValidWord(lowerCaseTargetWord, false /* ignoreCase */)) { // Add the lower-cased word. return createAndGetLanguageModelParamOfWord(prevWordsInfo, lowerCaseTargetWord, - timestamp, true /* isValidWord */, locale); + timestamp, true /* isValidWord */, locale, distracterFilter); } - // Treat the word as an OOV word. The following statement checks whether this OOV - // is a distracter to words in dictionaries. Being a distracter means the OOV word is - // too close to a common word in dictionaries (e.g., the OOV "mot" is very close to "not"). - // Adding such a word to dictonaries would interfere with entering in-dictionary words. For - // example, adding "mot" to dictionaries might interfere with entering "not". - // This kind of OOV should be filtered out. - if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, targetWord, locale)) { - return null; - } + // Treat the word as an OOV word. return createAndGetLanguageModelParamOfWord(prevWordsInfo, targetWord, timestamp, - false /* isValidWord */, locale); + false /* isValidWord */, locale, distracterFilter); } private static LanguageModelParam createAndGetLanguageModelParamOfWord( final PrevWordsInfo prevWordsInfo, final String targetWord, final int timestamp, - final boolean isValidWord, final Locale locale) { + final boolean isValidWord, final Locale locale, + final DistracterFilter distracterFilter) { final String word; if (StringUtils.getCapitalizationType(targetWord) == StringUtils.CAPITALIZE_FIRST && prevWordsInfo.mPrevWord == null && !isValidWord) { @@ -168,6 +158,13 @@ public final class LanguageModelParam { } else { word = targetWord; } + // Check whether the word is a distracter to words in the dictionaries. + if (distracterFilter.isDistracterToWordsInDictionaries(prevWordsInfo, word, locale)) { + if (DEBUG) { + Log.d(TAG, "The word (" + word + ") is a distracter. Skip this word."); + } + return null; + } final int unigramProbability = isValidWord ? UNIGRAM_PROBABILITY_FOR_VALID_WORD : UNIGRAM_PROBABILITY_FOR_OOV_WORD; if (prevWordsInfo.mPrevWord == null) { diff --git a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java b/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java deleted file mode 100644 index d14ba508b..000000000 --- a/java/src/com/android/inputmethod/latin/utils/LatinImeLoggerUtils.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.utils; - -import android.text.TextUtils; - -import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.WordComposer; - -public final class LatinImeLoggerUtils { - private LatinImeLoggerUtils() { - // This utility class is not publicly instantiable. - } - - public static void onNonSeparator(final char code, final int x, final int y) { - UserLogRingCharBuffer.getInstance().push(code, x, y); - LatinImeLogger.logOnInputChar(); - } - - public static void onSeparator(final int code, final int x, final int y) { - // Helper method to log a single code point separator - // TODO: cache this mapping of a code point to a string in a sparse array in StringUtils - onSeparator(StringUtils.newSingleCodePointString(code), x, y); - } - - public static void onSeparator(final String separator, final int x, final int y) { - final int length = separator.length(); - for (int i = 0; i < length; i = Character.offsetByCodePoints(separator, i, 1)) { - int codePoint = Character.codePointAt(separator, i); - // TODO: accept code points - UserLogRingCharBuffer.getInstance().push((char)codePoint, x, y); - } - LatinImeLogger.logOnInputSeparator(); - } - - public static void onAutoCorrection(final String typedWord, final String correctedWord, - final String separatorString, final WordComposer wordComposer) { - final boolean isBatchMode = wordComposer.isBatchMode(); - if (!isBatchMode && TextUtils.isEmpty(typedWord)) { - return; - } - // TODO: this fails when the separator is more than 1 code point long, but - // the backend can't handle it yet. The only case when this happens is with - // smileys and other multi-character keys. - final int codePoint = TextUtils.isEmpty(separatorString) ? Constants.NOT_A_CODE - : separatorString.codePointAt(0); - if (!isBatchMode) { - LatinImeLogger.logOnAutoCorrectionForTyping(typedWord, correctedWord, codePoint); - } else { - if (!TextUtils.isEmpty(correctedWord)) { - // We must make sure that InputPointer contains only the relative timestamps, - // not actual timestamps. - LatinImeLogger.logOnAutoCorrectionForGeometric( - "", correctedWord, codePoint, wordComposer.getInputPointers()); - } - } - } - - public static void onAutoCorrectionCancellation() { - LatinImeLogger.logOnAutoCorrectionCancelled(); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java index 8469c87b0..dd6fac671 100644 --- a/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java +++ b/java/src/com/android/inputmethod/latin/utils/LeakGuardHandlerWrapper.java @@ -33,7 +33,7 @@ public class LeakGuardHandlerWrapper<T> extends Handler { if (ownerInstance == null) { throw new NullPointerException("ownerInstance is null"); } - mOwnerInstanceRef = new WeakReference<T>(ownerInstance); + mOwnerInstanceRef = new WeakReference<>(ownerInstance); } public T getOwnerInstance() { diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java index 0c55484b4..c519a0de6 100644 --- a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java @@ -159,7 +159,7 @@ public final class LocaleUtils { return LOCALE_MATCH <= level; } - private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap(); + private static final HashMap<String, Locale> sLocaleCache = new HashMap<>(); /** * Creates a locale from a string specification. diff --git a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java b/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java deleted file mode 100644 index bf38abc95..000000000 --- a/java/src/com/android/inputmethod/latin/utils/PrioritizedSerialExecutor.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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.utils; - -import com.android.inputmethod.annotations.UsedForTesting; - -import java.util.Queue; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * An object that executes submitted tasks using a thread. - */ -public class PrioritizedSerialExecutor { - public static final String TAG = PrioritizedSerialExecutor.class.getSimpleName(); - - private final Object mLock = new Object(); - - private final Queue<Runnable> mTasks; - private final Queue<Runnable> mPrioritizedTasks; - private boolean mIsShutdown; - private final ThreadPoolExecutor mThreadPoolExecutor; - - // The task which is running now. - private Runnable mActive; - - public PrioritizedSerialExecutor() { - mTasks = new ConcurrentLinkedQueue<Runnable>(); - mPrioritizedTasks = new ConcurrentLinkedQueue<Runnable>(); - mIsShutdown = false; - mThreadPoolExecutor = new ThreadPoolExecutor(1 /* corePoolSize */, 1 /* maximumPoolSize */, - 0 /* keepAliveTime */, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1)); - } - - /** - * Enqueues the given task into the task queue. - * @param r the enqueued task - */ - public void execute(final Runnable r) { - synchronized(mLock) { - if (!mIsShutdown) { - mTasks.offer(new Runnable() { - @Override - public void run() { - try { - r.run(); - } finally { - scheduleNext(); - } - } - }); - if (mActive == null) { - scheduleNext(); - } - } - } - } - - /** - * Enqueues the given task into the prioritized task queue. - * @param r the enqueued task - */ - @UsedForTesting - public void executePrioritized(final Runnable r) { - synchronized(mLock) { - if (!mIsShutdown) { - mPrioritizedTasks.offer(new Runnable() { - @Override - public void run() { - try { - r.run(); - } finally { - scheduleNext(); - } - } - }); - if (mActive == null) { - scheduleNext(); - } - } - } - } - - private boolean fetchNextTasksLocked() { - mActive = mPrioritizedTasks.poll(); - if (mActive == null) { - mActive = mTasks.poll(); - } - return mActive != null; - } - - private void scheduleNext() { - synchronized(mLock) { - if (fetchNextTasksLocked()) { - mThreadPoolExecutor.execute(mActive); - } - } - } - - public void shutdown() { - synchronized(mLock) { - mIsShutdown = true; - mThreadPoolExecutor.shutdown(); - } - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java index 4521ec531..e3cac97f0 100644 --- a/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java +++ b/java/src/com/android/inputmethod/latin/utils/RecapitalizeStatus.java @@ -62,18 +62,22 @@ public class RecapitalizeStatus { private Locale mLocale; private int[] mSortedSeparators; private String mStringAfter; - private boolean mIsActive; + private boolean mIsStarted; + private boolean mIsEnabled = true; private static final int[] EMPTY_STORTED_SEPARATORS = {}; public RecapitalizeStatus() { // By default, initialize with dummy values that won't match any real recapitalize. - initialize(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS); - deactivate(); + start(-1, -1, "", Locale.getDefault(), EMPTY_STORTED_SEPARATORS); + stop(); } - public void initialize(final int cursorStart, final int cursorEnd, final String string, + public void start(final int cursorStart, final int cursorEnd, final String string, final Locale locale, final int[] sortedSeparators) { + if (!mIsEnabled) { + return; + } mCursorStartBefore = cursorStart; mStringBefore = string; mCursorStartAfter = cursorStart; @@ -96,15 +100,27 @@ public class RecapitalizeStatus { mRotationStyleCurrentIndex = currentMode; mSkipOriginalMixedCaseMode = true; } - mIsActive = true; + mIsStarted = true; + } + + public void stop() { + mIsStarted = false; + } + + public boolean isStarted() { + return mIsStarted; + } + + public void enable() { + mIsEnabled = true; } - public void deactivate() { - mIsActive = false; + public void disable() { + mIsEnabled = false; } - public boolean isActive() { - return mIsActive; + public boolean mIsEnabled() { + return mIsEnabled; } public boolean isSetAt(final int cursorStart, final int cursorEnd) { diff --git a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java index 49f4929b4..093c5a6c1 100644 --- a/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/ResourceUtils.java @@ -41,8 +41,7 @@ public final class ResourceUtils { // This utility class is not publicly instantiable. } - private static final HashMap<String, String> sDeviceOverrideValueMap = - CollectionUtils.newHashMap(); + private static final HashMap<String, String> sDeviceOverrideValueMap = new HashMap<>(); private static final String[] BUILD_KEYS_AND_VALUES = { "HARDWARE", Build.HARDWARE, @@ -54,8 +53,8 @@ public final class ResourceUtils { private static final String sBuildKeyValuesDebugString; static { - sBuildKeyValues = CollectionUtils.newHashMap(); - final ArrayList<String> keyValuePairs = CollectionUtils.newArrayList(); + sBuildKeyValues = new HashMap<>(); + final ArrayList<String> keyValuePairs = new ArrayList<>(); final int keyCount = BUILD_KEYS_AND_VALUES.length / 2; for (int i = 0; i < keyCount; i++) { final int index = i * 2; diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java index 73ac9a573..e4237a7f2 100644 --- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java @@ -28,7 +28,6 @@ import java.util.Arrays; import java.util.Locale; public final class StringUtils { - private static final String TAG = StringUtils.class.getSimpleName(); public static final int CAPITALIZE_NONE = 0; // No caps, or mixed case public static final int CAPITALIZE_FIRST = 1; // First only public static final int CAPITALIZE_ALL = 2; // All caps @@ -110,7 +109,7 @@ public final class StringUtils { if (!containsInArray(text, elements)) { return extraValues; } - final ArrayList<String> result = CollectionUtils.newArrayList(elements.length - 1); + final ArrayList<String> result = new ArrayList<>(elements.length - 1); for (final String element : elements) { if (!text.equals(element)) { result.add(element); @@ -316,24 +315,6 @@ public final class StringUtils { return true; } - /** - * Returns true if all code points in text are whitespace, false otherwise. Empty is true. - */ - // Interestingly enough, U+00A0 NO-BREAK SPACE and U+200B ZERO-WIDTH SPACE are not considered - // whitespace, while EN SPACE, EM SPACE and IDEOGRAPHIC SPACES are. - public static boolean containsOnlyWhitespace(final String text) { - final int length = text.length(); - int i = 0; - while (i < length) { - final int codePoint = text.codePointAt(i); - if (!Character.isWhitespace(codePoint)) { - return false; - } - i += Character.charCount(codePoint); - } - return true; - } - public static boolean isIdenticalAfterCapitalizeEachWord(final String text, final int[] sortedSeparators) { boolean needsCapsNext = true; diff --git a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java index 938d27122..351d01400 100644 --- a/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/SubtypeLocaleUtils.java @@ -49,17 +49,14 @@ public final class SubtypeLocaleUtils { private static Resources sResources; private static String[] sPredefinedKeyboardLayoutSet; // Keyboard layout to its display name map. - private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = - CollectionUtils.newHashMap(); + private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>(); // Keyboard layout to subtype name resource id map. - private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = - CollectionUtils.newHashMap(); + private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>(); // Exceptional locale to subtype name resource id map. - private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = - CollectionUtils.newHashMap(); + private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>(); // Exceptional locale to subtype name with layout resource id map. private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap = - CollectionUtils.newHashMap(); + new HashMap<>(); private static final String SUBTYPE_NAME_RESOURCE_PREFIX = "string/subtype_"; private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX = @@ -71,7 +68,7 @@ public final class SubtypeLocaleUtils { // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value. // This is for compatibility to keep the same subtype ids as pre-JellyBean. private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap = - CollectionUtils.newHashMap(); + new HashMap<>(); private SubtypeLocaleUtils() { // Intentional empty constructor for utility class. diff --git a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java index 42ea3c959..ab2b00e36 100644 --- a/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java +++ b/java/src/com/android/inputmethod/latin/utils/TargetPackageInfoGetterTask.java @@ -27,8 +27,7 @@ import com.android.inputmethod.compat.AppWorkaroundsUtils; public final class TargetPackageInfoGetterTask extends AsyncTask<String, Void, PackageInfo> { private static final int MAX_CACHE_ENTRIES = 64; // arbitrary - private static final LruCache<String, PackageInfo> sCache = - new LruCache<String, PackageInfo>(MAX_CACHE_ENTRIES); + private static final LruCache<String, PackageInfo> sCache = new LruCache<>(MAX_CACHE_ENTRIES); public static PackageInfo getCachedPackageInfo(final String packageName) { if (null == packageName) return null; diff --git a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java index 087a7f255..fafba79c2 100644 --- a/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/TypefaceUtils.java @@ -30,7 +30,7 @@ public final class TypefaceUtils { } // This sparse array caches key label text height in pixel indexed by key label text size. - private static final SparseArray<Float> sTextHeightCache = CollectionUtils.newSparseArray(); + private static final SparseArray<Float> sTextHeightCache = new SparseArray<>(); // Working variable for the following method. private static final Rect sTextHeightBounds = new Rect(); @@ -50,7 +50,7 @@ public final class TypefaceUtils { } // This sparse array caches key label text width in pixel indexed by key label text size. - private static final SparseArray<Float> sTextWidthCache = CollectionUtils.newSparseArray(); + private static final SparseArray<Float> sTextWidthCache = new SparseArray<>(); // Working variable for the following method. private static final Rect sTextWidthBounds = new Rect(); diff --git a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java b/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java deleted file mode 100644 index 06826dac0..000000000 --- a/java/src/com/android/inputmethod/latin/utils/UsabilityStudyLogUtils.java +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2016 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.utils; - -import android.content.Intent; -import android.content.pm.PackageManager; -import android.inputmethodservice.InputMethodService; -import android.net.Uri; -import android.os.Environment; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Process; -import android.util.Log; -import android.view.MotionEvent; - -import com.android.inputmethod.latin.LatinImeLogger; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.FileReader; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.channels.FileChannel; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -public final class UsabilityStudyLogUtils { - // TODO: remove code duplication with ResearchLog class - private static final String USABILITY_TAG = UsabilityStudyLogUtils.class.getSimpleName(); - private static final String FILENAME = "log.txt"; - private final Handler mLoggingHandler; - private File mFile; - private File mDirectory; - private InputMethodService mIms; - private PrintWriter mWriter; - private final Date mDate; - private final SimpleDateFormat mDateFormat; - - private UsabilityStudyLogUtils() { - mDate = new Date(); - mDateFormat = new SimpleDateFormat("yyyyMMdd-HHmmss.SSSZ", Locale.US); - - HandlerThread handlerThread = new HandlerThread("UsabilityStudyLogUtils logging task", - Process.THREAD_PRIORITY_BACKGROUND); - handlerThread.start(); - mLoggingHandler = new Handler(handlerThread.getLooper()); - } - - // Initialization-on-demand holder - private static final class OnDemandInitializationHolder { - public static final UsabilityStudyLogUtils sInstance = new UsabilityStudyLogUtils(); - } - - public static UsabilityStudyLogUtils getInstance() { - return OnDemandInitializationHolder.sInstance; - } - - public void init(final InputMethodService ims) { - mIms = ims; - mDirectory = ims.getFilesDir(); - } - - private void createLogFileIfNotExist() { - if ((mFile == null || !mFile.exists()) - && (mDirectory != null && mDirectory.exists())) { - try { - mWriter = getPrintWriter(mDirectory, FILENAME, false); - } catch (final IOException e) { - Log.e(USABILITY_TAG, "Can't create log file."); - } - } - } - - public static void writeBackSpace(final int x, final int y) { - UsabilityStudyLogUtils.getInstance().write("<backspace>\t" + x + "\t" + y); - } - - public static void writeChar(final char c, final int x, final int y) { - String inputChar = String.valueOf(c); - switch (c) { - case '\n': - inputChar = "<enter>"; - break; - case '\t': - inputChar = "<tab>"; - break; - case ' ': - inputChar = "<space>"; - break; - } - UsabilityStudyLogUtils.getInstance().write(inputChar + "\t" + x + "\t" + y); - LatinImeLogger.onPrintAllUsabilityStudyLogs(); - } - - public static void writeMotionEvent(final MotionEvent me) { - final int action = me.getActionMasked(); - final long eventTime = me.getEventTime(); - final int pointerCount = me.getPointerCount(); - for (int index = 0; index < pointerCount; index++) { - final int id = me.getPointerId(index); - final int x = (int)me.getX(index); - final int y = (int)me.getY(index); - final float size = me.getSize(index); - final float pressure = me.getPressure(index); - - final String eventTag; - switch (action) { - case MotionEvent.ACTION_UP: - eventTag = "[Up]"; - break; - case MotionEvent.ACTION_DOWN: - eventTag = "[Down]"; - break; - case MotionEvent.ACTION_POINTER_UP: - eventTag = "[PointerUp]"; - break; - case MotionEvent.ACTION_POINTER_DOWN: - eventTag = "[PointerDown]"; - break; - case MotionEvent.ACTION_MOVE: - eventTag = "[Move]"; - break; - default: - eventTag = "[Action" + action + "]"; - break; - } - getInstance().write(eventTag + eventTime + "," + id + "," + x + "," + y + "," + size - + "," + pressure); - } - } - - public void write(final String log) { - mLoggingHandler.post(new Runnable() { - @Override - public void run() { - createLogFileIfNotExist(); - final long currentTime = System.currentTimeMillis(); - mDate.setTime(currentTime); - - final String printString = String.format(Locale.US, "%s\t%d\t%s\n", - mDateFormat.format(mDate), currentTime, log); - if (LatinImeLogger.sDBG) { - Log.d(USABILITY_TAG, "Write: " + log); - } - mWriter.print(printString); - } - }); - } - - private synchronized String getBufferedLogs() { - mWriter.flush(); - final StringBuilder sb = new StringBuilder(); - final BufferedReader br = getBufferedReader(); - String line; - try { - while ((line = br.readLine()) != null) { - sb.append('\n'); - sb.append(line); - } - } catch (final IOException e) { - Log.e(USABILITY_TAG, "Can't read log file."); - } finally { - if (LatinImeLogger.sDBG) { - Log.d(USABILITY_TAG, "Got all buffered logs\n" + sb.toString()); - } - try { - br.close(); - } catch (final IOException e) { - // ignore. - } - } - return sb.toString(); - } - - public void emailResearcherLogsAll() { - mLoggingHandler.post(new Runnable() { - @Override - public void run() { - final Date date = new Date(); - date.setTime(System.currentTimeMillis()); - final String currentDateTimeString = - new SimpleDateFormat("yyyyMMdd-HHmmssZ", Locale.US).format(date); - if (mFile == null) { - Log.w(USABILITY_TAG, "No internal log file found."); - return; - } - if (mIms.checkCallingOrSelfPermission( - android.Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - Log.w(USABILITY_TAG, "Doesn't have the permission WRITE_EXTERNAL_STORAGE"); - return; - } - mWriter.flush(); - final String destPath = Environment.getExternalStorageDirectory() - + "/research-" + currentDateTimeString + ".log"; - final File destFile = new File(destPath); - try { - final FileInputStream srcStream = new FileInputStream(mFile); - final FileOutputStream destStream = new FileOutputStream(destFile); - final FileChannel src = srcStream.getChannel(); - final FileChannel dest = destStream.getChannel(); - src.transferTo(0, src.size(), dest); - src.close(); - srcStream.close(); - dest.close(); - destStream.close(); - } catch (final FileNotFoundException e1) { - Log.w(USABILITY_TAG, e1); - return; - } catch (final IOException e2) { - Log.w(USABILITY_TAG, e2); - return; - } - if (!destFile.exists()) { - Log.w(USABILITY_TAG, "Dest file doesn't exist."); - return; - } - final Intent intent = new Intent(Intent.ACTION_SEND); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - if (LatinImeLogger.sDBG) { - Log.d(USABILITY_TAG, "Destination file URI is " + destFile.toURI()); - } - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + destPath)); - intent.putExtra(Intent.EXTRA_SUBJECT, - "[Research Logs] " + currentDateTimeString); - mIms.startActivity(intent); - } - }); - } - - public void printAll() { - mLoggingHandler.post(new Runnable() { - @Override - public void run() { - mIms.getCurrentInputConnection().commitText(getBufferedLogs(), 0); - } - }); - } - - public void clearAll() { - mLoggingHandler.post(new Runnable() { - @Override - public void run() { - if (mFile != null && mFile.exists()) { - if (LatinImeLogger.sDBG) { - Log.d(USABILITY_TAG, "Delete log file."); - } - mFile.delete(); - mWriter.close(); - } - } - }); - } - - private BufferedReader getBufferedReader() { - createLogFileIfNotExist(); - try { - return new BufferedReader(new FileReader(mFile)); - } catch (final FileNotFoundException e) { - return null; - } - } - - private PrintWriter getPrintWriter(final File dir, final String filename, - final boolean renew) throws IOException { - mFile = new File(dir, filename); - if (mFile.exists()) { - if (renew) { - mFile.delete(); - } - } - return new PrintWriter(new FileOutputStream(mFile), true /* autoFlush */); - } -} diff --git a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java b/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java deleted file mode 100644 index a75d353c9..000000000 --- a/java/src/com/android/inputmethod/latin/utils/UserLogRingCharBuffer.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.utils; - -import android.inputmethodservice.InputMethodService; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.LatinImeLogger; -import com.android.inputmethod.latin.settings.Settings; - -public final class UserLogRingCharBuffer { - public /* for test */ static final int BUFSIZE = 20; - public /* for test */ int mLength = 0; - - private static UserLogRingCharBuffer sUserLogRingCharBuffer = new UserLogRingCharBuffer(); - private static final char PLACEHOLDER_DELIMITER_CHAR = '\uFFFC'; - private static final int INVALID_COORDINATE = -2; - private boolean mEnabled = false; - private int mEnd = 0; - private char[] mCharBuf = new char[BUFSIZE]; - private int[] mXBuf = new int[BUFSIZE]; - private int[] mYBuf = new int[BUFSIZE]; - - private UserLogRingCharBuffer() { - // Intentional empty constructor for singleton. - } - - @UsedForTesting - public static UserLogRingCharBuffer getInstance() { - return sUserLogRingCharBuffer; - } - - public static UserLogRingCharBuffer init(final InputMethodService context, - final boolean enabled, final boolean usabilityStudy) { - if (!(enabled || usabilityStudy)) { - return null; - } - sUserLogRingCharBuffer.mEnabled = true; - UsabilityStudyLogUtils.getInstance().init(context); - return sUserLogRingCharBuffer; - } - - private static int normalize(final int in) { - int ret = in % BUFSIZE; - return ret < 0 ? ret + BUFSIZE : ret; - } - - // TODO: accept code points - @UsedForTesting - public void push(final char c, final int x, final int y) { - if (!mEnabled) { - return; - } - if (LatinImeLogger.sUsabilityStudy) { - UsabilityStudyLogUtils.getInstance().writeChar(c, x, y); - } - mCharBuf[mEnd] = c; - mXBuf[mEnd] = x; - mYBuf[mEnd] = y; - mEnd = normalize(mEnd + 1); - if (mLength < BUFSIZE) { - ++mLength; - } - } - - public char pop() { - if (mLength < 1) { - return PLACEHOLDER_DELIMITER_CHAR; - } - mEnd = normalize(mEnd - 1); - --mLength; - return mCharBuf[mEnd]; - } - - public char getBackwardNthChar(final int n) { - if (mLength <= n || n < 0) { - return PLACEHOLDER_DELIMITER_CHAR; - } - return mCharBuf[normalize(mEnd - n - 1)]; - } - - public int getPreviousX(final char c, final int back) { - final int index = normalize(mEnd - 2 - back); - if (mLength <= back - || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { - return INVALID_COORDINATE; - } - return mXBuf[index]; - } - - public int getPreviousY(final char c, final int back) { - int index = normalize(mEnd - 2 - back); - if (mLength <= back - || Character.toLowerCase(c) != Character.toLowerCase(mCharBuf[index])) { - return INVALID_COORDINATE; - } - return mYBuf[index]; - } - - public String getLastWord(final int ignoreCharCount) { - final StringBuilder sb = new StringBuilder(); - int i = ignoreCharCount; - for (; i < mLength; ++i) { - final char c = mCharBuf[normalize(mEnd - 1 - i)]; - if (!Settings.getInstance().isWordSeparator(c)) { - break; - } - } - for (; i < mLength; ++i) { - char c = mCharBuf[normalize(mEnd - 1 - i)]; - if (!Settings.getInstance().isWordSeparator(c)) { - sb.append(c); - } else { - break; - } - } - return sb.reverse().toString(); - } - - public void reset() { - mLength = 0; - } -} diff --git a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java b/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java deleted file mode 100644 index 4f86526a7..000000000 --- a/java/src/com/android/inputmethod/research/BootBroadcastReceiver.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -/** - * Arrange for the uploading service to be run on regular intervals. - */ -public final class BootBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(final Context context, final Intent intent) { - if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { - UploaderService.cancelAndRescheduleUploadingService(context, - true /* needsRescheduling */); - } - } -} diff --git a/java/src/com/android/inputmethod/research/FeedbackActivity.java b/java/src/com/android/inputmethod/research/FeedbackActivity.java deleted file mode 100644 index 520b88d2f..000000000 --- a/java/src/com/android/inputmethod/research/FeedbackActivity.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.app.Activity; -import android.os.Bundle; - -import com.android.inputmethod.latin.R; - -public class FeedbackActivity extends Activity { - @Override - protected void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.research_feedback_activity); - final FeedbackLayout layout = (FeedbackLayout) findViewById(R.id.research_feedback_layout); - layout.setActivity(this); - } - - @Override - public void onBackPressed() { - ResearchLogger.getInstance().onLeavingSendFeedbackDialog(); - super.onBackPressed(); - } -} diff --git a/java/src/com/android/inputmethod/research/FeedbackFragment.java b/java/src/com/android/inputmethod/research/FeedbackFragment.java deleted file mode 100644 index 75fbbf1ba..000000000 --- a/java/src/com/android/inputmethod/research/FeedbackFragment.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.app.Fragment; -import android.os.Bundle; -import android.text.Editable; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.Toast; - -import com.android.inputmethod.latin.R; - -public class FeedbackFragment extends Fragment implements OnClickListener { - private static final String TAG = FeedbackFragment.class.getSimpleName(); - - public static final String KEY_FEEDBACK_STRING = "FeedbackString"; - public static final String KEY_INCLUDE_ACCOUNT_NAME = "IncludeAccountName"; - public static final String KEY_HAS_USER_RECORDING = "HasRecording"; - - private EditText mEditText; - private CheckBox mIncludingAccountNameCheckBox; - private CheckBox mIncludingUserRecordingCheckBox; - private Button mSendButton; - private Button mCancelButton; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - final View view = inflater.inflate(R.layout.research_feedback_fragment_layout, container, - false); - mEditText = (EditText) view.findViewById(R.id.research_feedback_contents); - mEditText.requestFocus(); - mIncludingAccountNameCheckBox = (CheckBox) view.findViewById( - R.id.research_feedback_include_account_name); - mIncludingUserRecordingCheckBox = (CheckBox) view.findViewById( - R.id.research_feedback_include_recording_checkbox); - mIncludingUserRecordingCheckBox.setOnClickListener(this); - - mSendButton = (Button) view.findViewById(R.id.research_feedback_send_button); - mSendButton.setOnClickListener(this); - mCancelButton = (Button) view.findViewById(R.id.research_feedback_cancel_button); - mCancelButton.setOnClickListener(this); - - if (savedInstanceState != null) { - restoreState(savedInstanceState); - } else { - final Bundle bundle = getActivity().getIntent().getExtras(); - if (bundle != null) { - restoreState(bundle); - } - } - return view; - } - - @Override - public void onClick(final View view) { - final ResearchLogger researchLogger = ResearchLogger.getInstance(); - if (view == mIncludingUserRecordingCheckBox) { - if (mIncludingUserRecordingCheckBox.isChecked()) { - final Bundle bundle = new Bundle(); - onSaveInstanceState(bundle); - - // Let the user make a recording - getActivity().finish(); - - researchLogger.setFeedbackDialogBundle(bundle); - researchLogger.onLeavingSendFeedbackDialog(); - researchLogger.startRecording(); - } - } else if (view == mSendButton) { - final Editable editable = mEditText.getText(); - final String feedbackContents = editable.toString(); - if (TextUtils.isEmpty(feedbackContents)) { - Toast.makeText(getActivity(), - R.string.research_feedback_empty_feedback_error_message, - Toast.LENGTH_LONG).show(); - } else { - final boolean isIncludingAccountName = mIncludingAccountNameCheckBox.isChecked(); - researchLogger.sendFeedback(feedbackContents, false /* isIncludingHistory */, - isIncludingAccountName, mIncludingUserRecordingCheckBox.isChecked()); - getActivity().finish(); - researchLogger.setFeedbackDialogBundle(null); - researchLogger.onLeavingSendFeedbackDialog(); - } - } else if (view == mCancelButton) { - Log.d(TAG, "Finishing"); - getActivity().finish(); - researchLogger.setFeedbackDialogBundle(null); - researchLogger.onLeavingSendFeedbackDialog(); - } else { - Log.e(TAG, "Unknown view passed to FeedbackFragment.onClick()"); - } - } - - @Override - public void onSaveInstanceState(final Bundle bundle) { - final String savedFeedbackString = mEditText.getText().toString(); - - bundle.putString(KEY_FEEDBACK_STRING, savedFeedbackString); - bundle.putBoolean(KEY_INCLUDE_ACCOUNT_NAME, mIncludingAccountNameCheckBox.isChecked()); - bundle.putBoolean(KEY_HAS_USER_RECORDING, mIncludingUserRecordingCheckBox.isChecked()); - } - - private void restoreState(final Bundle bundle) { - mEditText.setText(bundle.getString(KEY_FEEDBACK_STRING)); - mIncludingAccountNameCheckBox.setChecked(bundle.getBoolean(KEY_INCLUDE_ACCOUNT_NAME)); - mIncludingUserRecordingCheckBox.setChecked(bundle.getBoolean(KEY_HAS_USER_RECORDING)); - } -} diff --git a/java/src/com/android/inputmethod/research/FeedbackLayout.java b/java/src/com/android/inputmethod/research/FeedbackLayout.java deleted file mode 100644 index d283d14b2..000000000 --- a/java/src/com/android/inputmethod/research/FeedbackLayout.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.app.Activity; -import android.content.Context; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.widget.LinearLayout; - -public class FeedbackLayout extends LinearLayout { - private Activity mActivity; - - public FeedbackLayout(Context context) { - super(context); - } - - public FeedbackLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public FeedbackLayout(Context context, AttributeSet attrs, int defstyle) { - super(context, attrs, defstyle); - } - - public void setActivity(Activity activity) { - mActivity = activity; - } - - @Override - public boolean dispatchKeyEventPreIme(KeyEvent event) { - if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { - KeyEvent.DispatcherState state = getKeyDispatcherState(); - if (state != null) { - if (event.getAction() == KeyEvent.ACTION_DOWN - && event.getRepeatCount() == 0) { - state.startTracking(event, this); - return true; - } else if (event.getAction() == KeyEvent.ACTION_UP - && !event.isCanceled() && state.isTracking(event)) { - mActivity.onBackPressed(); - return true; - } - } - } - return super.dispatchKeyEventPreIme(event); - } -} diff --git a/java/src/com/android/inputmethod/research/FeedbackLog.java b/java/src/com/android/inputmethod/research/FeedbackLog.java deleted file mode 100644 index 5af194c32..000000000 --- a/java/src/com/android/inputmethod/research/FeedbackLog.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.content.Context; - -import java.io.File; - -public class FeedbackLog extends ResearchLog { - public FeedbackLog(final File outputFile, final Context context) { - super(outputFile, context); - } - - @Override - public boolean isFeedbackLog() { - return true; - } -} diff --git a/java/src/com/android/inputmethod/research/FixedLogBuffer.java b/java/src/com/android/inputmethod/research/FixedLogBuffer.java deleted file mode 100644 index 8b64de8ae..000000000 --- a/java/src/com/android/inputmethod/research/FixedLogBuffer.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import java.util.ArrayList; -import java.util.LinkedList; - -/** - * A buffer that holds a fixed number of LogUnits. - * - * LogUnits are added in and shifted out in temporal order. Only a subset of the LogUnits are - * actual words; the other LogUnits do not count toward the word limit. Once the buffer reaches - * capacity, adding another LogUnit that is a word evicts the oldest LogUnits out one at a time to - * stay under the capacity limit. - * - * This variant of a LogBuffer has a limited memory footprint because of its limited size. This - * makes it useful, for example, for recording a window of the user's most recent actions in case - * they want to report an observed error that they do not know how to reproduce. - */ -public class FixedLogBuffer extends LogBuffer { - /* package for test */ int mWordCapacity; - // The number of members of mLogUnits that are actual words. - private int mNumActualWords; - - /** - * Create a new LogBuffer that can hold a fixed number of LogUnits that are words (and - * unlimited number of non-word LogUnits), and that outputs its result to a researchLog. - * - * @param wordCapacity maximum number of words - */ - public FixedLogBuffer(final int wordCapacity) { - super(); - if (wordCapacity <= 0) { - throw new IllegalArgumentException("wordCapacity must be 1 or greater."); - } - mWordCapacity = wordCapacity; - mNumActualWords = 0; - } - - /** - * Adds a new LogUnit to the front of the LIFO queue, evicting existing LogUnit's - * (oldest first) if word capacity is reached. - */ - @Override - public void shiftIn(final LogUnit newLogUnit) { - if (!newLogUnit.hasOneOrMoreWords()) { - // This LogUnit doesn't contain any word, so it doesn't count toward the word-limit. - super.shiftIn(newLogUnit); - return; - } - 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) { - shiftOutWords(numWordsIncoming); - } - } - super.shiftIn(newLogUnit); - mNumActualWords += numWordsIncoming; - } - - @Override - public LogUnit unshiftIn() { - final LogUnit logUnit = super.unshiftIn(); - if (logUnit != null && logUnit.hasOneOrMoreWords()) { - mNumActualWords -= logUnit.getNumWords(); - } - return logUnit; - } - - public int getNumWords() { - return mNumActualWords; - } - - /** - * Removes all LogUnits from the buffer without calling onShiftOut(). - */ - @Override - public void clear() { - super.clear(); - mNumActualWords = 0; - } - - /** - * Called when the buffer has just shifted in one more word than its maximum, and its about to - * shift out LogUnits to bring it back down to the maximum. - * - * Base class does nothing; subclasses may override if they want to record non-privacy sensitive - * events that fall off the end. - */ - protected void onBufferFull() { - } - - @Override - public LogUnit shiftOut() { - final LogUnit logUnit = super.shiftOut(); - if (logUnit != null && logUnit.hasOneOrMoreWords()) { - mNumActualWords -= logUnit.getNumWords(); - } - return logUnit; - } - - /** - * Remove LogUnits from the front of the LogBuffer until {@code numWords} have been removed. - * - * If there are less than {@code numWords} in the buffer, shifts out all {@code LogUnit}s. - * - * @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 numWordsShiftedOut = 0; - do { - final LogUnit logUnit = shiftOut(); - if (logUnit == null) break; - numWordsShiftedOut += logUnit.getNumWords(); - } while (numWordsShiftedOut < numWords); - return numWordsShiftedOut; - } - - public void shiftOutAll() { - final LinkedList<LogUnit> logUnits = getLogUnits(); - while (!logUnits.isEmpty()) { - shiftOut(); - } - mNumActualWords = 0; - } - - /** - * Returns a list of {@link LogUnit}s at the front of the buffer that have words associated with - * them. - * - * There will be no more than {@code n} words in the returned list. So if 2 words are - * requested, and the first LogUnit has 3 words, it is not returned. If 2 words are requested, - * and the first LogUnit has only 1 word, and the next LogUnit 2 words, only the first LogUnit - * is returned. If the first LogUnit has no words associated with it, and the second LogUnit - * has three words, then only the first LogUnit (which has no associated words) is returned. If - * there are not enough LogUnits in the buffer to meet the word requirement, then all LogUnits - * will be returned. - * - * @param n The maximum number of {@link LogUnit}s with words to return. - * @return The list of the {@link LogUnit}s containing the first n words - */ - public ArrayList<LogUnit> peekAtFirstNWords(int n) { - final LinkedList<LogUnit> logUnits = getLogUnits(); - // Allocate space for n*2 logUnits. There will be at least n, one for each word, and - // there may be additional for punctuation, between-word commands, etc. This should be - // enough that reallocation won't be necessary. - final ArrayList<LogUnit> resultList = new ArrayList<LogUnit>(n * 2); - for (final LogUnit logUnit : logUnits) { - n -= logUnit.getNumWords(); - if (n < 0) break; - resultList.add(logUnit); - } - return resultList; - } -} diff --git a/java/src/com/android/inputmethod/research/JsonUtils.java b/java/src/com/android/inputmethod/research/JsonUtils.java deleted file mode 100644 index 6170b4339..000000000 --- a/java/src/com/android/inputmethod/research/JsonUtils.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.content.SharedPreferences; -import android.util.JsonWriter; -import android.view.MotionEvent; -import android.view.inputmethod.CompletionInfo; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; - -import java.io.IOException; -import java.util.Map; - -/** - * Routines for mapping classes and variables to JSON representations for logging. - */ -/* package */ class JsonUtils { - private JsonUtils() { - // This utility class is not publicly instantiable. - } - - /* package */ static void writeJson(final CompletionInfo[] ci, final JsonWriter jsonWriter) - throws IOException { - jsonWriter.beginArray(); - for (int j = 0; j < ci.length; j++) { - jsonWriter.value(ci[j].toString()); - } - jsonWriter.endArray(); - } - - /* package */ static void writeJson(final SharedPreferences prefs, final JsonWriter jsonWriter) - throws IOException { - jsonWriter.beginObject(); - for (Map.Entry<String,?> entry : prefs.getAll().entrySet()) { - jsonWriter.name(entry.getKey()); - final Object innerValue = entry.getValue(); - if (innerValue == null) { - jsonWriter.nullValue(); - } else if (innerValue instanceof Boolean) { - jsonWriter.value((Boolean) innerValue); - } else if (innerValue instanceof Number) { - jsonWriter.value((Number) innerValue); - } else { - jsonWriter.value(innerValue.toString()); - } - } - jsonWriter.endObject(); - } - - /* package */ static void writeJson(final Key[] keys, final JsonWriter jsonWriter) - throws IOException { - jsonWriter.beginArray(); - for (Key key : keys) { - writeJson(key, jsonWriter); - } - jsonWriter.endArray(); - } - - private static void writeJson(final Key key, final JsonWriter jsonWriter) throws IOException { - jsonWriter.beginObject(); - jsonWriter.name("code").value(key.getCode()); - jsonWriter.name("altCode").value(key.getAltCode()); - jsonWriter.name("x").value(key.getX()); - jsonWriter.name("y").value(key.getY()); - jsonWriter.name("w").value(key.getWidth()); - jsonWriter.name("h").value(key.getHeight()); - jsonWriter.endObject(); - } - - /* package */ static void writeJson(final SuggestedWords words, final JsonWriter jsonWriter) - throws IOException { - jsonWriter.beginObject(); - jsonWriter.name("typedWordValid").value(words.mTypedWordValid); - jsonWriter.name("willAutoCorrect") - .value(words.mWillAutoCorrect); - jsonWriter.name("isPunctuationSuggestions") - .value(words.isPunctuationSuggestions()); - jsonWriter.name("isObsoleteSuggestions").value(words.mIsObsoleteSuggestions); - jsonWriter.name("isPrediction").value(words.mIsPrediction); - jsonWriter.name("suggestedWords"); - jsonWriter.beginArray(); - final int size = words.size(); - for (int j = 0; j < size; j++) { - final SuggestedWordInfo wordInfo = words.getInfo(j); - jsonWriter.beginObject(); - jsonWriter.name("word").value(wordInfo.toString()); - jsonWriter.name("score").value(wordInfo.mScore); - jsonWriter.name("kind").value(wordInfo.mKind); - jsonWriter.name("sourceDict").value(wordInfo.mSourceDict.mDictType); - jsonWriter.endObject(); - } - jsonWriter.endArray(); - jsonWriter.endObject(); - } - - /* package */ static void writeJson(final MotionEvent me, final JsonWriter jsonWriter) - throws IOException { - jsonWriter.beginObject(); - jsonWriter.name("pointerIds"); - jsonWriter.beginArray(); - final int pointerCount = me.getPointerCount(); - for (int index = 0; index < pointerCount; index++) { - jsonWriter.value(me.getPointerId(index)); - } - jsonWriter.endArray(); - - jsonWriter.name("xyt"); - jsonWriter.beginArray(); - final int historicalSize = me.getHistorySize(); - for (int index = 0; index < historicalSize; index++) { - jsonWriter.beginObject(); - jsonWriter.name("t"); - jsonWriter.value(me.getHistoricalEventTime(index)); - jsonWriter.name("d"); - jsonWriter.beginArray(); - for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) { - jsonWriter.beginObject(); - jsonWriter.name("x"); - jsonWriter.value(me.getHistoricalX(pointerIndex, index)); - jsonWriter.name("y"); - jsonWriter.value(me.getHistoricalY(pointerIndex, index)); - jsonWriter.endObject(); - } - jsonWriter.endArray(); - jsonWriter.endObject(); - } - jsonWriter.beginObject(); - jsonWriter.name("t"); - jsonWriter.value(me.getEventTime()); - jsonWriter.name("d"); - jsonWriter.beginArray(); - for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) { - jsonWriter.beginObject(); - jsonWriter.name("x"); - jsonWriter.value(me.getX(pointerIndex)); - jsonWriter.name("y"); - jsonWriter.value(me.getY(pointerIndex)); - jsonWriter.endObject(); - } - jsonWriter.endArray(); - jsonWriter.endObject(); - jsonWriter.endArray(); - jsonWriter.endObject(); - } -} diff --git a/java/src/com/android/inputmethod/research/LogBuffer.java b/java/src/com/android/inputmethod/research/LogBuffer.java deleted file mode 100644 index b07b761f0..000000000 --- a/java/src/com/android/inputmethod/research/LogBuffer.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import java.util.LinkedList; - -/** - * Maintain a FIFO queue of LogUnits. - * - * This class provides an unbounded queue. This is useful when the user is aware that their actions - * are being recorded, such as when they are trying to reproduce a bug. In this case, there should - * not be artificial restrictions on how many events that can be saved. - */ -public class LogBuffer { - // TODO: Gracefully handle situations in which this LogBuffer is consuming too much memory. - // This may happen, for example, if the user has forgotten that data is being logged. - private final LinkedList<LogUnit> mLogUnits; - - public LogBuffer() { - mLogUnits = new LinkedList<LogUnit>(); - } - - protected LinkedList<LogUnit> getLogUnits() { - return mLogUnits; - } - - public void clear() { - mLogUnits.clear(); - } - - public void shiftIn(final LogUnit logUnit) { - mLogUnits.add(logUnit); - } - - public LogUnit unshiftIn() { - if (mLogUnits.isEmpty()) { - return null; - } - return mLogUnits.removeLast(); - } - - public LogUnit peekLastLogUnit() { - if (mLogUnits.isEmpty()) { - return null; - } - return mLogUnits.peekLast(); - } - - public boolean isEmpty() { - return mLogUnits.isEmpty(); - } - - public LogUnit shiftOut() { - if (isEmpty()) { - return null; - } - return mLogUnits.removeFirst(); - } -} diff --git a/java/src/com/android/inputmethod/research/LogStatement.java b/java/src/com/android/inputmethod/research/LogStatement.java deleted file mode 100644 index 06b918af5..000000000 --- a/java/src/com/android/inputmethod/research/LogStatement.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.content.SharedPreferences; -import android.util.JsonWriter; -import android.util.Log; -import android.view.MotionEvent; -import android.view.inputmethod.CompletionInfo; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.define.ProductionFlag; - -import java.io.IOException; - -/** - * A template for typed information stored in the logs. - * - * A LogStatement contains a name, keys, and flags about whether the {@code Object[] values} - * associated with the {@code String[] keys} are likely to reveal information about the user. The - * actual values are stored separately. - */ -public class LogStatement { - private static final String TAG = LogStatement.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - - // Constants for particular statements - public static final String TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT = - "PointerTrackerCallListenerOnCodeInput"; - public static final String KEY_CODE = "code"; - public static final String VALUE_RESEARCH = "research"; - public static final String TYPE_MAIN_KEYBOARD_VIEW_ON_LONG_PRESS = - "MainKeyboardViewOnLongPress"; - public static final String ACTION = "action"; - public static final String VALUE_DOWN = "DOWN"; - public static final String TYPE_MOTION_EVENT = "MotionEvent"; - public static final String KEY_IS_LOGGING_RELATED = "isLoggingRelated"; - - // Keys for internal key/value pairs - private static final String CURRENT_TIME_KEY = "_ct"; - private static final String UPTIME_KEY = "_ut"; - private static final String EVENT_TYPE_KEY = "_ty"; - - // Name specifying the LogStatement type. - private final String mType; - - // mIsPotentiallyPrivate indicates that event contains potentially private information. If - // the word that this event is a part of is determined to be privacy-sensitive, then this - // event should not be included in the output log. The system waits to output until the - // containing word is known. - private final boolean mIsPotentiallyPrivate; - - // mIsPotentiallyRevealing indicates that this statement may disclose details about other - // words typed in other LogUnits. This can happen if the user is not inserting spaces, and - // data from Suggestions and/or Composing text reveals the entire "megaword". For example, - // say the user is typing "for the win", and the system wants to record the bigram "the - // win". If the user types "forthe", omitting the space, the system will give "for the" as - // a suggestion. If the user accepts the autocorrection, the suggestion for "for the" is - // included in the log for the word "the", disclosing that the previous word had been "for". - // For now, we simply do not include this data when logging part of a "megaword". - private final boolean mIsPotentiallyRevealing; - - // mKeys stores the names that are the attributes in the output json objects - private final String[] mKeys; - private static final String[] NULL_KEYS = new String[0]; - - LogStatement(final String name, final boolean isPotentiallyPrivate, - final boolean isPotentiallyRevealing, final String... keys) { - mType = name; - mIsPotentiallyPrivate = isPotentiallyPrivate; - mIsPotentiallyRevealing = isPotentiallyRevealing; - mKeys = (keys == null) ? NULL_KEYS : keys; - } - - public String getType() { - return mType; - } - - public boolean isPotentiallyPrivate() { - return mIsPotentiallyPrivate; - } - - public boolean isPotentiallyRevealing() { - return mIsPotentiallyRevealing; - } - - public String[] getKeys() { - return mKeys; - } - - /** - * Utility function to test whether a key-value pair exists in a LogStatement. - * - * A LogStatement is really just a template -- it does not contain the values, only the - * keys. So the values must be passed in as an argument. - * - * @param queryKey the String that is tested by {@code String.equals()} to the keys in the - * LogStatement - * @param queryValue an Object that must be {@code Object.equals()} to the key's corresponding - * value in the {@code values} array - * @param values the values corresponding to mKeys - * - * @returns {@true} if {@code queryKey} exists in the keys for this LogStatement, and {@code - * queryValue} matches the corresponding value in {@code values} - * - * @throws IllegalArgumentException if {@code values.length} is not equal to keys().length() - */ - public boolean containsKeyValuePair(final String queryKey, final Object queryValue, - final Object[] values) { - if (mKeys.length != values.length) { - throw new IllegalArgumentException("Mismatched number of keys and values."); - } - final int length = mKeys.length; - for (int i = 0; i < length; i++) { - if (mKeys[i].equals(queryKey) && values[i].equals(queryValue)) { - return true; - } - } - return false; - } - - /** - * Utility function to set a value in a LogStatement. - * - * A LogStatement is really just a template -- it does not contain the values, only the - * keys. So the values must be passed in as an argument. - * - * @param queryKey the String that is tested by {@code String.equals()} to the keys in the - * LogStatement - * @param values the array of values corresponding to mKeys - * @param newValue the replacement value to go into the {@code values} array - * - * @returns {@true} if the key exists and the value was successfully set, {@false} otherwise - * - * @throws IllegalArgumentException if {@code values.length} is not equal to keys().length() - */ - public boolean setValue(final String queryKey, final Object[] values, final Object newValue) { - if (mKeys.length != values.length) { - throw new IllegalArgumentException("Mismatched number of keys and values."); - } - final int length = mKeys.length; - for (int i = 0; i < length; i++) { - if (mKeys[i].equals(queryKey)) { - values[i] = newValue; - return true; - } - } - return false; - } - - /** - * Write the contents out through jsonWriter. - * - * The JsonWriter class must have already had {@code JsonWriter.beginArray} called on it. - * - * Note that this method is not thread safe for the same jsonWriter. Callers must ensure - * thread safety. - */ - public boolean outputToLocked(final JsonWriter jsonWriter, final Long time, - final Object... values) { - if (DEBUG) { - if (mKeys.length != values.length) { - Log.d(TAG, "Key and Value list sizes do not match. " + mType); - } - } - try { - jsonWriter.beginObject(); - jsonWriter.name(CURRENT_TIME_KEY).value(System.currentTimeMillis()); - jsonWriter.name(UPTIME_KEY).value(time); - jsonWriter.name(EVENT_TYPE_KEY).value(mType); - final int length = values.length; - for (int i = 0; i < length; i++) { - jsonWriter.name(mKeys[i]); - final Object value = values[i]; - if (value instanceof CharSequence) { - jsonWriter.value(value.toString()); - } else if (value instanceof Number) { - jsonWriter.value((Number) value); - } else if (value instanceof Boolean) { - jsonWriter.value((Boolean) value); - } else if (value instanceof CompletionInfo[]) { - JsonUtils.writeJson((CompletionInfo[]) value, jsonWriter); - } else if (value instanceof SharedPreferences) { - JsonUtils.writeJson((SharedPreferences) value, jsonWriter); - } else if (value instanceof Key[]) { - JsonUtils.writeJson((Key[]) value, jsonWriter); - } else if (value instanceof SuggestedWords) { - JsonUtils.writeJson((SuggestedWords) value, jsonWriter); - } else if (value instanceof MotionEvent) { - JsonUtils.writeJson((MotionEvent) value, jsonWriter); - } else if (value == null) { - jsonWriter.nullValue(); - } else { - if (DEBUG) { - Log.w(TAG, "Unrecognized type to be logged: " - + (value == null ? "<null>" : value.getClass().getName())); - } - jsonWriter.nullValue(); - } - } - jsonWriter.endObject(); - } catch (IOException e) { - e.printStackTrace(); - Log.w(TAG, "Error in JsonWriter; skipping LogStatement"); - return false; - } - return true; - } -} diff --git a/java/src/com/android/inputmethod/research/LogUnit.java b/java/src/com/android/inputmethod/research/LogUnit.java deleted file mode 100644 index 3366df12a..000000000 --- a/java/src/com/android/inputmethod/research/LogUnit.java +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.JsonWriter; -import android.util.Log; - -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; -import com.android.inputmethod.latin.define.ProductionFlag; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; - -/** - * A group of log statements related to each other. - * - * A LogUnit is collection of LogStatements, each of which is generated by at a particular point - * in the code. (There is no LogStatement class; the data is stored across the instance variables - * here.) A single LogUnit's statements can correspond to all the calls made while in the same - * composing region, or all the calls between committing the last composing region, and the first - * character of the next composing region. - * - * Individual statements in a log may be marked as potentially private. If so, then they are only - * published to a ResearchLog if the ResearchLogger determines that publishing the entire LogUnit - * will not violate the user's privacy. Checks for this may include whether other LogUnits have - * been published recently, or whether the LogUnit contains numbers, etc. - */ -public class LogUnit { - private static final String TAG = LogUnit.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - - private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); - private static final String[] EMPTY_STRING_ARRAY = new String[0]; - - private final ArrayList<LogStatement> mLogStatementList; - private final ArrayList<Object[]> mValuesList; - // Assume that mTimeList is sorted in increasing order. Do not insert null values into - // mTimeList. - private final ArrayList<Long> mTimeList; - // Words that this LogUnit generates. Should be null if the data in the LogUnit does not - // generate a genuine word (i.e. separators alone do not count as a word). Should never be - // empty. Note that if the user types spaces explicitly, then normally mWords should contain - // only a single word; it will only contain space-separate multiple words if the user does not - // enter a space, and the system enters one automatically. - private String mWords; - private String[] mWordArray = EMPTY_STRING_ARRAY; - private boolean mMayContainDigit; - private boolean mIsPartOfMegaword; - private boolean mContainsUserDeletions; - - // mCorrectionType indicates whether the word was corrected at all, and if so, the nature of the - // correction. - private int mCorrectionType; - // LogUnits start in this state. If a word is entered without being corrected, it will have - // this CorrectiontType. - public static final int CORRECTIONTYPE_NO_CORRECTION = 0; - // The LogUnit was corrected manually by the user in an unspecified way. - public static final int CORRECTIONTYPE_CORRECTION = 1; - // The LogUnit was corrected manually by the user to a word not in the list of suggestions of - // the first word typed here. (Note: this is a heuristic value, it may be incorrect, for - // example, if the user repositions the cursor). - public static final int CORRECTIONTYPE_DIFFERENT_WORD = 2; - // The LogUnit was corrected manually by the user to a word that was in the list of suggestions - // of the first word typed here. (Again, a heuristic). It is probably a typo correction. - public static final int CORRECTIONTYPE_TYPO = 3; - // TODO: Rather than just tracking the current state, keep a historical record of the LogUnit's - // state and statistics. This should include how many times it has been corrected, whether - // other LogUnit edits were done between edits to this LogUnit, etc. Also track when a LogUnit - // previously contained a word, but was corrected to empty (because it was deleted, and there is - // no known replacement). - - private SuggestedWords mSuggestedWords; - - public LogUnit() { - mLogStatementList = new ArrayList<LogStatement>(); - mValuesList = new ArrayList<Object[]>(); - mTimeList = new ArrayList<Long>(); - mIsPartOfMegaword = false; - mCorrectionType = CORRECTIONTYPE_NO_CORRECTION; - mSuggestedWords = null; - } - - private LogUnit(final ArrayList<LogStatement> logStatementList, - final ArrayList<Object[]> valuesList, - final ArrayList<Long> timeList, - final boolean isPartOfMegaword) { - mLogStatementList = logStatementList; - mValuesList = valuesList; - mTimeList = timeList; - mIsPartOfMegaword = isPartOfMegaword; - mCorrectionType = CORRECTIONTYPE_NO_CORRECTION; - mSuggestedWords = null; - } - - private static final Object[] NULL_VALUES = new Object[0]; - /** - * Adds a new log statement. The time parameter in successive calls to this method must be - * monotonically increasing, or splitByTime() will not work. - */ - public void addLogStatement(final LogStatement logStatement, final long time, - Object... values) { - if (values == null) { - values = NULL_VALUES; - } - mLogStatementList.add(logStatement); - mValuesList.add(values); - mTimeList.add(time); - } - - /** - * Publish the contents of this LogUnit to {@code researchLog}. - * - * For each publishable {@code LogStatement}, invoke {@link LogStatement#outputToLocked}. - * - * @param researchLog where to publish the contents of this {@code LogUnit} - * @param canIncludePrivateData whether the private data in this {@code LogUnit} should be - * included - * - * @throws IOException if publication to the log file is not possible - */ - public synchronized void publishTo(final ResearchLog researchLog, - final boolean canIncludePrivateData) throws IOException { - // Write out any logStatement that passes the privacy filter. - final int size = mLogStatementList.size(); - if (size != 0) { - // Note that jsonWriter is only set to a non-null value if the logUnit start text is - // output and at least one logStatement is output. - JsonWriter jsonWriter = researchLog.getInitializedJsonWriterLocked(); - outputLogUnitStart(jsonWriter, canIncludePrivateData); - for (int i = 0; i < size; i++) { - final LogStatement logStatement = mLogStatementList.get(i); - if (!canIncludePrivateData && logStatement.isPotentiallyPrivate()) { - continue; - } - if (mIsPartOfMegaword && logStatement.isPotentiallyRevealing()) { - continue; - } - logStatement.outputToLocked(jsonWriter, mTimeList.get(i), mValuesList.get(i)); - } - outputLogUnitStop(jsonWriter); - } - } - - private static final String WORD_KEY = "_wo"; - private static final String NUM_WORDS_KEY = "_nw"; - private static final String CORRECTION_TYPE_KEY = "_corType"; - private static final String LOG_UNIT_BEGIN_KEY = "logUnitStart"; - private static final String LOG_UNIT_END_KEY = "logUnitEnd"; - - final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA = - new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */, - false /* isPotentiallyRevealing */, WORD_KEY, CORRECTION_TYPE_KEY, - NUM_WORDS_KEY); - final LogStatement LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA = - new LogStatement(LOG_UNIT_BEGIN_KEY, false /* isPotentiallyPrivate */, - false /* isPotentiallyRevealing */, NUM_WORDS_KEY); - private void outputLogUnitStart(final JsonWriter jsonWriter, - final boolean canIncludePrivateData) { - final LogStatement logStatement; - if (canIncludePrivateData) { - LOGSTATEMENT_LOG_UNIT_BEGIN_WITH_PRIVATE_DATA.outputToLocked(jsonWriter, - SystemClock.uptimeMillis(), getWordsAsString(), getCorrectionType(), - getNumWords()); - } else { - LOGSTATEMENT_LOG_UNIT_BEGIN_WITHOUT_PRIVATE_DATA.outputToLocked(jsonWriter, - SystemClock.uptimeMillis(), getNumWords()); - } - } - - final LogStatement LOGSTATEMENT_LOG_UNIT_END = - new LogStatement(LOG_UNIT_END_KEY, false /* isPotentiallyPrivate */, - false /* isPotentiallyRevealing */); - private void outputLogUnitStop(final JsonWriter jsonWriter) { - LOGSTATEMENT_LOG_UNIT_END.outputToLocked(jsonWriter, SystemClock.uptimeMillis()); - } - - /** - * Mark the current logUnit as containing data to generate {@code newWords}. - * - * If {@code setWord()} was previously called for this LogUnit, then the method will try to - * determine what kind of correction it is, and update its internal state of the correctionType - * accordingly. - * - * @param newWords The words this LogUnit generates. Caller should not pass null or the empty - * string. - */ - public void setWords(final String newWords) { - if (hasOneOrMoreWords()) { - // The word was already set once, and it is now being changed. See if the new word - // is close to the old word. If so, then the change is probably a typo correction. - // If not, the user may have decided to enter a different word, so flag it. - if (mSuggestedWords != null) { - if (isInSuggestedWords(newWords, mSuggestedWords)) { - mCorrectionType = CORRECTIONTYPE_TYPO; - } else { - mCorrectionType = CORRECTIONTYPE_DIFFERENT_WORD; - } - } else { - // No suggested words, so it's not clear whether it's a typo or different word. - // Mark it as a generic correction. - mCorrectionType = CORRECTIONTYPE_CORRECTION; - } - } else { - mCorrectionType = CORRECTIONTYPE_NO_CORRECTION; - } - mWords = newWords; - - // Update mWordArray - mWordArray = (TextUtils.isEmpty(mWords)) ? EMPTY_STRING_ARRAY - : WHITESPACE_PATTERN.split(mWords); - if (mWordArray.length > 0 && TextUtils.isEmpty(mWordArray[0])) { - // Empty string at beginning of array. Must have been whitespace at the start of the - // word. Remove the empty string. - mWordArray = Arrays.copyOfRange(mWordArray, 1, mWordArray.length); - } - } - - public String getWordsAsString() { - return mWords; - } - - /** - * Retuns the words generated by the data in this LogUnit. - * - * The first word may be an empty string, if the data in the LogUnit started by generating - * whitespace. - * - * @return the array of words. an empty list of there are no words associated with this LogUnit. - */ - public String[] getWordsAsStringArray() { - return mWordArray; - } - - public boolean hasOneOrMoreWords() { - return mWordArray.length >= 1; - } - - public int getNumWords() { - return mWordArray.length; - } - - // TODO: Refactor to eliminate getter/setters - public void setMayContainDigit() { - mMayContainDigit = true; - } - - // TODO: Refactor to eliminate getter/setters - public boolean mayContainDigit() { - return mMayContainDigit; - } - - // TODO: Refactor to eliminate getter/setters - public void setContainsUserDeletions() { - mContainsUserDeletions = true; - } - - // TODO: Refactor to eliminate getter/setters - public boolean containsUserDeletions() { - return mContainsUserDeletions; - } - - // TODO: Refactor to eliminate getter/setters - public void setCorrectionType(final int correctionType) { - mCorrectionType = correctionType; - } - - // TODO: Refactor to eliminate getter/setters - public int getCorrectionType() { - return mCorrectionType; - } - - public boolean isEmpty() { - return mLogStatementList.isEmpty(); - } - - /** - * Split this logUnit, with all events before maxTime staying in the current logUnit, and all - * events after maxTime going into a new LogUnit that is returned. - */ - public LogUnit splitByTime(final long maxTime) { - // Assume that mTimeList is in sorted order. - final int length = mTimeList.size(); - // TODO: find time by binary search, e.g. using Collections#binarySearch() - for (int index = 0; index < length; index++) { - if (mTimeList.get(index) > maxTime) { - final List<LogStatement> laterLogStatements = - mLogStatementList.subList(index, length); - final List<Object[]> laterValues = mValuesList.subList(index, length); - final List<Long> laterTimes = mTimeList.subList(index, length); - - // Create the LogUnit containing the later logStatements and associated data. - final LogUnit newLogUnit = new LogUnit( - new ArrayList<LogStatement>(laterLogStatements), - new ArrayList<Object[]>(laterValues), - new ArrayList<Long>(laterTimes), - true /* isPartOfMegaword */); - newLogUnit.mWords = null; - newLogUnit.mMayContainDigit = mMayContainDigit; - newLogUnit.mContainsUserDeletions = mContainsUserDeletions; - - // Purge the logStatements and associated data from this LogUnit. - laterLogStatements.clear(); - laterValues.clear(); - laterTimes.clear(); - mIsPartOfMegaword = true; - - return newLogUnit; - } - } - return new LogUnit(); - } - - public void append(final LogUnit logUnit) { - mLogStatementList.addAll(logUnit.mLogStatementList); - mValuesList.addAll(logUnit.mValuesList); - mTimeList.addAll(logUnit.mTimeList); - mWords = null; - if (logUnit.mWords != null) { - setWords(logUnit.mWords); - } - mMayContainDigit = mMayContainDigit || logUnit.mMayContainDigit; - mContainsUserDeletions = mContainsUserDeletions || logUnit.mContainsUserDeletions; - mIsPartOfMegaword = false; - } - - public SuggestedWords getSuggestions() { - return mSuggestedWords; - } - - /** - * Initialize the suggestions. - * - * Once set to a non-null value, the suggestions may not be changed again. This is to keep - * track of the list of words that are close to the user's initial effort to type the word. - * Only words that are close to the initial effort are considered typo corrections. - */ - public void initializeSuggestions(final SuggestedWords suggestedWords) { - if (mSuggestedWords == null) { - mSuggestedWords = suggestedWords; - } - } - - private static boolean isInSuggestedWords(final String queryWord, - final SuggestedWords suggestedWords) { - if (TextUtils.isEmpty(queryWord)) { - return false; - } - final int size = suggestedWords.size(); - for (int i = 0; i < size; i++) { - final SuggestedWordInfo wordInfo = suggestedWords.getInfo(i); - if (queryWord.equals(wordInfo.mWord)) { - return true; - } - } - return false; - } - - /** - * Remove data associated with selecting the Research button. - * - * A LogUnit will capture all user interactions with the IME, including the "meta-interactions" - * of using the Research button to control the logging (e.g. by starting and stopping recording - * of a test case). Because meta-interactions should not be part of the normal log, calling - * this method will set a field in the LogStatements of the motion events to indiciate that - * they should be disregarded. - * - * This implementation assumes that the data recorded by the meta-interaction takes the - * form of all events following the first MotionEvent.ACTION_DOWN before the first long-press - * before the last onCodeEvent containing a code matching {@code LogStatement.VALUE_RESEARCH}. - * - * @returns true if data was removed - */ - public boolean removeResearchButtonInvocation() { - // This method is designed to be idempotent. - - // First, find last invocation of "research" key - final int indexOfLastResearchKey = findLastIndexContainingKeyValue( - LogStatement.TYPE_POINTER_TRACKER_CALL_LISTENER_ON_CODE_INPUT, - LogStatement.KEY_CODE, LogStatement.VALUE_RESEARCH); - if (indexOfLastResearchKey < 0) { - // Could not find invocation of "research" key. Leave log as is. - if (DEBUG) { - Log.d(TAG, "Could not find research key"); - } - return false; - } - - // Look for the long press that started the invocation of the research key code input. - final int indexOfLastLongPressBeforeResearchKey = - findLastIndexBefore(LogStatement.TYPE_MAIN_KEYBOARD_VIEW_ON_LONG_PRESS, - indexOfLastResearchKey); - - // Look for DOWN event preceding the long press - final int indexOfLastDownEventBeforeLongPress = - findLastIndexContainingKeyValueBefore(LogStatement.TYPE_MOTION_EVENT, - LogStatement.ACTION, LogStatement.VALUE_DOWN, - indexOfLastLongPressBeforeResearchKey); - - // Flag all LatinKeyboardViewProcessMotionEvents from the DOWN event to the research key as - // logging-related - final int startingIndex = indexOfLastDownEventBeforeLongPress == -1 ? 0 - : indexOfLastDownEventBeforeLongPress; - for (int index = startingIndex; index < indexOfLastResearchKey; index++) { - final LogStatement logStatement = mLogStatementList.get(index); - final String type = logStatement.getType(); - final Object[] values = mValuesList.get(index); - if (type.equals(LogStatement.TYPE_MOTION_EVENT)) { - logStatement.setValue(LogStatement.KEY_IS_LOGGING_RELATED, values, true); - } - } - return true; - } - - /** - * Find the index of the last LogStatement before {@code startingIndex} of type {@code type}. - * - * @param queryType a String that must be {@code String.equals()} to the LogStatement type - * @param startingIndex the index to start the backward search from. Must be less than the - * length of mLogStatementList, or an IndexOutOfBoundsException is thrown. Can be negative, - * in which case -1 is returned. - * - * @return The index of the last LogStatement, -1 if none exists. - */ - private int findLastIndexBefore(final String queryType, final int startingIndex) { - return findLastIndexContainingKeyValueBefore(queryType, null, null, startingIndex); - } - - /** - * Find the index of the last LogStatement before {@code startingIndex} of type {@code type} - * containing the given key-value pair. - * - * @param queryType a String that must be {@code String.equals()} to the LogStatement type - * @param queryKey a String that must be {@code String.equals()} to a key in the LogStatement - * @param queryValue an Object that must be {@code String.equals()} to the key's corresponding - * value - * - * @return The index of the last LogStatement, -1 if none exists. - */ - private int findLastIndexContainingKeyValue(final String queryType, final String queryKey, - final Object queryValue) { - return findLastIndexContainingKeyValueBefore(queryType, queryKey, queryValue, - mLogStatementList.size() - 1); - } - - /** - * Find the index of the last LogStatement before {@code startingIndex} of type {@code type} - * containing the given key-value pair. - * - * @param queryType a String that must be {@code String.equals()} to the LogStatement type - * @param queryKey a String that must be {@code String.equals()} to a key in the LogStatement - * @param queryValue an Object that must be {@code String.equals()} to the key's corresponding - * value - * @param startingIndex the index to start the backward search from. Must be less than the - * length of mLogStatementList, or an IndexOutOfBoundsException is thrown. Can be negative, - * in which case -1 is returned. - * - * @return The index of the last LogStatement, -1 if none exists. - */ - private int findLastIndexContainingKeyValueBefore(final String queryType, final String queryKey, - final Object queryValue, final int startingIndex) { - if (startingIndex < 0) { - return -1; - } - for (int index = startingIndex; index >= 0; index--) { - final LogStatement logStatement = mLogStatementList.get(index); - final String type = logStatement.getType(); - if (type.equals(queryType) && (queryKey == null - || logStatement.containsKeyValuePair(queryKey, queryValue, - mValuesList.get(index)))) { - return index; - } - } - return -1; - } -} diff --git a/java/src/com/android/inputmethod/research/LoggingUtils.java b/java/src/com/android/inputmethod/research/LoggingUtils.java deleted file mode 100644 index 1261d6780..000000000 --- a/java/src/com/android/inputmethod/research/LoggingUtils.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.view.MotionEvent; - -/* package */ class LoggingUtils { - private LoggingUtils() { - // This utility class is not publicly instantiable. - } - - /* package */ static String getMotionEventActionTypeString(final int actionType) { - switch (actionType) { - case MotionEvent.ACTION_CANCEL: return "CANCEL"; - case MotionEvent.ACTION_UP: return "UP"; - case MotionEvent.ACTION_DOWN: return "DOWN"; - case MotionEvent.ACTION_POINTER_UP: return "POINTER_UP"; - case MotionEvent.ACTION_POINTER_DOWN: return "POINTER_DOWN"; - case MotionEvent.ACTION_MOVE: return "MOVE"; - case MotionEvent.ACTION_OUTSIDE: return "OUTSIDE"; - default: return "ACTION_" + actionType; - } - } -} diff --git a/java/src/com/android/inputmethod/research/MainLogBuffer.java b/java/src/com/android/inputmethod/research/MainLogBuffer.java deleted file mode 100644 index ffdb43c15..000000000 --- a/java/src/com/android/inputmethod/research/MainLogBuffer.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.Dictionary; -import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; -import com.android.inputmethod.latin.define.ProductionFlag; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedList; - -/** - * MainLogBuffer is a FixedLogBuffer that tracks the state of LogUnits to make privacy guarantees. - * - * There are three forms of privacy protection: 1) only words in the main dictionary are allowed to - * be logged in enough detail to determine their contents, 2) only a subset of words are logged - * in detail, such as 10%, and 3) no numbers are logged. - * - * This class maintains a list of LogUnits, each corresponding to a word. As the user completes - * words, they are added here. But if the user backs up over their current word to edit a word - * entered earlier, then it is pulled out of this LogBuffer, changes are then added to the end of - * the LogUnit, and it is pushed back in here when the user is done. Because words may be pulled - * back out even after they are pushed in, we must not publish the contents of this LogBuffer too - * quickly. However, we cannot let the contents pile up either, or it will limit the editing that - * a user can perform. - * - * To balance these requirements (keep history so user can edit, flush history so it does not pile - * up), the LogBuffer is considered "complete" when the user has entered enough words to form an - * n-gram, followed by enough additional non-detailed words (that are in the 90%, as per above). - * Once complete, the n-gram may be published to flash storage (via the ResearchLog class). - * However, the additional non-detailed words are retained, in case the user backspaces to edit - * them. The MainLogBuffer then continues to add words, publishing individual non-detailed words - * as new words arrive. After enough non-detailed words have been pushed out to account for the - * 90% between words, the words at the front of the LogBuffer can be published as an n-gram again. - * - * If the words that would form the valid n-gram are not in the dictionary, then words are pushed - * through the LogBuffer one at a time until an n-gram is found that is entirely composed of - * dictionary words. - * - * If the user closes a session, then the entire LogBuffer is flushed, publishing any embedded - * n-gram containing dictionary words. - */ -public abstract class MainLogBuffer extends FixedLogBuffer { - private static final String TAG = MainLogBuffer.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - - // Keep consistent with switch statement in Statistics.recordPublishabilityResultCode() - public static final int PUBLISHABILITY_PUBLISHABLE = 0; - public static final int PUBLISHABILITY_UNPUBLISHABLE_STOPPING = 1; - public static final int PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT = 2; - public static final int PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY = 3; - public static final int PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE = 4; - public static final int PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT = 5; - public static final int PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY = 6; - - // The size of the n-grams logged. E.g. N_GRAM_SIZE = 2 means to sample bigrams. - public static final int N_GRAM_SIZE = 2; - - private final DictionaryFacilitatorForSuggest mDictionaryFacilitator; - @UsedForTesting - private Dictionary mDictionaryForTesting; - private boolean mIsStopping = false; - - /* package for test */ int mNumWordsBetweenNGrams; - - // Counter for words left to suppress before an n-gram can be sampled. Reset to mMinWordPeriod - // after a sample is taken. - /* package for test */ int mNumWordsUntilSafeToSample; - - public MainLogBuffer(final int wordsBetweenSamples, final int numInitialWordsToIgnore, - final DictionaryFacilitatorForSuggest dictionaryFacilitator) { - super(N_GRAM_SIZE + wordsBetweenSamples); - mNumWordsBetweenNGrams = wordsBetweenSamples; - mNumWordsUntilSafeToSample = DEBUG ? 0 : numInitialWordsToIgnore; - mDictionaryFacilitator = dictionaryFacilitator; - } - - @UsedForTesting - /* package for test */ void setDictionaryForTesting(final Dictionary dictionary) { - mDictionaryForTesting = dictionary; - } - - private boolean isValidDictWord(final String word) { - if (mDictionaryForTesting != null) { - return mDictionaryForTesting.isValidWord(word); - } - if (mDictionaryFacilitator != null) { - return mDictionaryFacilitator.isValidMainDictWord(word); - } - return false; - } - - public void setIsStopping() { - mIsStopping = true; - } - - /** - * Determines whether the string determined by a series of LogUnits will not violate user - * privacy if published. - * - * @param logUnits a LogUnit list to check for publishability - * @param nGramSize the smallest n-gram acceptable to be published. if - * {@link ResearchLogger#IS_LOGGING_EVERYTHING} is true, then publish if there are more than - * {@code minNGramSize} words in the logUnits, otherwise wait. if {@link - * ResearchLogger#IS_LOGGING_EVERYTHING} is false, then ensure that there are exactly nGramSize - * words in the LogUnits. - * - * @return one of the {@code PUBLISHABILITY_*} result codes defined in this class. - */ - private int getPublishabilityResultCode(final ArrayList<LogUnit> logUnits, - final int nGramSize) { - // Bypass privacy checks when debugging. - if (ResearchLogger.IS_LOGGING_EVERYTHING) { - if (mIsStopping) { - return PUBLISHABILITY_UNPUBLISHABLE_STOPPING; - } - // Only check that it is the right length. If not, wait for later words to make - // complete n-grams. - int numWordsInLogUnitList = 0; - final int length = logUnits.size(); - for (int i = 0; i < length; i++) { - final LogUnit logUnit = logUnits.get(i); - numWordsInLogUnitList += logUnit.getNumWords(); - } - if (numWordsInLogUnitList >= nGramSize) { - return PUBLISHABILITY_PUBLISHABLE; - } else { - return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT; - } - } - - // Check that we are not sampling too frequently. Having sampled recently might disclose - // too much of the user's intended meaning. - if (mNumWordsUntilSafeToSample > 0) { - return PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY; - } - // Reload the dictionary in case it has changed (e.g., because the user has changed - // languages). - if ((mDictionaryFacilitator == null - || !mDictionaryFacilitator.hasInitializedMainDictionary()) - && mDictionaryForTesting == null) { - // Main dictionary is unavailable. Since we cannot check it, we cannot tell if a - // word is out-of-vocabulary or not. Therefore, we must judge the entire buffer - // contents to potentially pose a privacy risk. - return PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE; - } - - // Check each word in the buffer. If any word poses a privacy threat, we cannot upload - // the complete buffer contents in detail. - int numWordsInLogUnitList = 0; - for (final LogUnit logUnit : logUnits) { - if (!logUnit.hasOneOrMoreWords()) { - // Digits outside words are a privacy threat. - if (logUnit.mayContainDigit()) { - return PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT; - } - } else { - numWordsInLogUnitList += logUnit.getNumWords(); - final String[] words = logUnit.getWordsAsStringArray(); - for (final String word : words) { - // Words not in the dictionary are a privacy threat. - if (ResearchLogger.hasLetters(word) && !isValidDictWord(word)) { - if (DEBUG) { - Log.d(TAG, "\"" + word + "\" NOT SAFE!: hasLetters: " - + ResearchLogger.hasLetters(word) - + ", isValid: " + isValidDictWord(word)); - } - return PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY; - } - } - } - } - - // Finally, only return true if the ngram is the right size. - if (numWordsInLogUnitList == nGramSize) { - return PUBLISHABILITY_PUBLISHABLE; - } else { - return PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT; - } - } - - public void shiftAndPublishAll() throws IOException { - final LinkedList<LogUnit> logUnits = getLogUnits(); - while (!logUnits.isEmpty()) { - publishLogUnitsAtFrontOfBuffer(); - } - } - - @Override - protected final void onBufferFull() { - try { - publishLogUnitsAtFrontOfBuffer(); - } catch (final IOException e) { - if (DEBUG) { - Log.w(TAG, "IOException when publishing front of LogBuffer", e); - } - } - } - - /** - * If there is a safe n-gram at the front of this log buffer, publish it with all details, and - * remove the LogUnits that constitute it. - * - * An n-gram might not be "safe" if it violates privacy controls. E.g., it might contain - * numbers, an out-of-vocabulary word, or another n-gram may have been published recently. If - * there is no safe n-gram, then the LogUnits up through the first word-containing LogUnit are - * published, but without disclosing any privacy-related details, such as the word the LogUnit - * generated, motion data, etc. - * - * Note that a LogUnit can hold more than one word if the user types without explicit spaces. - * In this case, the words may be grouped together in such a way that pulling an n-gram off the - * front would require splitting a LogUnit. Splitting a LogUnit is not possible, so this case - * is treated just as the unsafe n-gram case. This may cause n-grams to be sampled at slightly - * less than the target frequency. - */ - protected final void publishLogUnitsAtFrontOfBuffer() throws IOException { - // TODO: Refactor this method to require fewer passes through the LogUnits. Should really - // require only one pass. - ArrayList<LogUnit> logUnits = peekAtFirstNWords(N_GRAM_SIZE); - final int publishabilityResultCode = getPublishabilityResultCode(logUnits, N_GRAM_SIZE); - ResearchLogger.recordPublishabilityResultCode(publishabilityResultCode); - if (publishabilityResultCode == MainLogBuffer.PUBLISHABILITY_PUBLISHABLE) { - // Good n-gram at the front of the buffer. Publish it, disclosing details. - publish(logUnits, true /* canIncludePrivateData */); - shiftOutWords(N_GRAM_SIZE); - mNumWordsUntilSafeToSample = mNumWordsBetweenNGrams; - 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; - } - logUnit = shiftOut(); - } - publish(logUnits, false /* canIncludePrivateData */); - } - - /** - * Called when a list of logUnits should be published. - * - * It is the subclass's responsibility to implement the publication. - * - * @param logUnits The list of logUnits to be published. - * @param canIncludePrivateData Whether the private data in the logUnits can be included in - * publication. - * - * @throws IOException if publication to the log file is not possible - */ - protected abstract void publish(final ArrayList<LogUnit> logUnits, - final boolean canIncludePrivateData) throws IOException; - - @Override - protected int shiftOutWords(final int numWords) { - final int numWordsShiftedOut = super.shiftOutWords(numWords); - mNumWordsUntilSafeToSample = Math.max(0, mNumWordsUntilSafeToSample - numWordsShiftedOut); - if (DEBUG) { - Log.d(TAG, "wordsUntilSafeToSample now at " + mNumWordsUntilSafeToSample); - } - return numWordsShiftedOut; - } -} diff --git a/java/src/com/android/inputmethod/research/MotionEventReader.java b/java/src/com/android/inputmethod/research/MotionEventReader.java deleted file mode 100644 index 3388645b7..000000000 --- a/java/src/com/android/inputmethod/research/MotionEventReader.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.research; - -import android.util.JsonReader; -import android.util.Log; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.define.ProductionFlag; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; - -public class MotionEventReader { - private static final String TAG = MotionEventReader.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - // Assumes that MotionEvent.ACTION_MASK does not have all bits set.` - private static final int UNINITIALIZED_ACTION = ~MotionEvent.ACTION_MASK; - // No legitimate int is negative - private static final int UNINITIALIZED_INT = -1; - // No legitimate long is negative - private static final long UNINITIALIZED_LONG = -1L; - // No legitimate float is negative - private static final float UNINITIALIZED_FLOAT = -1.0f; - - public ReplayData readMotionEventData(final File file) { - final ReplayData replayData = new ReplayData(); - try { - // Read file - final JsonReader jsonReader = new JsonReader(new BufferedReader(new InputStreamReader( - new FileInputStream(file)))); - jsonReader.beginArray(); - while (jsonReader.hasNext()) { - readLogStatement(jsonReader, replayData); - } - jsonReader.endArray(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - return replayData; - } - - @UsedForTesting - static class ReplayData { - final ArrayList<Integer> mActions = new ArrayList<Integer>(); - final ArrayList<PointerProperties[]> mPointerPropertiesArrays - = new ArrayList<PointerProperties[]>(); - final ArrayList<PointerCoords[]> mPointerCoordsArrays = new ArrayList<PointerCoords[]>(); - final ArrayList<Long> mTimes = new ArrayList<Long>(); - } - - /** - * Read motion data from a logStatement and store it in {@code replayData}. - * - * Two kinds of logStatements can be read. In the first variant, the MotionEvent data is - * represented as attributes at the top level like so: - * - * <pre> - * { - * "_ct": 1359590400000, - * "_ut": 4381933, - * "_ty": "MotionEvent", - * "action": "UP", - * "isLoggingRelated": false, - * "x": 100, - * "y": 200 - * } - * </pre> - * - * In the second variant, there is a separate attribute for the MotionEvent that includes - * historical data if present: - * - * <pre> - * { - * "_ct": 135959040000, - * "_ut": 4382702, - * "_ty": "MotionEvent", - * "action": "MOVE", - * "isLoggingRelated": false, - * "motionEvent": { - * "pointerIds": [ - * 0 - * ], - * "xyt": [ - * { - * "t": 4382551, - * "d": [ - * { - * "x": 141.25, - * "y": 151.8485107421875, - * "toma": 101.82337188720703, - * "tomi": 101.82337188720703, - * "o": 0.0 - * } - * ] - * }, - * { - * "t": 4382559, - * "d": [ - * { - * "x": 140.7266082763672, - * "y": 151.8485107421875, - * "toma": 101.82337188720703, - * "tomi": 101.82337188720703, - * "o": 0.0 - * } - * ] - * } - * ] - * } - * }, - * </pre> - */ - @UsedForTesting - /* package for test */ void readLogStatement(final JsonReader jsonReader, - final ReplayData replayData) throws IOException { - String logStatementType = null; - int actionType = UNINITIALIZED_ACTION; - int x = UNINITIALIZED_INT; - int y = UNINITIALIZED_INT; - long time = UNINITIALIZED_LONG; - boolean isLoggingRelated = false; - - jsonReader.beginObject(); - while (jsonReader.hasNext()) { - final String key = jsonReader.nextName(); - if (key.equals("_ty")) { - logStatementType = jsonReader.nextString(); - } else if (key.equals("_ut")) { - time = jsonReader.nextLong(); - } else if (key.equals("x")) { - x = jsonReader.nextInt(); - } else if (key.equals("y")) { - y = jsonReader.nextInt(); - } else if (key.equals("action")) { - final String s = jsonReader.nextString(); - if (s.equals("UP")) { - actionType = MotionEvent.ACTION_UP; - } else if (s.equals("DOWN")) { - actionType = MotionEvent.ACTION_DOWN; - } else if (s.equals("MOVE")) { - actionType = MotionEvent.ACTION_MOVE; - } - } else if (key.equals("loggingRelated")) { - isLoggingRelated = jsonReader.nextBoolean(); - } else if (logStatementType != null && logStatementType.equals("MotionEvent") - && key.equals("motionEvent")) { - if (actionType == UNINITIALIZED_ACTION) { - Log.e(TAG, "no actionType assigned in MotionEvent json"); - } - // Second variant of LogStatement. - if (isLoggingRelated) { - jsonReader.skipValue(); - } else { - readEmbeddedMotionEvent(jsonReader, replayData, actionType); - } - } else { - if (DEBUG) { - Log.w(TAG, "Unknown JSON key in LogStatement: " + key); - } - jsonReader.skipValue(); - } - } - jsonReader.endObject(); - - if (logStatementType != null && time != UNINITIALIZED_LONG && x != UNINITIALIZED_INT - && y != UNINITIALIZED_INT && actionType != UNINITIALIZED_ACTION - && logStatementType.equals("MotionEvent") && !isLoggingRelated) { - // First variant of LogStatement. - final PointerProperties pointerProperties = new PointerProperties(); - pointerProperties.id = 0; - pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN; - final PointerProperties[] pointerPropertiesArray = { - pointerProperties - }; - final PointerCoords pointerCoords = new PointerCoords(); - pointerCoords.x = x; - pointerCoords.y = y; - pointerCoords.pressure = 1.0f; - pointerCoords.size = 1.0f; - final PointerCoords[] pointerCoordsArray = { - pointerCoords - }; - addMotionEventData(replayData, actionType, time, pointerPropertiesArray, - pointerCoordsArray); - } - } - - private void readEmbeddedMotionEvent(final JsonReader jsonReader, final ReplayData replayData, - final int actionType) throws IOException { - jsonReader.beginObject(); - PointerProperties[] pointerPropertiesArray = null; - while (jsonReader.hasNext()) { // pointerIds/xyt - final String name = jsonReader.nextName(); - if (name.equals("pointerIds")) { - pointerPropertiesArray = readPointerProperties(jsonReader); - } else if (name.equals("xyt")) { - readPointerData(jsonReader, replayData, actionType, pointerPropertiesArray); - } - } - jsonReader.endObject(); - } - - private PointerProperties[] readPointerProperties(final JsonReader jsonReader) - throws IOException { - final ArrayList<PointerProperties> pointerPropertiesArrayList = - new ArrayList<PointerProperties>(); - jsonReader.beginArray(); - while (jsonReader.hasNext()) { - final PointerProperties pointerProperties = new PointerProperties(); - pointerProperties.id = jsonReader.nextInt(); - pointerProperties.toolType = MotionEvent.TOOL_TYPE_UNKNOWN; - pointerPropertiesArrayList.add(pointerProperties); - } - jsonReader.endArray(); - return pointerPropertiesArrayList.toArray( - new PointerProperties[pointerPropertiesArrayList.size()]); - } - - private void readPointerData(final JsonReader jsonReader, final ReplayData replayData, - final int actionType, final PointerProperties[] pointerPropertiesArray) - throws IOException { - if (pointerPropertiesArray == null) { - Log.e(TAG, "PointerIDs must be given before xyt data in json for MotionEvent"); - jsonReader.skipValue(); - return; - } - long time = UNINITIALIZED_LONG; - jsonReader.beginArray(); - while (jsonReader.hasNext()) { // Array of historical data - jsonReader.beginObject(); - final ArrayList<PointerCoords> pointerCoordsArrayList = new ArrayList<PointerCoords>(); - while (jsonReader.hasNext()) { // Time/data object - final String name = jsonReader.nextName(); - if (name.equals("t")) { - time = jsonReader.nextLong(); - } else if (name.equals("d")) { - jsonReader.beginArray(); - while (jsonReader.hasNext()) { // array of data per pointer - final PointerCoords pointerCoords = readPointerCoords(jsonReader); - if (pointerCoords != null) { - pointerCoordsArrayList.add(pointerCoords); - } - } - jsonReader.endArray(); - } else { - jsonReader.skipValue(); - } - } - jsonReader.endObject(); - // Data was recorded as historical events, but must be split apart into - // separate MotionEvents for replaying - if (time != UNINITIALIZED_LONG) { - addMotionEventData(replayData, actionType, time, pointerPropertiesArray, - pointerCoordsArrayList.toArray( - new PointerCoords[pointerCoordsArrayList.size()])); - } else { - Log.e(TAG, "Time not assigned in json for MotionEvent"); - } - } - jsonReader.endArray(); - } - - private PointerCoords readPointerCoords(final JsonReader jsonReader) throws IOException { - jsonReader.beginObject(); - float x = UNINITIALIZED_FLOAT; - float y = UNINITIALIZED_FLOAT; - while (jsonReader.hasNext()) { // x,y - final String name = jsonReader.nextName(); - if (name.equals("x")) { - x = (float) jsonReader.nextDouble(); - } else if (name.equals("y")) { - y = (float) jsonReader.nextDouble(); - } else { - jsonReader.skipValue(); - } - } - jsonReader.endObject(); - - if (Float.compare(x, UNINITIALIZED_FLOAT) == 0 - || Float.compare(y, UNINITIALIZED_FLOAT) == 0) { - Log.w(TAG, "missing x or y value in MotionEvent json"); - return null; - } - final PointerCoords pointerCoords = new PointerCoords(); - pointerCoords.x = x; - pointerCoords.y = y; - pointerCoords.pressure = 1.0f; - pointerCoords.size = 1.0f; - return pointerCoords; - } - - private void addMotionEventData(final ReplayData replayData, final int actionType, - final long time, final PointerProperties[] pointerProperties, - final PointerCoords[] pointerCoords) { - replayData.mActions.add(actionType); - replayData.mTimes.add(time); - replayData.mPointerPropertiesArrays.add(pointerProperties); - replayData.mPointerCoordsArrays.add(pointerCoords); - } -} diff --git a/java/src/com/android/inputmethod/research/Replayer.java b/java/src/com/android/inputmethod/research/Replayer.java deleted file mode 100644 index 903875f3c..000000000 --- a/java/src/com/android/inputmethod/research/Replayer.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.os.SystemClock; -import android.util.Log; -import android.view.MotionEvent; -import android.view.MotionEvent.PointerCoords; -import android.view.MotionEvent.PointerProperties; - -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.latin.define.ProductionFlag; -import com.android.inputmethod.research.MotionEventReader.ReplayData; - -/** - * Replays a sequence of motion events in realtime on the screen. - * - * Useful for user inspection of logged data. - */ -public class Replayer { - private static final String TAG = Replayer.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - private static final long START_TIME_DELAY_MS = 500; - - private boolean mIsReplaying = false; - private KeyboardSwitcher mKeyboardSwitcher; - - private Replayer() { - } - - private static final Replayer sInstance = new Replayer(); - public static Replayer getInstance() { - return sInstance; - } - - public void setKeyboardSwitcher(final KeyboardSwitcher keyboardSwitcher) { - mKeyboardSwitcher = keyboardSwitcher; - } - - private static final int MSG_MOTION_EVENT = 0; - private static final int MSG_DONE = 1; - private static final int COMPLETION_TIME_MS = 500; - - // TODO: Support historical events and multi-touch. - public void replay(final ReplayData replayData, final Runnable callback) { - if (mIsReplaying) { - return; - } - mIsReplaying = true; - final int numActions = replayData.mActions.size(); - if (DEBUG) { - Log.d(TAG, "replaying " + numActions + " actions"); - } - if (numActions == 0) { - mIsReplaying = false; - return; - } - final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); - - // The reference time relative to the times stored in events. - final long origStartTime = replayData.mTimes.get(0); - // The reference time relative to which events are replayed in the present. - final long currentStartTime = SystemClock.uptimeMillis() + START_TIME_DELAY_MS; - // The adjustment needed to translate times from the original recorded time to the current - // time. - final long timeAdjustment = currentStartTime - origStartTime; - final Handler handler = new Handler(Looper.getMainLooper()) { - // Track the time of the most recent DOWN event, to be passed as a parameter when - // constructing a MotionEvent. It's initialized here to the origStartTime, but this is - // only a precaution. The value should be overwritten by the first ACTION_DOWN event - // before the first use of the variable. Note that this may cause the first few events - // to have incorrect {@code downTime}s. - private long mOrigDownTime = origStartTime; - - @Override - public void handleMessage(final Message msg) { - switch (msg.what) { - case MSG_MOTION_EVENT: - final int index = msg.arg1; - final int action = replayData.mActions.get(index); - final PointerProperties[] pointerPropertiesArray = - replayData.mPointerPropertiesArrays.get(index); - final PointerCoords[] pointerCoordsArray = - replayData.mPointerCoordsArrays.get(index); - final long origTime = replayData.mTimes.get(index); - if (action == MotionEvent.ACTION_DOWN) { - mOrigDownTime = origTime; - } - - final MotionEvent me = MotionEvent.obtain(mOrigDownTime + timeAdjustment, - origTime + timeAdjustment, action, - pointerPropertiesArray.length, pointerPropertiesArray, - pointerCoordsArray, 0, 0, 1.0f, 1.0f, 0, 0, 0, 0); - mainKeyboardView.processMotionEvent(me); - me.recycle(); - break; - case MSG_DONE: - mIsReplaying = false; - ResearchLogger.getInstance().requestIndicatorRedraw(); - break; - } - } - }; - - handler.post(new Runnable() { - @Override - public void run() { - ResearchLogger.getInstance().requestIndicatorRedraw(); - } - }); - for (int i = 0; i < numActions; i++) { - final Message msg = Message.obtain(handler, MSG_MOTION_EVENT, i, 0); - final long msgTime = replayData.mTimes.get(i) + timeAdjustment; - handler.sendMessageAtTime(msg, msgTime); - if (DEBUG) { - Log.d(TAG, "queuing event at " + msgTime); - } - } - - final long presentDoneTime = replayData.mTimes.get(numActions - 1) + timeAdjustment - + COMPLETION_TIME_MS; - handler.sendMessageAtTime(Message.obtain(handler, MSG_DONE), presentDoneTime); - if (callback != null) { - handler.postAtTime(callback, presentDoneTime + 1); - } - } - - public boolean isReplaying() { - return mIsReplaying; - } -} diff --git a/java/src/com/android/inputmethod/research/ReplayerService.java b/java/src/com/android/inputmethod/research/ReplayerService.java deleted file mode 100644 index 88d9033cf..000000000 --- a/java/src/com/android/inputmethod/research/ReplayerService.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - -package com.android.inputmethod.research; - -import android.app.IntentService; -import android.content.Intent; -import android.util.Log; - -import com.android.inputmethod.research.MotionEventReader.ReplayData; - -import java.io.File; -import java.util.concurrent.TimeUnit; - -/** - * Provide a mechanism to invoke the replayer from outside. - * - * In particular, makes access from a host possible through {@code adb am startservice}. - */ -public class ReplayerService extends IntentService { - private static final String TAG = ReplayerService.class.getSimpleName(); - private static final String EXTRA_FILENAME = "com.android.inputmethod.research.extra.FILENAME"; - private static final long MAX_REPLAY_TIME = TimeUnit.SECONDS.toMillis(60); - - public ReplayerService() { - super(ReplayerService.class.getSimpleName()); - } - - @Override - protected void onHandleIntent(final Intent intent) { - final String filename = intent.getStringExtra(EXTRA_FILENAME); - if (filename == null) return; - - final ReplayData replayData = new MotionEventReader().readMotionEventData( - new File(filename)); - synchronized (this) { - Replayer.getInstance().replay(replayData, new Runnable() { - @Override - public void run() { - synchronized (ReplayerService.this) { - ReplayerService.this.notify(); - } - } - }); - try { - wait(MAX_REPLAY_TIME); - } catch (InterruptedException e) { - Log.e(TAG, "Timeout while replaying.", e); - } - } - } -} diff --git a/java/src/com/android/inputmethod/research/ResearchLog.java b/java/src/com/android/inputmethod/research/ResearchLog.java deleted file mode 100644 index 46e620ae5..000000000 --- a/java/src/com/android/inputmethod/research/ResearchLog.java +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.content.Context; -import android.util.JsonWriter; -import android.util.Log; - -import com.android.inputmethod.annotations.UsedForTesting; -import com.android.inputmethod.latin.define.ProductionFlag; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -/** - * Logs the use of the LatinIME keyboard. - * - * This class logs operations on the IME keyboard, including what the user has typed. Data is - * written to a {@link JsonWriter}, which will write to a local file. - * - * The JsonWriter is created on-demand by calling {@link #getInitializedJsonWriterLocked}. - * - * This class uses an executor to perform file-writing operations on a separate thread. It also - * tries to avoid creating unnecessary files if there is nothing to write. It also handles - * flushing, making sure it happens, but not too frequently. - * - * This functionality is off by default. See - * {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}. - */ -public class ResearchLog { - // TODO: Automatically initialize the JsonWriter rather than requiring the caller to manage it. - private static final String TAG = ResearchLog.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - private static final long FLUSH_DELAY_IN_MS = TimeUnit.SECONDS.toMillis(5); - - /* package */ final ScheduledExecutorService mExecutor; - /* package */ final File mFile; - private final Context mContext; - - // Earlier implementations used a dummy JsonWriter that just swallowed what it was given, but - // this was tricky to do well, because JsonWriter throws an exception if it is passed more than - // one top-level object. - private JsonWriter mJsonWriter = null; - - // true if at least one byte of data has been written out to the log file. This must be - // remembered because JsonWriter requires that calls matching calls to beginObject and - // endObject, as well as beginArray and endArray, and the file is opened lazily, only when - // it is certain that data will be written. Alternatively, the matching call exceptions - // could be caught, but this might suppress other errors. - private boolean mHasWrittenData = false; - - public ResearchLog(final File outputFile, final Context context) { - mExecutor = Executors.newSingleThreadScheduledExecutor(); - mFile = outputFile; - mContext = context; - } - - /** - * Returns true if this is a FeedbackLog. - * - * FeedbackLogs record only the data associated with a Feedback dialog. Instead of normal - * logging, they contain a LogStatement with the complete feedback string and optionally a - * recording of the user's supplied demo of the problem. - */ - public boolean isFeedbackLog() { - return false; - } - - /** - * Waits for any publication requests to finish and closes the {@link JsonWriter} used for - * output. - * - * See class comment for details about {@code JsonWriter} construction. - * - * @param onClosed run after the close() operation has completed asynchronously - */ - private synchronized void close(final Runnable onClosed) { - mExecutor.submit(new Callable<Object>() { - @Override - public Object call() throws Exception { - try { - if (mJsonWriter == null) return null; - // TODO: This is necessary to avoid an exception. Better would be to not even - // open the JsonWriter if the file is not even opened unless there is valid data - // to write. - if (!mHasWrittenData) { - mJsonWriter.beginArray(); - } - mJsonWriter.endArray(); - mHasWrittenData = false; - mJsonWriter.flush(); - mJsonWriter.close(); - if (DEBUG) { - Log.d(TAG, "closed " + mFile); - } - } catch (final Exception e) { - Log.d(TAG, "error when closing ResearchLog:", e); - } finally { - // Marking the file as read-only signals that this log file is ready to be - // uploaded. - if (mFile != null && mFile.exists()) { - mFile.setWritable(false, false); - } - if (onClosed != null) { - onClosed.run(); - } - } - return null; - } - }); - removeAnyScheduledFlush(); - mExecutor.shutdown(); - } - - /** - * Block until the research log has shut down and spooled out all output or {@code timeout} - * occurs. - * - * @param timeout time to wait for close in milliseconds - */ - public void blockingClose(final long timeout) { - close(null); - awaitTermination(timeout, TimeUnit.MILLISECONDS); - } - - /** - * Waits for publication requests to finish, closes the JsonWriter, but then deletes the backing - * output file. - * - * @param onAbort run after the abort() operation has completed asynchronously - */ - private synchronized void abort(final Runnable onAbort) { - mExecutor.submit(new Callable<Object>() { - @Override - public Object call() throws Exception { - try { - if (mJsonWriter == null) return null; - if (mHasWrittenData) { - // TODO: This is necessary to avoid an exception. Better would be to not - // even open the JsonWriter if the file is not even opened unless there is - // valid data to write. - if (!mHasWrittenData) { - mJsonWriter.beginArray(); - } - mJsonWriter.endArray(); - mJsonWriter.close(); - mHasWrittenData = false; - } - } finally { - if (mFile != null) { - mFile.delete(); - } - if (onAbort != null) { - onAbort.run(); - } - } - return null; - } - }); - removeAnyScheduledFlush(); - mExecutor.shutdown(); - } - - /** - * Block until the research log has aborted or {@code timeout} occurs. - * - * @param timeout time to wait for close in milliseconds - */ - public void blockingAbort(final long timeout) { - abort(null); - awaitTermination(timeout, TimeUnit.MILLISECONDS); - } - - @UsedForTesting - public void awaitTermination(final long delay, final TimeUnit timeUnit) { - try { - if (!mExecutor.awaitTermination(delay, timeUnit)) { - Log.e(TAG, "ResearchLog executor timed out while awaiting terminaion"); - } - } catch (final InterruptedException e) { - Log.e(TAG, "ResearchLog executor interrupted while awaiting terminaion", e); - } - } - - /* package */ synchronized void flush() { - removeAnyScheduledFlush(); - mExecutor.submit(mFlushCallable); - } - - private final Callable<Object> mFlushCallable = new Callable<Object>() { - @Override - public Object call() throws Exception { - if (mJsonWriter != null) mJsonWriter.flush(); - return null; - } - }; - - private ScheduledFuture<Object> mFlushFuture; - - private void removeAnyScheduledFlush() { - if (mFlushFuture != null) { - mFlushFuture.cancel(false); - mFlushFuture = null; - } - } - - private void scheduleFlush() { - removeAnyScheduledFlush(); - mFlushFuture = mExecutor.schedule(mFlushCallable, FLUSH_DELAY_IN_MS, TimeUnit.MILLISECONDS); - } - - /** - * Queues up {@code logUnit} to be published in the background. - * - * @param logUnit the {@link LogUnit} to be published - * @param canIncludePrivateData whether private data in the LogUnit should be included - */ - public synchronized void publish(final LogUnit logUnit, final boolean canIncludePrivateData) { - try { - mExecutor.submit(new Callable<Object>() { - @Override - public Object call() throws Exception { - logUnit.publishTo(ResearchLog.this, canIncludePrivateData); - scheduleFlush(); - return null; - } - }); - } catch (final RejectedExecutionException e) { - // TODO: Add code to record loss of data, and report. - if (DEBUG) { - Log.d(TAG, "ResearchLog.publish() rejecting scheduled execution", e); - } - } - } - - /** - * Return a JsonWriter for this ResearchLog. It is initialized the first time this method is - * called. The cached value is returned in future calls. - * - * @throws IOException if opening the JsonWriter is not possible - */ - public JsonWriter getInitializedJsonWriterLocked() throws IOException { - if (mJsonWriter != null) return mJsonWriter; - if (mFile == null) throw new FileNotFoundException(); - try { - final JsonWriter jsonWriter = createJsonWriter(mContext, mFile); - if (jsonWriter == null) throw new IOException("Could not create JsonWriter"); - - jsonWriter.beginArray(); - mJsonWriter = jsonWriter; - mHasWrittenData = true; - return mJsonWriter; - } catch (final IOException e) { - if (DEBUG) { - Log.w(TAG, "Exception when creating JsonWriter", e); - Log.w(TAG, "Closing JsonWriter"); - } - if (mJsonWriter != null) mJsonWriter.close(); - mJsonWriter = null; - throw e; - } - } - - /** - * Create the JsonWriter to write the ResearchLog to. - * - * This method may be overriden in testing to redirect the output. - */ - /* package for test */ JsonWriter createJsonWriter(final Context context, final File file) - throws IOException { - return new JsonWriter(new BufferedWriter(new OutputStreamWriter( - context.openFileOutput(file.getName(), Context.MODE_PRIVATE)))); - } -} diff --git a/java/src/com/android/inputmethod/research/ResearchLogDirectory.java b/java/src/com/android/inputmethod/research/ResearchLogDirectory.java deleted file mode 100644 index d156068d6..000000000 --- a/java/src/com/android/inputmethod/research/ResearchLogDirectory.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.content.Context; -import android.util.Log; - -import java.io.File; -import java.io.FileFilter; - -/** - * Manages log files. - * - * This class handles all aspects where and how research log data is stored. This includes - * generating log filenames in the correct place with the correct names, and cleaning up log files - * under this directory. - */ -public class ResearchLogDirectory { - public static final String TAG = ResearchLogDirectory.class.getSimpleName(); - /* package */ static final String LOG_FILENAME_PREFIX = "researchLog"; - private static final String FILENAME_SUFFIX = ".txt"; - private static final String USER_RECORDING_FILENAME_PREFIX = "recording"; - - private static final ReadOnlyLogFileFilter sUploadableLogFileFilter = - new ReadOnlyLogFileFilter(); - - private final File mFilesDir; - - static class ReadOnlyLogFileFilter implements FileFilter { - @Override - public boolean accept(final File pathname) { - return pathname.getName().startsWith(ResearchLogDirectory.LOG_FILENAME_PREFIX) - && !pathname.canWrite(); - } - } - - /** - * Creates a new ResearchLogDirectory, creating the storage directory if it does not exist. - */ - public ResearchLogDirectory(final Context context) { - mFilesDir = getLoggingDirectory(context); - if (mFilesDir == null) { - throw new NullPointerException("No files directory specified"); - } - if (!mFilesDir.exists()) { - mFilesDir.mkdirs(); - } - } - - private File getLoggingDirectory(final Context context) { - // TODO: Switch to using a subdirectory of getFilesDir(). - return context.getFilesDir(); - } - - /** - * Get an array of log files that are ready for uploading. - * - * A file is ready for uploading if it is marked as read-only. - * - * @return the array of uploadable files - */ - public File[] getUploadableLogFiles() { - try { - return mFilesDir.listFiles(sUploadableLogFileFilter); - } catch (final SecurityException e) { - Log.e(TAG, "Could not cleanup log directory, permission denied", e); - return new File[0]; - } - } - - public void cleanupLogFilesOlderThan(final long time) { - try { - for (final File file : mFilesDir.listFiles()) { - final String filename = file.getName(); - if ((filename.startsWith(LOG_FILENAME_PREFIX) - || filename.startsWith(USER_RECORDING_FILENAME_PREFIX)) - && (file.lastModified() < time)) { - file.delete(); - } - } - } catch (final SecurityException e) { - Log.e(TAG, "Could not cleanup log directory, permission denied", e); - } - } - - public File getLogFilePath(final long time, final long nanoTime) { - return new File(mFilesDir, getUniqueFilename(LOG_FILENAME_PREFIX, time, nanoTime)); - } - - public File getUserRecordingFilePath(final long time, final long nanoTime) { - return new File(mFilesDir, getUniqueFilename(USER_RECORDING_FILENAME_PREFIX, time, - nanoTime)); - } - - private static String getUniqueFilename(final String prefix, final long time, - final long nanoTime) { - return prefix + "-" + time + "-" + nanoTime + FILENAME_SUFFIX; - } -} diff --git a/java/src/com/android/inputmethod/research/ResearchLogger.java b/java/src/com/android/inputmethod/research/ResearchLogger.java deleted file mode 100644 index d907dd1b0..000000000 --- a/java/src/com/android/inputmethod/research/ResearchLogger.java +++ /dev/null @@ -1,1885 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Style; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.SystemClock; -import android.preference.PreferenceManager; -import android.text.TextUtils; -import android.util.Log; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -import android.widget.Toast; - -import com.android.inputmethod.keyboard.Key; -import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardId; -import com.android.inputmethod.keyboard.KeyboardSwitcher; -import com.android.inputmethod.keyboard.KeyboardView; -import com.android.inputmethod.keyboard.MainKeyboardView; -import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.DictionaryFacilitatorForSuggest; -import com.android.inputmethod.latin.LatinIME; -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.RichInputConnection; -import com.android.inputmethod.latin.SuggestedWords; -import com.android.inputmethod.latin.define.ProductionFlag; -import com.android.inputmethod.latin.utils.InputTypeUtils; -import com.android.inputmethod.latin.utils.StringUtils; -import com.android.inputmethod.latin.utils.TextRange; -import com.android.inputmethod.research.MotionEventReader.ReplayData; -import com.android.inputmethod.research.ui.SplashScreen; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; - -// TODO: Add a unit test for every "logging" method (i.e. that is called from the IME and calls -// enqueueEvent to record a LogStatement). -/** - * Logs the use of the LatinIME keyboard. - * - * This class logs operations on the IME keyboard, including what the user has typed. - * Data is stored locally in a file in app-specific storage. - * - * This functionality is off by default. See - * {@link ProductionFlag#USES_DEVELOPMENT_ONLY_DIAGNOSTICS}. - */ -public class ResearchLogger implements SharedPreferences.OnSharedPreferenceChangeListener, - SplashScreen.UserConsentListener { - // TODO: This class has grown quite large and combines several concerns that should be - // separated. The following refactorings will be applied as soon as possible after adding - // support for replaying historical events, fixing some replay bugs, adding some ui constraints - // on the feedback dialog, and adding the survey dialog. - // TODO: Refactor. Move feedback screen code into separate class. - // TODO: Refactor. Move logging invocations into their own class. - // TODO: Refactor. Move currentLogUnit management into separate class. - private static final String TAG = ResearchLogger.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - private static final boolean DEBUG_REPLAY_AFTER_FEEDBACK = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - // Whether the feedback dialog preserves the editable text across invocations. Should be false - // for normal research builds so users do not have to delete the same feedback string they - // entered earlier. Should be true for builds internal to a development team so when the text - // field holds a channel name, the developer does not have to re-enter it when using the - // feedback mechanism to generate multiple tests. - private static final boolean FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD = false; - /* package */ static boolean sIsLogging = false; - private static final int OUTPUT_FORMAT_VERSION = 6; - // Whether all words should be recorded, leaving unsampled word between bigrams. Useful for - // testing. - /* package for test */ static final boolean IS_LOGGING_EVERYTHING = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - // The number of words between n-grams to omit from the log. - private static final int NUMBER_OF_WORDS_BETWEEN_SAMPLES = - IS_LOGGING_EVERYTHING ? 0 : (DEBUG ? 2 : 18); - - // Whether to show an indicator on the screen that logging is on. Currently a very small red - // dot in the lower right hand corner. Most users should not notice it. - private static final boolean IS_SHOWING_INDICATOR = true; - // Change the default indicator to something very visible. Currently two red vertical bars on - // either side of they keyboard. - private static final boolean IS_SHOWING_INDICATOR_CLEARLY = false || - (IS_LOGGING_EVERYTHING && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG); - // FEEDBACK_WORD_BUFFER_SIZE should add 1 because it must also hold the feedback LogUnit itself. - public static final int FEEDBACK_WORD_BUFFER_SIZE = (Integer.MAX_VALUE - 1) + 1; - - // The special output text to invoke a research feedback dialog. - public static final String RESEARCH_KEY_OUTPUT_TEXT = ".research."; - - // constants related to specific log points - private static final int[] WHITESPACE_SEPARATORS = - StringUtils.toSortedCodePointArray(" \t\n\r"); - private static final int MAX_INPUTVIEW_LENGTH_TO_CAPTURE = 8192; // must be >=1 - private static final String PREF_RESEARCH_SAVED_CHANNEL = "pref_research_saved_channel"; - - private static final long RESEARCHLOG_CLOSE_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5); - private static final long RESEARCHLOG_ABORT_TIMEOUT_IN_MS = TimeUnit.SECONDS.toMillis(5); - private static final long DURATION_BETWEEN_DIR_CLEANUP_IN_MS = TimeUnit.DAYS.toMillis(1); - private static final long MAX_LOGFILE_AGE_IN_MS = TimeUnit.DAYS.toMillis(4); - - private static final ResearchLogger sInstance = new ResearchLogger(); - private static String sAccountType = null; - private static String sAllowedAccountDomain = null; - private ResearchLog mMainResearchLog; // always non-null after init() is called - // mFeedbackLog records all events for the session, private or not (excepting - // passwords). It is written to permanent storage only if the user explicitly commands - // the system to do so. - // LogUnits are queued in the LogBuffers and published to the ResearchLogs when words are - // complete. - /* package for test */ MainLogBuffer mMainLogBuffer; // always non-null after init() is called - /* package */ ResearchLog mUserRecordingLog; - /* package */ LogBuffer mUserRecordingLogBuffer; - private File mUserRecordingFile = null; - - private boolean mIsPasswordView = false; - private SharedPreferences mPrefs; - - // digits entered by the user are replaced with this codepoint. - /* package for test */ static final int DIGIT_REPLACEMENT_CODEPOINT = - Character.codePointAt("\uE000", 0); // U+E000 is in the "private-use area" - // U+E001 is in the "private-use area" - /* package for test */ static final String WORD_REPLACEMENT_STRING = "\uE001"; - protected static final int SUSPEND_DURATION_IN_MINUTES = 1; - - // used to check whether words are not unique - private DictionaryFacilitatorForSuggest mDictionaryFacilitator; - private MainKeyboardView mMainKeyboardView; - // TODO: Check whether a superclass can be used instead of LatinIME. - /* package for test */ LatinIME mLatinIME; - private final Statistics mStatistics; - private final MotionEventReader mMotionEventReader = new MotionEventReader(); - private final Replayer mReplayer = Replayer.getInstance(); - private ResearchLogDirectory mResearchLogDirectory; - private SplashScreen mSplashScreen; - - private Intent mUploadNowIntent; - - /* package for test */ LogUnit mCurrentLogUnit = new LogUnit(); - - // Gestured or tapped words may be committed after the gesture of the next word has started. - // To ensure that the gesture data of the next word is not associated with the previous word, - // thereby leaking private data, we store the time of the down event that started the second - // gesture, and when committing the earlier word, split the LogUnit. - private long mSavedDownEventTime; - private Bundle mFeedbackDialogBundle = null; - // Whether the feedback dialog is visible, and the user is typing into it. Normal logging is - // not performed on text that the user types into the feedback dialog. - private boolean mInFeedbackDialog = false; - private Handler mUserRecordingTimeoutHandler; - private static final long USER_RECORDING_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30); - - // Stores a temporary LogUnit while generating a phantom space. Needed because phantom spaces - // are issued out-of-order, immediately before the characters generated by other operations that - // have already outputted LogStatements. - private LogUnit mPhantomSpaceLogUnit = null; - - private ResearchLogger() { - mStatistics = Statistics.getInstance(); - } - - public static ResearchLogger getInstance() { - return sInstance; - } - - public void init(final LatinIME latinIME, final KeyboardSwitcher keyboardSwitcher) { - assert latinIME != null; - mLatinIME = latinIME; - mPrefs = PreferenceManager.getDefaultSharedPreferences(latinIME); - mPrefs.registerOnSharedPreferenceChangeListener(this); - - // Initialize fields from preferences - sIsLogging = ResearchSettings.readResearchLoggerEnabledFlag(mPrefs); - - // Initialize fields from resources - final Resources res = latinIME.getResources(); - sAccountType = res.getString(R.string.research_account_type); - sAllowedAccountDomain = res.getString(R.string.research_allowed_account_domain); - - // Initialize directory manager - mResearchLogDirectory = new ResearchLogDirectory(mLatinIME); - cleanLogDirectoryIfNeeded(mResearchLogDirectory, System.currentTimeMillis()); - - // Initialize log buffers - resetLogBuffers(); - - // Initialize external services - mUploadNowIntent = new Intent(mLatinIME, UploaderService.class); - mUploadNowIntent.putExtra(UploaderService.EXTRA_UPLOAD_UNCONDITIONALLY, true); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - UploaderService.cancelAndRescheduleUploadingService(mLatinIME, - true /* needsRescheduling */); - } - mReplayer.setKeyboardSwitcher(keyboardSwitcher); - } - - private void resetLogBuffers() { - mMainResearchLog = new ResearchLog(mResearchLogDirectory.getLogFilePath( - System.currentTimeMillis(), System.nanoTime()), mLatinIME); - final int numWordsToIgnore = new Random().nextInt(NUMBER_OF_WORDS_BETWEEN_SAMPLES + 1); - mMainLogBuffer = new MainLogBuffer(NUMBER_OF_WORDS_BETWEEN_SAMPLES, numWordsToIgnore, - mDictionaryFacilitator) { - @Override - protected void publish(final ArrayList<LogUnit> logUnits, - boolean canIncludePrivateData) { - canIncludePrivateData |= IS_LOGGING_EVERYTHING; - for (final LogUnit logUnit : logUnits) { - if (DEBUG) { - final String wordsString = logUnit.getWordsAsString(); - Log.d(TAG, "onPublish: '" + wordsString - + "', hc: " + logUnit.containsUserDeletions() - + ", cipd: " + canIncludePrivateData); - } - for (final String word : logUnit.getWordsAsStringArray()) { - final boolean isDictionaryWord = mDictionaryFacilitator != null - && mDictionaryFacilitator.isValidMainDictWord(word); - mStatistics.recordWordEntered( - isDictionaryWord, logUnit.containsUserDeletions()); - } - } - publishLogUnits(logUnits, mMainResearchLog, canIncludePrivateData); - } - }; - } - - private void cleanLogDirectoryIfNeeded(final ResearchLogDirectory researchLogDirectory, - final long now) { - final long lastCleanupTime = ResearchSettings.readResearchLastDirCleanupTime(mPrefs); - if (now - lastCleanupTime < DURATION_BETWEEN_DIR_CLEANUP_IN_MS) return; - final long oldestAllowedFileTime = now - MAX_LOGFILE_AGE_IN_MS; - mResearchLogDirectory.cleanupLogFilesOlderThan(oldestAllowedFileTime); - ResearchSettings.writeResearchLastDirCleanupTime(mPrefs, now); - } - - public void mainKeyboardView_onAttachedToWindow(final MainKeyboardView mainKeyboardView) { - mMainKeyboardView = mainKeyboardView; - maybeShowSplashScreen(); - } - - public void mainKeyboardView_onDetachedFromWindow() { - mMainKeyboardView = null; - } - - public void onDestroy() { - if (mPrefs != null) { - mPrefs.unregisterOnSharedPreferenceChangeListener(this); - } - } - - private void maybeShowSplashScreen() { - if (ResearchSettings.readHasSeenSplash(mPrefs)) return; - if (mSplashScreen != null && mSplashScreen.isShowing()) return; - if (mMainKeyboardView == null) return; - final IBinder windowToken = mMainKeyboardView.getWindowToken(); - if (windowToken == null) return; - - mSplashScreen = new SplashScreen(mLatinIME, this); - mSplashScreen.showSplashScreen(windowToken); - } - - @Override - public void onSplashScreenUserClickedOk() { - if (mPrefs == null) { - mPrefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME); - if (mPrefs == null) return; - } - sIsLogging = true; - ResearchSettings.writeResearchLoggerEnabledFlag(mPrefs, true); - ResearchSettings.writeHasSeenSplash(mPrefs, true); - restart(); - } - - private void checkForEmptyEditor() { - if (mLatinIME == null) { - return; - } - final InputConnection ic = mLatinIME.getCurrentInputConnection(); - if (ic == null) { - return; - } - final CharSequence textBefore = ic.getTextBeforeCursor(1, 0); - if (!TextUtils.isEmpty(textBefore)) { - mStatistics.setIsEmptyUponStarting(false); - return; - } - final CharSequence textAfter = ic.getTextAfterCursor(1, 0); - if (!TextUtils.isEmpty(textAfter)) { - mStatistics.setIsEmptyUponStarting(false); - return; - } - if (textBefore != null && textAfter != null) { - mStatistics.setIsEmptyUponStarting(true); - } - } - - private void start() { - if (DEBUG) { - Log.d(TAG, "start called"); - } - maybeShowSplashScreen(); - requestIndicatorRedraw(); - mStatistics.reset(); - checkForEmptyEditor(); - } - - /* package */ void stop() { - if (DEBUG) { - Log.d(TAG, "stop called"); - } - // Commit mCurrentLogUnit before closing. - commitCurrentLogUnit(); - - try { - mMainLogBuffer.shiftAndPublishAll(); - } catch (final IOException e) { - Log.w(TAG, "IOException when publishing LogBuffer", e); - } - logStatistics(); - commitCurrentLogUnit(); - mMainLogBuffer.setIsStopping(); - try { - mMainLogBuffer.shiftAndPublishAll(); - } catch (final IOException e) { - Log.w(TAG, "IOException when publishing LogBuffer", e); - } - mMainResearchLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); - - resetLogBuffers(); - cancelFeedbackDialog(); - } - - public void abort() { - if (DEBUG) { - Log.d(TAG, "abort called"); - } - mMainLogBuffer.clear(); - mMainResearchLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); - - resetLogBuffers(); - } - - private void restart() { - stop(); - start(); - } - - @Override - public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { - if (key == null || prefs == null) { - return; - } - requestIndicatorRedraw(); - mPrefs = prefs; - prefsChanged(prefs); - } - - public void onResearchKeySelected(final LatinIME latinIME) { - mCurrentLogUnit.removeResearchButtonInvocation(); - if (mInFeedbackDialog) { - Toast.makeText(latinIME, R.string.research_please_exit_feedback_form, - Toast.LENGTH_LONG).show(); - return; - } - presentFeedbackDialog(latinIME); - } - - public void presentFeedbackDialogFromSettings() { - if (mLatinIME != null) { - presentFeedbackDialog(mLatinIME); - } - } - - public void presentFeedbackDialog(final LatinIME latinIME) { - if (isMakingUserRecording()) { - saveRecording(); - } - mInFeedbackDialog = true; - - final Intent intent = new Intent(); - intent.setClass(mLatinIME, FeedbackActivity.class); - if (mFeedbackDialogBundle == null) { - // Restore feedback field with channel name - final Bundle bundle = new Bundle(); - bundle.putBoolean(FeedbackFragment.KEY_INCLUDE_ACCOUNT_NAME, true); - bundle.putBoolean(FeedbackFragment.KEY_HAS_USER_RECORDING, false); - if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) { - final String savedChannelName = mPrefs.getString(PREF_RESEARCH_SAVED_CHANNEL, ""); - bundle.putString(FeedbackFragment.KEY_FEEDBACK_STRING, savedChannelName); - } - mFeedbackDialogBundle = bundle; - } - intent.putExtras(mFeedbackDialogBundle); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - latinIME.startActivity(intent); - } - - public void setFeedbackDialogBundle(final Bundle bundle) { - mFeedbackDialogBundle = bundle; - } - - public void startRecording() { - final Resources res = mLatinIME.getResources(); - Toast.makeText(mLatinIME, - res.getString(R.string.research_feedback_demonstration_instructions), - Toast.LENGTH_LONG).show(); - startRecordingInternal(); - } - - private void startRecordingInternal() { - if (mUserRecordingLog != null) { - mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); - } - mUserRecordingFile = mResearchLogDirectory.getUserRecordingFilePath( - System.currentTimeMillis(), System.nanoTime()); - mUserRecordingLog = new ResearchLog(mUserRecordingFile, mLatinIME); - mUserRecordingLogBuffer = new LogBuffer(); - resetRecordingTimer(); - } - - private boolean isMakingUserRecording() { - return mUserRecordingLog != null; - } - - private void resetRecordingTimer() { - if (mUserRecordingTimeoutHandler == null) { - mUserRecordingTimeoutHandler = new Handler(); - } - clearRecordingTimer(); - mUserRecordingTimeoutHandler.postDelayed(mRecordingHandlerTimeoutRunnable, - USER_RECORDING_TIMEOUT_MS); - } - - private void clearRecordingTimer() { - mUserRecordingTimeoutHandler.removeCallbacks(mRecordingHandlerTimeoutRunnable); - } - - private Runnable mRecordingHandlerTimeoutRunnable = new Runnable() { - @Override - public void run() { - cancelRecording(); - requestIndicatorRedraw(); - final Resources res = mLatinIME.getResources(); - Toast.makeText(mLatinIME, res.getString(R.string.research_feedback_recording_failure), - Toast.LENGTH_LONG).show(); - } - }; - - private void cancelRecording() { - if (mUserRecordingLog != null) { - mUserRecordingLog.blockingAbort(RESEARCHLOG_ABORT_TIMEOUT_IN_MS); - } - mUserRecordingLog = null; - mUserRecordingLogBuffer = null; - if (mFeedbackDialogBundle != null) { - mFeedbackDialogBundle.putBoolean("HasRecording", false); - } - } - - private void saveRecording() { - commitCurrentLogUnit(); - publishLogBuffer(mUserRecordingLogBuffer, mUserRecordingLog, true); - mUserRecordingLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); - mUserRecordingLog = null; - mUserRecordingLogBuffer = null; - - if (mFeedbackDialogBundle != null) { - mFeedbackDialogBundle.putBoolean(FeedbackFragment.KEY_HAS_USER_RECORDING, true); - } - clearRecordingTimer(); - } - - // TODO: currently unreachable. Remove after being sure enable/disable is - // not needed. - /* - public void enableOrDisable(final boolean showEnable, final LatinIME latinIME) { - if (showEnable) { - if (!sIsLogging) { - setLoggingAllowed(true); - } - resumeLogging(); - Toast.makeText(latinIME, - R.string.research_notify_session_logging_enabled, - Toast.LENGTH_LONG).show(); - } else { - Toast toast = Toast.makeText(latinIME, - R.string.research_notify_session_log_deleting, - Toast.LENGTH_LONG); - toast.show(); - boolean isLogDeleted = abort(); - final long currentTime = System.currentTimeMillis(); - final long resumeTime = currentTime - + TimeUnit.MINUTES.toMillis(SUSPEND_DURATION_IN_MINUTES); - suspendLoggingUntil(resumeTime); - toast.cancel(); - Toast.makeText(latinIME, R.string.research_notify_logging_suspended, - Toast.LENGTH_LONG).show(); - } - } - */ - - /** - * Get the name of the first allowed account on the device. - * - * Allowed accounts must be in the domain given by ALLOWED_ACCOUNT_DOMAIN. - * - * @return The user's account name. - */ - public String getAccountName() { - if (sAccountType == null || sAccountType.isEmpty()) { - return null; - } - if (sAllowedAccountDomain == null || sAllowedAccountDomain.isEmpty()) { - return null; - } - final AccountManager manager = AccountManager.get(mLatinIME); - // Filter first by account type. - final Account[] accounts = manager.getAccountsByType(sAccountType); - - for (final Account account : accounts) { - if (DEBUG) { - Log.d(TAG, account.name); - } - final String[] parts = account.name.split("@"); - if (parts.length > 1 && parts[1].equals(sAllowedAccountDomain)) { - return parts[0]; - } - } - return null; - } - - private static final LogStatement LOGSTATEMENT_FEEDBACK = - new LogStatement("UserFeedback", false, false, "contents", "accountName", "recording"); - public void sendFeedback(final String feedbackContents, final boolean includeHistory, - final boolean isIncludingAccountName, final boolean isIncludingRecording) { - String recording = ""; - if (isIncludingRecording) { - // Try to read recording from recently written json file - if (mUserRecordingFile != null) { - FileChannel channel = null; - try { - channel = new FileInputStream(mUserRecordingFile).getChannel(); - final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, - channel.size()); - // Android's openFileOutput() creates the file, so we use Android's default - // Charset (UTF-8) here to read it. - recording = Charset.defaultCharset().decode(buffer).toString(); - } catch (FileNotFoundException e) { - Log.e(TAG, "Could not find recording file", e); - } catch (IOException e) { - Log.e(TAG, "Error reading recording file", e); - } finally { - if (channel != null) { - try { - channel.close(); - } catch (IOException e) { - Log.e(TAG, "Error closing recording file", e); - } - } - } - } - } - final LogUnit feedbackLogUnit = new LogUnit(); - final String accountName = isIncludingAccountName ? getAccountName() : ""; - feedbackLogUnit.addLogStatement(LOGSTATEMENT_FEEDBACK, SystemClock.uptimeMillis(), - feedbackContents, accountName, recording); - - final ResearchLog feedbackLog = new FeedbackLog(mResearchLogDirectory.getLogFilePath( - System.currentTimeMillis(), System.nanoTime()), mLatinIME); - final LogBuffer feedbackLogBuffer = new LogBuffer(); - feedbackLogBuffer.shiftIn(feedbackLogUnit); - publishLogBuffer(feedbackLogBuffer, feedbackLog, true /* isIncludingPrivateData */); - feedbackLog.blockingClose(RESEARCHLOG_CLOSE_TIMEOUT_IN_MS); - uploadNow(); - - if (isIncludingRecording && DEBUG_REPLAY_AFTER_FEEDBACK) { - final Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - final ReplayData replayData = - mMotionEventReader.readMotionEventData(mUserRecordingFile); - mReplayer.replay(replayData, null); - } - }, TimeUnit.SECONDS.toMillis(1)); - } - - if (FEEDBACK_DIALOG_SHOULD_PRESERVE_TEXT_FIELD) { - // Use feedback string as a channel name to label feedback strings. Here we record the - // string for prepopulating the field next time. - final String channelName = feedbackContents; - if (mPrefs == null) { - return; - } - mPrefs.edit().putString(PREF_RESEARCH_SAVED_CHANNEL, channelName).apply(); - } - } - - public void uploadNow() { - if (DEBUG) { - Log.d(TAG, "calling uploadNow()"); - } - mLatinIME.startService(mUploadNowIntent); - } - - public void onLeavingSendFeedbackDialog() { - mInFeedbackDialog = false; - } - - private void cancelFeedbackDialog() { - if (isMakingUserRecording()) { - cancelRecording(); - } - mInFeedbackDialog = false; - } - - public void initDictionary(final DictionaryFacilitatorForSuggest dictionaryFacilitator) { - mDictionaryFacilitator = dictionaryFacilitator; - // MainLogBuffer now has an out-of-date Suggest object. Close down MainLogBuffer and create - // a new one. - if (mMainLogBuffer != null) { - restart(); - } - } - - private void setIsPasswordView(boolean isPasswordView) { - mIsPasswordView = isPasswordView; - } - - /** - * Returns true if logging is permitted. - * - * This method is called when adding a LogStatement to a LogUnit, and when adding a LogUnit to a - * ResearchLog. It is checked in both places in case conditions change between these times, and - * as a defensive measure in case refactoring changes the logging pipeline. - */ - private boolean isAllowedToLogTo(final ResearchLog researchLog) { - // Logging is never allowed in these circumstances - if (mIsPasswordView) return false; - if (!sIsLogging) return false; - if (mInFeedbackDialog) { - // The FeedbackDialog is up. Normal logging should not happen (the user might be trying - // out things while the dialog is up, and their reporting of an issue may not be - // representative of what they normally type). However, after the user has finished - // entering their feedback, the logger packs their comments and an encoded version of - // any demonstration of the issue into a special "FeedbackLog". So if the FeedbackLog - // is the destination, we do want to allow logging to it. - return researchLog.isFeedbackLog(); - } - // No other exclusions. Logging is permitted. - return true; - } - - public void requestIndicatorRedraw() { - if (!IS_SHOWING_INDICATOR) { - return; - } - if (mMainKeyboardView == null) { - return; - } - mMainKeyboardView.invalidateAllKeys(); - } - - private boolean isReplaying() { - return mReplayer.isReplaying(); - } - - private int getIndicatorColor() { - if (isMakingUserRecording()) { - return Color.YELLOW; - } - if (isReplaying()) { - return Color.GREEN; - } - return Color.RED; - } - - public void paintIndicator(KeyboardView view, Paint paint, Canvas canvas, int width, - int height) { - // TODO: Reimplement using a keyboard background image specific to the ResearchLogger - // and remove this method. - // The check for MainKeyboardView ensures that the indicator only decorates the main - // keyboard, not every keyboard. - if (IS_SHOWING_INDICATOR && (isAllowedToLogTo(mMainResearchLog) || isReplaying()) - && view instanceof MainKeyboardView) { - final int savedColor = paint.getColor(); - paint.setColor(getIndicatorColor()); - final Style savedStyle = paint.getStyle(); - paint.setStyle(Style.STROKE); - final float savedStrokeWidth = paint.getStrokeWidth(); - if (IS_SHOWING_INDICATOR_CLEARLY) { - paint.setStrokeWidth(5); - canvas.drawLine(0, 0, 0, height, paint); - canvas.drawLine(width, 0, width, height, paint); - } else { - // Put a tiny dot on the screen so a knowledgeable user can check whether it is - // enabled. The dot is actually a zero-width, zero-height rectangle, placed at the - // lower-right corner of the canvas, painted with a non-zero border width. - paint.setStrokeWidth(3); - canvas.drawRect(width - 1, height - 1, width, height, paint); - } - paint.setColor(savedColor); - paint.setStyle(savedStyle); - paint.setStrokeWidth(savedStrokeWidth); - } - } - - /** - * Buffer a research log event, flagging it as privacy-sensitive. - */ - private synchronized void enqueueEvent(final LogStatement logStatement, - final Object... values) { - enqueueEvent(mCurrentLogUnit, logStatement, values); - } - - private synchronized void enqueueEvent(final LogUnit logUnit, final LogStatement logStatement, - final Object... values) { - assert values.length == logStatement.getKeys().length; - if (isAllowedToLogTo(mMainResearchLog) && logUnit != null) { - final long time = SystemClock.uptimeMillis(); - logUnit.addLogStatement(logStatement, time, values); - } - } - - private void setCurrentLogUnitContainsDigitFlag() { - mCurrentLogUnit.setMayContainDigit(); - } - - private void setCurrentLogUnitContainsUserDeletions() { - mCurrentLogUnit.setContainsUserDeletions(); - } - - private void setCurrentLogUnitCorrectionType(final int correctionType) { - mCurrentLogUnit.setCorrectionType(correctionType); - } - - /* package for test */ void commitCurrentLogUnit() { - if (DEBUG) { - Log.d(TAG, "commitCurrentLogUnit" + (mCurrentLogUnit.hasOneOrMoreWords() ? - ": " + mCurrentLogUnit.getWordsAsString() : "")); - } - if (!mCurrentLogUnit.isEmpty()) { - mMainLogBuffer.shiftIn(mCurrentLogUnit); - if (mUserRecordingLogBuffer != null) { - mUserRecordingLogBuffer.shiftIn(mCurrentLogUnit); - } - mCurrentLogUnit = new LogUnit(); - } else { - if (DEBUG) { - Log.d(TAG, "Warning: tried to commit empty log unit."); - } - } - } - - private static final LogStatement LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT = - new LogStatement("UncommitCurrentLogUnit", false, false); - public void uncommitCurrentLogUnit(final String expectedWord, - final boolean dumpCurrentLogUnit) { - // The user has deleted this word and returned to the previous. Check that the word in the - // logUnit matches the expected word. If so, restore the last log unit committed to be the - // current logUnit. I.e., pull out the last LogUnit from all the LogBuffers, and make - // it the mCurrentLogUnit so the new edits are captured with the word. Optionally dump the - // contents of mCurrentLogUnit (useful if they contain deletions of the next word that - // should not be reported to protect user privacy) - // - // Note that we don't use mLastLogUnit here, because it only goes one word back and is only - // needed for reverts, which only happen one back. - final LogUnit oldLogUnit = mMainLogBuffer.peekLastLogUnit(); - - // Check that expected word matches. It's ok if both strings are null, because this is the - // case where the LogUnit is storing a non-word, e.g. a separator. - if (oldLogUnit != null) { - // Because the word is stored in the LogUnit with digits scrubbed, the comparison must - // be made on a scrubbed version of the expectedWord as well. - final String scrubbedExpectedWord = scrubDigitsFromString(expectedWord); - final String oldLogUnitWords = oldLogUnit.getWordsAsString(); - if (!TextUtils.equals(scrubbedExpectedWord, oldLogUnitWords)) return; - } - - // Uncommit, merging if necessary. - mMainLogBuffer.unshiftIn(); - if (oldLogUnit != null && !dumpCurrentLogUnit) { - oldLogUnit.append(mCurrentLogUnit); - mSavedDownEventTime = Long.MAX_VALUE; - } - if (oldLogUnit == null) { - mCurrentLogUnit = new LogUnit(); - } else { - mCurrentLogUnit = oldLogUnit; - } - enqueueEvent(LOGSTATEMENT_UNCOMMIT_CURRENT_LOGUNIT); - if (DEBUG) { - Log.d(TAG, "uncommitCurrentLogUnit (dump=" + dumpCurrentLogUnit + ") back to " - + (mCurrentLogUnit.hasOneOrMoreWords() ? ": '" - + mCurrentLogUnit.getWordsAsString() + "'" : "")); - } - } - - /** - * Publish all the logUnits in the logBuffer, without doing any privacy filtering. - */ - /* package for test */ void publishLogBuffer(final LogBuffer logBuffer, - final ResearchLog researchLog, final boolean canIncludePrivateData) { - publishLogUnits(logBuffer.getLogUnits(), researchLog, canIncludePrivateData); - } - - private static final LogStatement LOGSTATEMENT_LOG_SEGMENT_OPENING = - new LogStatement("logSegmentStart", false, false, "isIncludingPrivateData"); - private static final LogStatement LOGSTATEMENT_LOG_SEGMENT_CLOSING = - new LogStatement("logSegmentEnd", false, false); - /** - * Publish all LogUnits in a list. - * - * Any privacy checks should be performed before calling this method. - */ - /* package for test */ void publishLogUnits(final List<LogUnit> logUnits, - final ResearchLog researchLog, final boolean canIncludePrivateData) { - final LogUnit openingLogUnit = new LogUnit(); - if (logUnits.isEmpty()) return; - if (!isAllowedToLogTo(researchLog)) return; - // LogUnits not containing private data, such as contextual data for the log, do not require - // logSegment boundary statements. - if (canIncludePrivateData) { - openingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_OPENING, - SystemClock.uptimeMillis(), canIncludePrivateData); - researchLog.publish(openingLogUnit, true /* isIncludingPrivateData */); - } - for (LogUnit logUnit : logUnits) { - if (DEBUG) { - Log.d(TAG, "publishLogBuffer: " + (logUnit.hasOneOrMoreWords() - ? logUnit.getWordsAsString() : "<wordless>") - + ", correction?: " + logUnit.containsUserDeletions()); - } - researchLog.publish(logUnit, canIncludePrivateData); - } - if (canIncludePrivateData) { - final LogUnit closingLogUnit = new LogUnit(); - closingLogUnit.addLogStatement(LOGSTATEMENT_LOG_SEGMENT_CLOSING, - SystemClock.uptimeMillis()); - researchLog.publish(closingLogUnit, true /* isIncludingPrivateData */); - } - } - - public static boolean hasLetters(final String word) { - final int length = word.length(); - for (int i = 0; i < length; i = word.offsetByCodePoints(i, 1)) { - final int codePoint = word.codePointAt(i); - if (Character.isLetter(codePoint)) { - return true; - } - } - return false; - } - - /** - * Commit the portion of mCurrentLogUnit before maxTime as a worded logUnit. - * - * After this operation completes, mCurrentLogUnit will hold any logStatements that happened - * after maxTime. - */ - /* package for test */ void commitCurrentLogUnitAsWord(final String word, final long maxTime, - final boolean isBatchMode) { - if (word == null) { - return; - } - if (word.length() > 0 && hasLetters(word)) { - mCurrentLogUnit.setWords(word); - } - final LogUnit newLogUnit = mCurrentLogUnit.splitByTime(maxTime); - enqueueCommitText(word, isBatchMode); - commitCurrentLogUnit(); - mCurrentLogUnit = newLogUnit; - } - - /** - * Record the time of a MotionEvent.ACTION_DOWN. - * - * Warning: Not thread safe. Only call from the main thread. - */ - private void setSavedDownEventTime(final long time) { - mSavedDownEventTime = time; - } - - public void onWordFinished(final String word, final boolean isBatchMode) { - commitCurrentLogUnitAsWord(word, mSavedDownEventTime, isBatchMode); - mSavedDownEventTime = Long.MAX_VALUE; - } - - private static int scrubDigitFromCodePoint(int codePoint) { - return Character.isDigit(codePoint) ? DIGIT_REPLACEMENT_CODEPOINT : codePoint; - } - - /* package for test */ static String scrubDigitsFromString(final String s) { - if (s == null) return null; - StringBuilder sb = null; - final int length = s.length(); - for (int i = 0; i < length; i = s.offsetByCodePoints(i, 1)) { - final int codePoint = Character.codePointAt(s, i); - if (Character.isDigit(codePoint)) { - if (sb == null) { - sb = new StringBuilder(length); - sb.append(s.substring(0, i)); - } - sb.appendCodePoint(DIGIT_REPLACEMENT_CODEPOINT); - } else { - if (sb != null) { - sb.appendCodePoint(codePoint); - } - } - } - if (sb == null) { - return s; - } else { - return sb.toString(); - } - } - - private String scrubWord(String word) { - if (mDictionaryFacilitator != null && mDictionaryFacilitator.isValidMainDictWord(word)) { - return word; - } - return WORD_REPLACEMENT_STRING; - } - - // Specific logging methods follow below. The comments for each logging method should - // indicate what specific method is logged, and how to trigger it from the user interface. - // - // Logging methods can be generally classified into two flavors, "UserAction", which should - // correspond closely to an event that is sensed by the IME, and is usually generated - // directly by the user, and "SystemResponse" which corresponds to an event that the IME - // generates, often after much processing of user input. SystemResponses should correspond - // closely to user-visible events. - // TODO: Consider exposing the UserAction classification in the log output. - - /** - * Log a call to LatinIME.onStartInputViewInternal(). - * - * UserAction: called each time the keyboard is opened up. - */ - private static final LogStatement LOGSTATEMENT_LATIN_IME_ON_START_INPUT_VIEW_INTERNAL = - new LogStatement("LatinImeOnStartInputViewInternal", false, false, "uuid", - "packageName", "inputType", "imeOptions", "fieldId", "display", "model", - "prefs", "versionCode", "versionName", "outputFormatVersion", "logEverything", - "isDevTeamBuild"); - public static void latinIME_onStartInputViewInternal(final EditorInfo editorInfo, - final SharedPreferences prefs) { - final ResearchLogger researchLogger = getInstance(); - if (editorInfo != null) { - final boolean isPassword = InputTypeUtils.isPasswordInputType(editorInfo.inputType) - || InputTypeUtils.isVisiblePasswordInputType(editorInfo.inputType); - getInstance().setIsPasswordView(isPassword); - researchLogger.start(); - final Context context = researchLogger.mLatinIME; - try { - final PackageInfo packageInfo; - packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), - 0); - final Integer versionCode = packageInfo.versionCode; - final String versionName = packageInfo.versionName; - final String uuid = ResearchSettings.readResearchLoggerUuid(researchLogger.mPrefs); - researchLogger.enqueueEvent(LOGSTATEMENT_LATIN_IME_ON_START_INPUT_VIEW_INTERNAL, - uuid, editorInfo.packageName, Integer.toHexString(editorInfo.inputType), - Integer.toHexString(editorInfo.imeOptions), editorInfo.fieldId, - Build.DISPLAY, Build.MODEL, prefs, versionCode, versionName, - OUTPUT_FORMAT_VERSION, IS_LOGGING_EVERYTHING, - researchLogger.isDevTeamBuild()); - // Commit the logUnit so the LatinImeOnStartInputViewInternal event is in its own - // logUnit at the beginning of the log. - researchLogger.commitCurrentLogUnit(); - } catch (final NameNotFoundException e) { - Log.e(TAG, "NameNotFound", e); - } - } - } - - // TODO: Update this heuristic pattern to something more reliable. Developer builds tend to - // have the developer name and year embedded. - private static final Pattern developerBuildRegex = Pattern.compile("[A-Za-z]\\.20[1-9]"); - private boolean isDevTeamBuild() { - try { - final PackageInfo packageInfo; - packageInfo = mLatinIME.getPackageManager().getPackageInfo(mLatinIME.getPackageName(), - 0); - final String versionName = packageInfo.versionName; - return developerBuildRegex.matcher(versionName).find(); - } catch (final NameNotFoundException e) { - Log.e(TAG, "Could not determine package name", e); - return false; - } - } - - /** - * Log a change in preferences. - * - * UserAction: called when the user changes the settings. - */ - private static final LogStatement LOGSTATEMENT_PREFS_CHANGED = - new LogStatement("PrefsChanged", false, false, "prefs"); - public static void prefsChanged(final SharedPreferences prefs) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_PREFS_CHANGED, prefs); - } - - /** - * Log a call to MainKeyboardView.processMotionEvent(). - * - * UserAction: called when the user puts their finger onto the screen (ACTION_DOWN). - * - */ - private static final LogStatement LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT = - new LogStatement("MotionEvent", true, false, "action", - LogStatement.KEY_IS_LOGGING_RELATED, "motionEvent"); - public static void mainKeyboardView_processMotionEvent(final MotionEvent me) { - if (me == null) { - return; - } - final int action = me.getActionMasked(); - final long eventTime = me.getEventTime(); - final String actionString = LoggingUtils.getMotionEventActionTypeString(action); - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_MAIN_KEYBOARD_VIEW_PROCESS_MOTION_EVENT, - actionString, false /* IS_LOGGING_RELATED */, MotionEvent.obtain(me)); - if (action == MotionEvent.ACTION_DOWN) { - // Subtract 1 from eventTime so the down event is included in the later - // LogUnit, not the earlier (the test is for inequality). - researchLogger.setSavedDownEventTime(eventTime - 1); - } - // Refresh the timer in case we are capturing user feedback. - if (researchLogger.isMakingUserRecording()) { - researchLogger.resetRecordingTimer(); - } - } - - /** - * Log a call to LatinIME.onCodeInput(). - * - * SystemResponse: The main processing step for entering text. Called when the user performs a - * tap, a flick, a long press, releases a gesture, or taps a punctuation suggestion. - */ - private static final LogStatement LOGSTATEMENT_LATIN_IME_ON_CODE_INPUT = - new LogStatement("LatinImeOnCodeInput", true, false, "code", "x", "y"); - public static void latinIME_onCodeInput(final int code, final int x, final int y) { - final long time = SystemClock.uptimeMillis(); - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_LATIN_IME_ON_CODE_INPUT, - Constants.printableCode(scrubDigitFromCodePoint(code)), x, y); - if (Character.isDigit(code)) { - researchLogger.setCurrentLogUnitContainsDigitFlag(); - } - researchLogger.mStatistics.recordChar(code, time); - } - /** - * Log a call to LatinIME.onDisplayCompletions(). - * - * SystemResponse: The IME has displayed application-specific completions. They may show up - * in the suggestion strip, such as a landscape phone. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_ONDISPLAYCOMPLETIONS = - new LogStatement("LatinIMEOnDisplayCompletions", true, true, - "applicationSpecifiedCompletions"); - public static void latinIME_onDisplayCompletions( - final CompletionInfo[] applicationSpecifiedCompletions) { - // Note; passing an array as a single element in a vararg list. Must create a new - // dummy array around it or it will get expanded. - getInstance().enqueueEvent(LOGSTATEMENT_LATINIME_ONDISPLAYCOMPLETIONS, - new Object[] { applicationSpecifiedCompletions }); - } - - /** - * The IME is finishing; it is either being destroyed, or is about to be hidden. - * - * UserAction: The user has performed an action that has caused the IME to be closed. They may - * have focused on something other than a text field, or explicitly closed it. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL = - new LogStatement("LatinIMEOnFinishInputViewInternal", false, false, "isTextTruncated", - "text"); - public static void latinIME_onFinishInputViewInternal(final boolean finishingInput) { - // The finishingInput flag is set in InputMethodService. It is true if called from - // doFinishInput(), which can be called as part of doStartInput(). This can happen at times - // when the IME is not closing, such as when powering up. The finishinInput flag is false - // if called from finishViews(), which is called from hideWindow() and onDestroy(). These - // are the situations in which we want to finish up the researchLog. - if (!finishingInput) { - final ResearchLogger researchLogger = getInstance(); - // Assume that OUTPUT_ENTIRE_BUFFER is only true when we don't care about privacy (e.g. - // during a live user test), so the normal isPotentiallyPrivate and - // isPotentiallyRevealing flags do not apply - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONFINISHINPUTVIEWINTERNAL, - true /* isTextTruncated */, "" /* text */); - researchLogger.commitCurrentLogUnit(); - getInstance().stop(); - } - } - - /** - * Log a call to LatinIME.onUpdateSelection(). - * - * UserAction/SystemResponse: The user has moved the cursor or selection. This function may - * be called, however, when the system has moved the cursor, say by inserting a character. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_ONUPDATESELECTION = - new LogStatement("LatinIMEOnUpdateSelection", true, false, "lastSelectionStart", - "lastSelectionEnd", "oldSelStart", "oldSelEnd", "newSelStart", "newSelEnd", - "composingSpanStart", "composingSpanEnd", "expectingUpdateSelection", - "expectingUpdateSelectionFromLogger", "context"); - public static void latinIME_onUpdateSelection(final int lastSelectionStart, - final int lastSelectionEnd, final int oldSelStart, final int oldSelEnd, - final int newSelStart, final int newSelEnd, final int composingSpanStart, - final int composingSpanEnd, final RichInputConnection connection) { - String word = ""; - if (connection != null) { - TextRange range = connection.getWordRangeAtCursor(WHITESPACE_SEPARATORS, 1); - if (range != null) { - word = range.mWord.toString(); - } - } - final ResearchLogger researchLogger = getInstance(); - final String scrubbedWord = researchLogger.scrubWord(word); - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONUPDATESELECTION, lastSelectionStart, - lastSelectionEnd, oldSelStart, oldSelEnd, newSelStart, newSelEnd, - composingSpanStart, composingSpanEnd, false /* expectingUpdateSelection */, - false /* expectingUpdateSelectionFromLogger */, scrubbedWord); - } - - /** - * Log a call to LatinIME.onTextInput(). - * - * SystemResponse: Raw text is added to the TextView. - */ - public static void latinIME_onTextInput(final String text, final boolean isBatchMode) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE, isBatchMode); - } - - /** - * Log a revert of onTextInput() (known in the IME as "EnteredText"). - * - * SystemResponse: Remove the LogUnit recording the textInput - */ - public static void latinIME_handleBackspace_cancelTextInput(final String text) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.uncommitCurrentLogUnit(text, true /* dumpCurrentLogUnit */); - } - - /** - * Log a call to LatinIME.pickSuggestionManually(). - * - * UserAction: The user has chosen a specific word from the suggestion strip. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY = - new LogStatement("LatinIMEPickSuggestionManually", true, false, "replacedWord", "index", - "suggestion", "x", "y", "isBatchMode", "score", "kind", "sourceDict"); - /** - * Log a call to LatinIME.pickSuggestionManually(). - * - * @param replacedWord the typed word that this manual suggestion replaces. May not be null. - * @param index the index in the suggestion strip - * @param suggestion the committed suggestion. May not be null. - * @param isBatchMode whether this was input in batch mode, aka gesture. - * @param score the internal score of the suggestion, as output by the dictionary - * @param kind the kind of suggestion, as one of the SuggestedWordInfo#KIND_* constants - * @param sourceDict the source origin of this word, as one of the Dictionary#TYPE_* constants. - */ - public static void latinIME_pickSuggestionManually(final String replacedWord, - final int index, final String suggestion, final boolean isBatchMode, - final int score, final int kind, final String sourceDict) { - final ResearchLogger researchLogger = getInstance(); - // Note : suggestion can't be null here, because it's only called in a place where it - // can't be null. - if (!replacedWord.equals(suggestion.toString())) { - // The user chose something other than what was already there. - researchLogger.setCurrentLogUnitContainsUserDeletions(); - researchLogger.setCurrentLogUnitCorrectionType(LogUnit.CORRECTIONTYPE_TYPO); - } - final String scrubbedWord = scrubDigitsFromString(suggestion); - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PICKSUGGESTIONMANUALLY, - scrubDigitsFromString(replacedWord), index, - scrubbedWord, Constants.SUGGESTION_STRIP_COORDINATE, - Constants.SUGGESTION_STRIP_COORDINATE, isBatchMode, score, kind, sourceDict); - researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode); - researchLogger.mStatistics.recordManualSuggestion(SystemClock.uptimeMillis()); - } - - /** - * Log a call to LatinIME.punctuationSuggestion(). - * - * UserAction: The user has chosen punctuation from the punctuation suggestion strip. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION = - new LogStatement("LatinIMEPunctuationSuggestion", false, false, "index", "suggestion", - "x", "y", "isPrediction"); - public static void latinIME_punctuationSuggestion(final int index, final String suggestion, - final boolean isBatchMode, final boolean isPrediction) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_PUNCTUATIONSUGGESTION, index, suggestion, - Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE, - isPrediction); - researchLogger.commitCurrentLogUnitAsWord(suggestion, Long.MAX_VALUE, isBatchMode); - } - - /** - * Log a call to LatinIME.sendKeyCodePoint(). - * - * SystemResponse: The IME is inserting text into the TextView for non-word-constituent, - * strings (separators, numbers, other symbols). - */ - private static final LogStatement LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT = - new LogStatement("LatinIMESendKeyCodePoint", true, false, "code"); - public static void latinIME_sendKeyCodePoint(final int code) { - final ResearchLogger researchLogger = getInstance(); - final LogUnit phantomSpaceLogUnit = researchLogger.mPhantomSpaceLogUnit; - if (phantomSpaceLogUnit == null) { - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT, - Constants.printableCode(scrubDigitFromCodePoint(code))); - if (Character.isDigit(code)) { - researchLogger.setCurrentLogUnitContainsDigitFlag(); - } - researchLogger.commitCurrentLogUnit(); - } else { - researchLogger.enqueueEvent(phantomSpaceLogUnit, LOGSTATEMENT_LATINIME_SENDKEYCODEPOINT, - Constants.printableCode(scrubDigitFromCodePoint(code))); - if (Character.isDigit(code)) { - phantomSpaceLogUnit.setMayContainDigit(); - } - researchLogger.mMainLogBuffer.shiftIn(phantomSpaceLogUnit); - if (researchLogger.mUserRecordingLogBuffer != null) { - researchLogger.mUserRecordingLogBuffer.shiftIn(phantomSpaceLogUnit); - } - researchLogger.mPhantomSpaceLogUnit = null; - } - } - - /** - * Log a call to LatinIME.promotePhantomSpace(). - * - * SystemResponse: The IME is inserting a real space in place of a phantom space. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE = - new LogStatement("LatinIMEPromotePhantomSpace", false, false); - public static void latinIME_promotePhantomSpace() { - // A phantom space is always added before the text that triggered it. The triggering text - // and the events that created it will be in mCurrentLogUnit, but the phantom space should - // be in its own LogUnit, committed before the triggering text. Although it is created - // here, it is not added to the LogBuffer until the following call to - // latinIME_sendKeyCodePoint, because SENDKEYCODEPOINT LogStatement also must go into that - // LogUnit. - final ResearchLogger researchLogger = getInstance(); - researchLogger.mPhantomSpaceLogUnit = new LogUnit(); - researchLogger.enqueueEvent(researchLogger.mPhantomSpaceLogUnit, - LOGSTATEMENT_LATINIME_PROMOTEPHANTOMSPACE); - } - - /** - * Log a call to LatinIME.swapSwapperAndSpace(). - * - * SystemResponse: A symbol has been swapped with a space character. E.g. punctuation may swap - * if a soft space is inserted after a word. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE = - new LogStatement("LatinIMESwapSwapperAndSpace", false, false, "originalCharacters", - "charactersAfterSwap"); - public static void latinIME_swapSwapperAndSpace(final CharSequence originalCharacters, - final String charactersAfterSwap) { - final ResearchLogger researchLogger = getInstance(); - final LogUnit logUnit; - logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); - if (logUnit != null) { - researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_SWAPSWAPPERANDSPACE, - originalCharacters, charactersAfterSwap); - } - } - - /** - * Log a call to LatinIME.maybeDoubleSpacePeriod(). - * - * SystemResponse: Two spaces have been replaced by period space. - */ - public static void latinIME_maybeDoubleSpacePeriod(final String text, - final boolean isBatchMode) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.commitCurrentLogUnitAsWord(text, Long.MAX_VALUE, isBatchMode); - } - - /** - * Log a call to MainKeyboardView.onLongPress(). - * - * UserAction: The user has performed a long-press on a key. - */ - private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_ONLONGPRESS = - new LogStatement("MainKeyboardViewOnLongPress", false, false); - public static void mainKeyboardView_onLongPress() { - getInstance().enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_ONLONGPRESS); - } - - /** - * Log a call to MainKeyboardView.setKeyboard(). - * - * SystemResponse: The IME has switched to a new keyboard (e.g. French, English). - * This is typically called right after LatinIME.onStartInputViewInternal (when starting a new - * IME), but may happen at other times if the user explicitly requests a keyboard change. - */ - private static final LogStatement LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD = - new LogStatement("MainKeyboardViewSetKeyboard", false, false, "elementId", "locale", - "orientation", "width", "modeName", "action", "navigateNext", - "navigatePrevious", "clobberSettingsKey", "passwordInput", - "supportsSwitchingToShortcutIme", "hasShortcutKey", "languageSwitchKeyEnabled", - "isMultiLine", "tw", "th", - "keys"); - public static void mainKeyboardView_setKeyboard(final Keyboard keyboard, - final int orientation) { - final KeyboardId kid = keyboard.mId; - final boolean isPasswordView = kid.passwordInput(); - final ResearchLogger researchLogger = getInstance(); - researchLogger.setIsPasswordView(isPasswordView); - researchLogger.enqueueEvent(LOGSTATEMENT_MAINKEYBOARDVIEW_SETKEYBOARD, - KeyboardId.elementIdToName(kid.mElementId), - kid.mLocale + ":" + kid.mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET), - orientation, kid.mWidth, KeyboardId.modeName(kid.mMode), kid.imeAction(), - kid.navigateNext(), kid.navigatePrevious(), kid.mClobberSettingsKey, - isPasswordView, kid.mSupportsSwitchingToShortcutIme, kid.mHasShortcutKey, - kid.mLanguageSwitchKeyEnabled, kid.isMultiLine(), keyboard.mOccupiedWidth, - keyboard.mOccupiedHeight, keyboard.getSortedKeys()); - } - - /** - * Log a call to LatinIME.revertCommit(). - * - * SystemResponse: The IME has reverted commited text. This happens when the user enters - * a word, commits it by pressing space or punctuation, and then reverts the commit by hitting - * backspace. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_REVERTCOMMIT = - new LogStatement("LatinIMERevertCommit", true, false, "committedWord", - "originallyTypedWord", "separatorString"); - public static void latinIME_revertCommit(final String committedWord, - final String originallyTypedWord, final boolean isBatchMode, - final String separatorString) { - // TODO: Prioritize adding a unit test for this method (as it is especially complex) - // TODO: Update the UserRecording LogBuffer as well as the MainLogBuffer - final ResearchLogger researchLogger = getInstance(); - // - // 1. Remove separator LogUnit - final LogUnit lastLogUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); - // Check that we're not at the beginning of input - if (lastLogUnit == null) return; - // Check that we're after a separator - if (lastLogUnit.getWordsAsString() != null) return; - // Remove separator - final LogUnit separatorLogUnit = researchLogger.mMainLogBuffer.unshiftIn(); - - // 2. Add revert LogStatement - final LogUnit revertedLogUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); - if (revertedLogUnit == null) return; - if (!revertedLogUnit.getWordsAsString().equals(scrubDigitsFromString(committedWord))) { - // Any word associated with the reverted LogUnit has already had its digits scrubbed, so - // any digits in the committedWord argument must also be scrubbed for an accurate - // comparison. - return; - } - researchLogger.enqueueEvent(revertedLogUnit, LOGSTATEMENT_LATINIME_REVERTCOMMIT, - committedWord, originallyTypedWord, separatorString); - - // 3. Update the word associated with the LogUnit - revertedLogUnit.setWords(originallyTypedWord); - revertedLogUnit.setContainsUserDeletions(); - - // 4. Re-add the separator LogUnit - researchLogger.mMainLogBuffer.shiftIn(separatorLogUnit); - - // 5. Record stats - researchLogger.mStatistics.recordRevertCommit(SystemClock.uptimeMillis()); - } - - /** - * Log a call to PointerTracker.callListenerOnCancelInput(). - * - * UserAction: The user has canceled the input, e.g., by pressing down, but then removing - * outside the keyboard area. - * TODO: Verify - */ - private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCANCELINPUT = - new LogStatement("PointerTrackerCallListenerOnCancelInput", false, false); - public static void pointerTracker_callListenerOnCancelInput() { - getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCANCELINPUT); - } - - /** - * Log a call to PointerTracker.callListenerOnCodeInput(). - * - * SystemResponse: The user has entered a key through the normal tapping mechanism. - * LatinIME.onCodeInput will also be called. - */ - private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCODEINPUT = - new LogStatement("PointerTrackerCallListenerOnCodeInput", true, false, "code", - "outputText", "x", "y", "ignoreModifierKey", "altersCode", "isEnabled"); - public static void pointerTracker_callListenerOnCodeInput(final Key key, final int x, - final int y, final boolean ignoreModifierKey, final boolean altersCode, - final int code) { - if (key != null) { - String outputText = key.getOutputText(); - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONCODEINPUT, - Constants.printableCode(scrubDigitFromCodePoint(code)), - outputText == null ? null : scrubDigitsFromString(outputText.toString()), - x, y, ignoreModifierKey, altersCode, key.isEnabled()); - } - } - - /** - * Log a call to PointerTracker.callListenerCallListenerOnRelease(). - * - * UserAction: The user has released their finger or thumb from the screen. - */ - private static final LogStatement LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONRELEASE = - new LogStatement("PointerTrackerCallListenerOnRelease", true, false, "code", - "withSliding", "ignoreModifierKey", "isEnabled"); - public static void pointerTracker_callListenerOnRelease(final Key key, final int primaryCode, - final boolean withSliding, final boolean ignoreModifierKey) { - if (key != null) { - getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_CALLLISTENERONRELEASE, - Constants.printableCode(scrubDigitFromCodePoint(primaryCode)), withSliding, - ignoreModifierKey, key.isEnabled()); - } - } - - /** - * Log a call to PointerTracker.onDownEvent(). - * - * UserAction: The user has pressed down on a key. - * TODO: Differentiate with LatinIME.processMotionEvent. - */ - private static final LogStatement LOGSTATEMENT_POINTERTRACKER_ONDOWNEVENT = - new LogStatement("PointerTrackerOnDownEvent", true, false, "deltaT", "distanceSquared"); - public static void pointerTracker_onDownEvent(long deltaT, int distanceSquared) { - getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_ONDOWNEVENT, deltaT, - distanceSquared); - } - - /** - * Log a call to PointerTracker.onMoveEvent(). - * - * UserAction: The user has moved their finger while pressing on the screen. - * TODO: Differentiate with LatinIME.processMotionEvent(). - */ - private static final LogStatement LOGSTATEMENT_POINTERTRACKER_ONMOVEEVENT = - new LogStatement("PointerTrackerOnMoveEvent", true, false, "x", "y", "lastX", "lastY"); - public static void pointerTracker_onMoveEvent(final int x, final int y, final int lastX, - final int lastY) { - getInstance().enqueueEvent(LOGSTATEMENT_POINTERTRACKER_ONMOVEEVENT, x, y, lastX, lastY); - } - - /** - * Log a call to RichInputConnection.commitCompletion(). - * - * SystemResponse: The IME has committed a completion. A completion is an application- - * specific suggestion that is presented in a pop-up menu in the TextView. - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_COMMITCOMPLETION = - new LogStatement("RichInputConnectionCommitCompletion", true, false, "completionInfo"); - public static void richInputConnection_commitCompletion(final CompletionInfo completionInfo) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_COMMITCOMPLETION, - completionInfo); - } - - /** - * Log a call to RichInputConnection.revertDoubleSpacePeriod(). - * - * SystemResponse: The IME has reverted ". ", which had previously replaced two typed spaces. - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD = - new LogStatement("RichInputConnectionRevertDoubleSpacePeriod", false, false); - public static void richInputConnection_revertDoubleSpacePeriod() { - final ResearchLogger researchLogger = getInstance(); - // An extra LogUnit is added for the period; this is removed here because of the revert. - researchLogger.uncommitCurrentLogUnit(null, true /* dumpCurrentLogUnit */); - // TODO: This will probably be lost as the user backspaces further. Figure out how to put - // it into the right logUnit. - researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTDOUBLESPACEPERIOD); - } - - /** - * Log a call to RichInputConnection.revertSwapPunctuation(). - * - * SystemResponse: The IME has reverted a punctuation swap. - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_REVERTSWAPPUNCTUATION = - new LogStatement("RichInputConnectionRevertSwapPunctuation", false, false); - public static void richInputConnection_revertSwapPunctuation() { - getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_REVERTSWAPPUNCTUATION); - } - - /** - * Log a call to LatinIME.commitCurrentAutoCorrection(). - * - * SystemResponse: The IME has committed an auto-correction. An auto-correction changes the raw - * text input to another word (or words) that the user more likely desired to type. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION = - new LogStatement("LatinIMECommitCurrentAutoCorrection", true, true, "typedWord", - "autoCorrection", "separatorString"); - public static void latinIme_commitCurrentAutoCorrection(final String typedWord, - final String autoCorrection, final String separatorString, final boolean isBatchMode, - final SuggestedWords suggestedWords) { - final String scrubbedTypedWord = scrubDigitsFromString(typedWord); - final String scrubbedAutoCorrection = scrubDigitsFromString(autoCorrection); - final ResearchLogger researchLogger = getInstance(); - researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords); - researchLogger.onWordFinished(scrubbedAutoCorrection, isBatchMode); - - // Add the autocorrection logStatement at the end of the logUnit for the committed word. - // We have to do this after calling commitCurrentLogUnitAsWord, because it may split the - // current logUnit, and then we have to peek to get the logUnit reference back. - final LogUnit logUnit = researchLogger.mMainLogBuffer.peekLastLogUnit(); - // TODO: Add test to confirm that the commitCurrentAutoCorrection log statement should - // always be added to logUnit (if non-null) and not mCurrentLogUnit. - researchLogger.enqueueEvent(logUnit, LOGSTATEMENT_LATINIME_COMMITCURRENTAUTOCORRECTION, - scrubbedTypedWord, scrubbedAutoCorrection, separatorString); - } - - private boolean isExpectingCommitText = false; - - /** - * Log a call to RichInputConnection.commitText(). - * - * SystemResponse: The IME is committing text. This happens after the user has typed a word - * and then a space or punctuation key. - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT = - new LogStatement("RichInputConnectionCommitText", true, false, "newCursorPosition"); - public static void richInputConnection_commitText(final String committedWord, - final int newCursorPosition, final boolean isBatchMode) { - final ResearchLogger researchLogger = getInstance(); - // Only include opening and closing logSegments if private data is included - final String scrubbedWord = scrubDigitsFromString(committedWord); - if (!researchLogger.isExpectingCommitText) { - researchLogger.enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTIONCOMMITTEXT, - newCursorPosition); - researchLogger.commitCurrentLogUnitAsWord(scrubbedWord, Long.MAX_VALUE, isBatchMode); - } - researchLogger.isExpectingCommitText = false; - } - - /** - * Shared events for logging committed text. - * - * The "CommitTextEventHappened" LogStatement is written to the log even if privacy rules - * indicate that the word contents should not be logged. It has no contents, and only serves to - * record the event and thereby make it easier to calculate word-level statistics even when the - * word contents are unknown. - */ - private static final LogStatement LOGSTATEMENT_COMMITTEXT = - new LogStatement("CommitText", true /* isPotentiallyPrivate */, - false /* isPotentiallyRevealing */, "committedText", "isBatchMode"); - private static final LogStatement LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED = - new LogStatement("CommitTextEventHappened", false /* isPotentiallyPrivate */, - false /* isPotentiallyRevealing */); - private void enqueueCommitText(final String word, final boolean isBatchMode) { - // Event containing the word; will be published only if privacy checks pass - enqueueEvent(LOGSTATEMENT_COMMITTEXT, word, isBatchMode); - // Event not containing the word; will always be published - enqueueEvent(LOGSTATEMENT_COMMITTEXT_EVENT_HAPPENED); - } - - /** - * Log a call to RichInputConnection.deleteSurroundingText(). - * - * SystemResponse: The IME has deleted text. - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT = - new LogStatement("RichInputConnectionDeleteSurroundingText", true, false, - "beforeLength", "afterLength"); - public static void richInputConnection_deleteSurroundingText(final int beforeLength, - final int afterLength) { - getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_DELETESURROUNDINGTEXT, - beforeLength, afterLength); - } - - /** - * Log a call to RichInputConnection.finishComposingText(). - * - * SystemResponse: The IME has left the composing text as-is. - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT = - new LogStatement("RichInputConnectionFinishComposingText", false, false); - public static void richInputConnection_finishComposingText() { - getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_FINISHCOMPOSINGTEXT); - } - - /** - * Log a call to RichInputConnection.performEditorAction(). - * - * SystemResponse: The IME is invoking an action specific to the editor. - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_PERFORMEDITORACTION = - new LogStatement("RichInputConnectionPerformEditorAction", false, false, - "imeActionId"); - public static void richInputConnection_performEditorAction(final int imeActionId) { - getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_PERFORMEDITORACTION, - imeActionId); - } - - /** - * Log a call to RichInputConnection.sendKeyEvent(). - * - * SystemResponse: The IME is telling the TextView that a key is being pressed through an - * alternate channel. - * TODO: only for hardware keys? - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SENDKEYEVENT = - new LogStatement("RichInputConnectionSendKeyEvent", true, false, "eventTime", "action", - "code"); - public static void richInputConnection_sendKeyEvent(final KeyEvent keyEvent) { - getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SENDKEYEVENT, - keyEvent.getEventTime(), keyEvent.getAction(), keyEvent.getKeyCode()); - } - - /** - * Log a call to RichInputConnection.setComposingText(). - * - * SystemResponse: The IME is setting the composing text. Happens each time a character is - * entered. - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SETCOMPOSINGTEXT = - new LogStatement("RichInputConnectionSetComposingText", true, true, "text", - "newCursorPosition"); - public static void richInputConnection_setComposingText(final CharSequence text, - final int newCursorPosition) { - if (text == null) { - throw new RuntimeException("setComposingText is null"); - } - getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SETCOMPOSINGTEXT, text, - newCursorPosition); - } - - /** - * Log a call to RichInputConnection.setSelection(). - * - * SystemResponse: The IME is requesting that the selection change. User-initiated selection- - * change requests do not go through this method -- it's only when the system wants to change - * the selection. - */ - private static final LogStatement LOGSTATEMENT_RICHINPUTCONNECTION_SETSELECTION = - new LogStatement("RichInputConnectionSetSelection", true, false, "from", "to"); - public static void richInputConnection_setSelection(final int from, final int to) { - getInstance().enqueueEvent(LOGSTATEMENT_RICHINPUTCONNECTION_SETSELECTION, from, to); - } - - /** - * Log a call to SuddenJumpingTouchEventHandler.onTouchEvent(). - * - * SystemResponse: The IME has filtered input events in case of an erroneous sensor reading. - */ - private static final LogStatement LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT = - new LogStatement("SuddenJumpingTouchEventHandlerOnTouchEvent", true, false, - "motionEvent"); - public static void suddenJumpingTouchEventHandler_onTouchEvent(final MotionEvent me) { - if (me != null) { - getInstance().enqueueEvent(LOGSTATEMENT_SUDDENJUMPINGTOUCHEVENTHANDLER_ONTOUCHEVENT, - MotionEvent.obtain(me)); - } - } - - /** - * Log a call to SuggestionsView.setSuggestions(). - * - * SystemResponse: The IME is setting the suggestions in the suggestion strip. - */ - private static final LogStatement LOGSTATEMENT_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS = - new LogStatement("SuggestionStripViewSetSuggestions", true, true, "suggestedWords"); - public static void suggestionStripView_setSuggestions(final SuggestedWords suggestedWords) { - if (suggestedWords != null) { - getInstance().enqueueEvent(LOGSTATEMENT_SUGGESTIONSTRIPVIEW_SETSUGGESTIONS, - suggestedWords); - } - } - - /** - * The user has indicated a particular point in the log that is of interest. - * - * UserAction: From direct menu invocation. - */ - private static final LogStatement LOGSTATEMENT_USER_TIMESTAMP = - new LogStatement("UserTimestamp", false, false); - public void userTimestamp() { - getInstance().enqueueEvent(LOGSTATEMENT_USER_TIMESTAMP); - } - - /** - * Log a call to LatinIME.onEndBatchInput(). - * - * SystemResponse: The system has completed a gesture. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_ONENDBATCHINPUT = - new LogStatement("LatinIMEOnEndBatchInput", true, false, "enteredText", - "enteredWordPos", "suggestedWords"); - public static void latinIME_onEndBatchInput(final CharSequence enteredText, - final int enteredWordPos, final SuggestedWords suggestedWords) { - final ResearchLogger researchLogger = getInstance(); - if (!TextUtils.isEmpty(enteredText) && hasLetters(enteredText.toString())) { - researchLogger.mCurrentLogUnit.setWords(enteredText.toString()); - } - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_ONENDBATCHINPUT, enteredText, - enteredWordPos, suggestedWords); - researchLogger.mCurrentLogUnit.initializeSuggestions(suggestedWords); - researchLogger.mStatistics.recordGestureInput(enteredText.length(), - SystemClock.uptimeMillis()); - } - - private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE = - new LogStatement("LatinIMEHandleBackspace", true, false, "numCharacters"); - /** - * Log a call to LatinIME.handleBackspace() that is not a batch delete. - * - * UserInput: The user is deleting one or more characters by hitting the backspace key once. - * The covers single character deletes as well as deleting selections. - * - * @param numCharacters how many characters the backspace operation deleted - * @param shouldUncommitLogUnit whether to uncommit the last {@code LogUnit} in the - * {@code LogBuffer} - */ - public static void latinIME_handleBackspace(final int numCharacters, - final boolean shouldUncommitLogUnit) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE, numCharacters); - if (shouldUncommitLogUnit) { - ResearchLogger.getInstance().uncommitCurrentLogUnit( - null, true /* dumpCurrentLogUnit */); - } - } - - /** - * Log a call to LatinIME.handleBackspace() that is a batch delete. - * - * UserInput: The user is deleting a gestured word by hitting the backspace key once. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH = - new LogStatement("LatinIMEHandleBackspaceBatch", true, false, "deletedText", - "numCharacters"); - public static void latinIME_handleBackspace_batch(final CharSequence deletedText, - final int numCharacters) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLEBACKSPACE_BATCH, deletedText, - numCharacters); - researchLogger.mStatistics.recordGestureDelete(deletedText.length(), - SystemClock.uptimeMillis()); - researchLogger.uncommitCurrentLogUnit(deletedText.toString(), - false /* dumpCurrentLogUnit */); - } - - /** - * Log a long interval between user operation. - * - * UserInput: The user has not done anything for a while. - */ - private static final LogStatement LOGSTATEMENT_ONUSERPAUSE = new LogStatement("OnUserPause", - false, false, "intervalInMs"); - public static void onUserPause(final long interval) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_ONUSERPAUSE, interval); - } - - /** - * Record the current time in case the LogUnit is later split. - * - * If the current logUnit is split, then tapping, motion events, etc. before this time should - * be assigned to one LogUnit, and events after this time should go into the following LogUnit. - */ - public static void recordTimeForLogUnitSplit() { - final ResearchLogger researchLogger = getInstance(); - researchLogger.setSavedDownEventTime(SystemClock.uptimeMillis()); - researchLogger.mSavedDownEventTime = Long.MAX_VALUE; - } - - /** - * Log a call to LatinIME.handleSeparator() - * - * SystemResponse: The system is inserting a separator character, possibly performing auto- - * correction or other actions appropriate at the end of a word. - */ - private static final LogStatement LOGSTATEMENT_LATINIME_HANDLESEPARATOR = - new LogStatement("LatinIMEHandleSeparator", false, false, "primaryCode", - "isComposingWord"); - public static void latinIME_handleSeparator(final int primaryCode, - final boolean isComposingWord) { - final ResearchLogger researchLogger = getInstance(); - researchLogger.enqueueEvent(LOGSTATEMENT_LATINIME_HANDLESEPARATOR, primaryCode, - isComposingWord); - } - - /** - * Call this method when the logging system has attempted publication of an n-gram. - * - * Statistics are gathered about the success or failure. - * - * @param publishabilityResultCode a result code as defined by - * {@code MainLogBuffer.PUBLISHABILITY_*} - */ - static void recordPublishabilityResultCode(final int publishabilityResultCode) { - final ResearchLogger researchLogger = getInstance(); - final Statistics statistics = researchLogger.mStatistics; - statistics.recordPublishabilityResultCode(publishabilityResultCode); - } - - /** - * Log statistics. - * - * ContextualData, recorded at the end of a session. - */ - private static final LogStatement LOGSTATEMENT_STATISTICS = - new LogStatement("Statistics", false, false, "charCount", "letterCount", "numberCount", - "spaceCount", "deleteOpsCount", "wordCount", "isEmptyUponStarting", - "isEmptinessStateKnown", "averageTimeBetweenKeys", "averageTimeBeforeDelete", - "averageTimeDuringRepeatedDelete", "averageTimeAfterDelete", - "dictionaryWordCount", "splitWordsCount", "gestureInputCount", - "gestureCharsCount", "gesturesDeletedCount", "manualSuggestionsCount", - "revertCommitsCount", "correctedWordsCount", "autoCorrectionsCount", - "publishableCount", "unpublishableStoppingCount", - "unpublishableIncorrectWordCount", "unpublishableSampledTooRecentlyCount", - "unpublishableDictionaryUnavailableCount", "unpublishableMayContainDigitCount", - "unpublishableNotInDictionaryCount"); - private static void logStatistics() { - final ResearchLogger researchLogger = getInstance(); - final Statistics statistics = researchLogger.mStatistics; - researchLogger.enqueueEvent(LOGSTATEMENT_STATISTICS, statistics.mCharCount, - statistics.mLetterCount, statistics.mNumberCount, statistics.mSpaceCount, - statistics.mDeleteKeyCount, statistics.mWordCount, statistics.mIsEmptyUponStarting, - statistics.mIsEmptinessStateKnown, statistics.mKeyCounter.getAverageTime(), - statistics.mBeforeDeleteKeyCounter.getAverageTime(), - statistics.mDuringRepeatedDeleteKeysCounter.getAverageTime(), - statistics.mAfterDeleteKeyCounter.getAverageTime(), - statistics.mDictionaryWordCount, statistics.mSplitWordsCount, - statistics.mGesturesInputCount, statistics.mGesturesCharsCount, - statistics.mGesturesDeletedCount, statistics.mManualSuggestionsCount, - statistics.mRevertCommitsCount, statistics.mCorrectedWordsCount, - statistics.mAutoCorrectionsCount, statistics.mPublishableCount, - statistics.mUnpublishableStoppingCount, statistics.mUnpublishableIncorrectWordCount, - statistics.mUnpublishableSampledTooRecently, - statistics.mUnpublishableDictionaryUnavailable, - statistics.mUnpublishableMayContainDigit, statistics.mUnpublishableNotInDictionary); - } -} diff --git a/java/src/com/android/inputmethod/research/ResearchSettings.java b/java/src/com/android/inputmethod/research/ResearchSettings.java deleted file mode 100644 index c0bc03fde..000000000 --- a/java/src/com/android/inputmethod/research/ResearchSettings.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.content.SharedPreferences; - -import java.util.UUID; - -public final class ResearchSettings { - public static final String PREF_RESEARCH_LOGGER_UUID = "pref_research_logger_uuid"; - public static final String PREF_RESEARCH_LOGGER_ENABLED_FLAG = - "pref_research_logger_enabled_flag"; - public static final String PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH = - "pref_research_logger_has_seen_splash"; - public static final String PREF_RESEARCH_LAST_DIR_CLEANUP_TIME = - "pref_research_last_dir_cleanup_time"; - - private ResearchSettings() { - // Intentional empty constructor for singleton. - } - - public static String readResearchLoggerUuid(final SharedPreferences prefs) { - if (prefs.contains(PREF_RESEARCH_LOGGER_UUID)) { - return prefs.getString(PREF_RESEARCH_LOGGER_UUID, null); - } - // Generate a random string as uuid if not yet set - final String newUuid = UUID.randomUUID().toString(); - prefs.edit().putString(PREF_RESEARCH_LOGGER_UUID, newUuid).apply(); - return newUuid; - } - - public static boolean readResearchLoggerEnabledFlag(final SharedPreferences prefs) { - return prefs.getBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, false); - } - - public static void writeResearchLoggerEnabledFlag(final SharedPreferences prefs, - final boolean isEnabled) { - prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_ENABLED_FLAG, isEnabled).apply(); - } - - public static boolean readHasSeenSplash(final SharedPreferences prefs) { - return prefs.getBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, false); - } - - public static void writeHasSeenSplash(final SharedPreferences prefs, - final boolean hasSeenSplash) { - prefs.edit().putBoolean(PREF_RESEARCH_LOGGER_HAS_SEEN_SPLASH, hasSeenSplash).apply(); - } - - public static long readResearchLastDirCleanupTime(final SharedPreferences prefs) { - return prefs.getLong(PREF_RESEARCH_LAST_DIR_CLEANUP_TIME, 0L); - } - - public static void writeResearchLastDirCleanupTime(final SharedPreferences prefs, - final long lastDirCleanupTime) { - prefs.edit().putLong(PREF_RESEARCH_LAST_DIR_CLEANUP_TIME, lastDirCleanupTime).apply(); - } -} diff --git a/java/src/com/android/inputmethod/research/Statistics.java b/java/src/com/android/inputmethod/research/Statistics.java deleted file mode 100644 index fd323a104..000000000 --- a/java/src/com/android/inputmethod/research/Statistics.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.util.Log; - -import com.android.inputmethod.latin.Constants; -import com.android.inputmethod.latin.define.ProductionFlag; - -import java.util.concurrent.TimeUnit; - -public class Statistics { - private static final String TAG = Statistics.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - - // TODO: Cleanup comments to only including those giving meaningful information. - // Number of characters entered during a typing session - int mCharCount; - // Number of letter characters entered during a typing session - int mLetterCount; - // Number of number characters entered - int mNumberCount; - // Number of space characters entered - int mSpaceCount; - // Number of delete operations entered (taps on the backspace key) - int mDeleteKeyCount; - // Number of words entered during a session. - int mWordCount; - // Number of words found in the dictionary. - int mDictionaryWordCount; - // Number of words split and spaces automatically entered. - int mSplitWordsCount; - // Number of words entered during a session. - int mCorrectedWordsCount; - // Number of gestures that were input. - int mGesturesInputCount; - // Number of gestures that were deleted. - int mGesturesDeletedCount; - // Total number of characters in words entered by gesture. - int mGesturesCharsCount; - // Number of manual suggestions chosen. - int mManualSuggestionsCount; - // Number of times that autocorrection was invoked. - int mAutoCorrectionsCount; - // Number of times a commit was reverted in this session. - int mRevertCommitsCount; - // Whether the text field was empty upon editing - boolean mIsEmptyUponStarting; - boolean mIsEmptinessStateKnown; - - // Counts of how often an n-gram is collected or not, and the reasons for the decision. - // Keep consistent with publishability result code list in MainLogBuffer - int mPublishableCount; - int mUnpublishableStoppingCount; - int mUnpublishableIncorrectWordCount; - int mUnpublishableSampledTooRecently; - int mUnpublishableDictionaryUnavailable; - int mUnpublishableMayContainDigit; - int mUnpublishableNotInDictionary; - - // Timers to count average time to enter a key, first press a delete key, - // between delete keys, and then to return typing after a delete key. - final AverageTimeCounter mKeyCounter = new AverageTimeCounter(); - final AverageTimeCounter mBeforeDeleteKeyCounter = new AverageTimeCounter(); - final AverageTimeCounter mDuringRepeatedDeleteKeysCounter = new AverageTimeCounter(); - final AverageTimeCounter mAfterDeleteKeyCounter = new AverageTimeCounter(); - - static class AverageTimeCounter { - int mCount; - int mTotalTime; - - public void reset() { - mCount = 0; - mTotalTime = 0; - } - - public void add(long deltaTime) { - mCount++; - mTotalTime += deltaTime; - } - - public int getAverageTime() { - if (mCount == 0) { - return 0; - } - return mTotalTime / mCount; - } - } - - // To account for the interruptions when the user's attention is directed elsewhere, times - // longer than MIN_TYPING_INTERMISSION are not counted when estimating this statistic. - public static final long MIN_TYPING_INTERMISSION = TimeUnit.SECONDS.toMillis(2); - public static final long MIN_DELETION_INTERMISSION = TimeUnit.SECONDS.toMillis(10); - - // The last time that a tap was performed - private long mLastTapTime; - // The type of the last keypress (delete key or not) - boolean mIsLastKeyDeleteKey; - - private static final Statistics sInstance = new Statistics(); - - public static Statistics getInstance() { - return sInstance; - } - - private Statistics() { - reset(); - } - - public void reset() { - mCharCount = 0; - mLetterCount = 0; - mNumberCount = 0; - mSpaceCount = 0; - mDeleteKeyCount = 0; - mWordCount = 0; - mDictionaryWordCount = 0; - mSplitWordsCount = 0; - mCorrectedWordsCount = 0; - mGesturesInputCount = 0; - mGesturesDeletedCount = 0; - mManualSuggestionsCount = 0; - mRevertCommitsCount = 0; - mAutoCorrectionsCount = 0; - mIsEmptyUponStarting = true; - mIsEmptinessStateKnown = false; - mKeyCounter.reset(); - mBeforeDeleteKeyCounter.reset(); - mDuringRepeatedDeleteKeysCounter.reset(); - mAfterDeleteKeyCounter.reset(); - mGesturesCharsCount = 0; - mGesturesDeletedCount = 0; - mPublishableCount = 0; - mUnpublishableStoppingCount = 0; - mUnpublishableIncorrectWordCount = 0; - mUnpublishableSampledTooRecently = 0; - mUnpublishableDictionaryUnavailable = 0; - mUnpublishableMayContainDigit = 0; - mUnpublishableNotInDictionary = 0; - - mLastTapTime = 0; - mIsLastKeyDeleteKey = false; - } - - public void recordChar(int codePoint, long time) { - if (DEBUG) { - Log.d(TAG, "recordChar() called"); - } - if (codePoint == Constants.CODE_DELETE) { - mDeleteKeyCount++; - recordUserAction(time, true /* isDeletion */); - } else { - mCharCount++; - if (Character.isDigit(codePoint)) { - mNumberCount++; - } - if (Character.isLetter(codePoint)) { - mLetterCount++; - } - if (Character.isSpaceChar(codePoint)) { - mSpaceCount++; - } - recordUserAction(time, false /* isDeletion */); - } - } - - public void recordWordEntered(final boolean isDictionaryWord, - final boolean containsCorrection) { - mWordCount++; - if (isDictionaryWord) { - mDictionaryWordCount++; - } - if (containsCorrection) { - mCorrectedWordsCount++; - } - } - - public void recordSplitWords() { - mSplitWordsCount++; - } - - public void recordGestureInput(final int numCharsEntered, final long time) { - mGesturesInputCount++; - mGesturesCharsCount += numCharsEntered; - recordUserAction(time, false /* isDeletion */); - } - - public void setIsEmptyUponStarting(final boolean isEmpty) { - mIsEmptyUponStarting = isEmpty; - mIsEmptinessStateKnown = true; - } - - public void recordGestureDelete(final int length, final long time) { - mGesturesDeletedCount++; - recordUserAction(time, true /* isDeletion */); - } - - public void recordManualSuggestion(final long time) { - mManualSuggestionsCount++; - recordUserAction(time, false /* isDeletion */); - } - - public void recordAutoCorrection(final long time) { - mAutoCorrectionsCount++; - recordUserAction(time, false /* isDeletion */); - } - - public void recordRevertCommit(final long time) { - mRevertCommitsCount++; - recordUserAction(time, true /* isDeletion */); - } - - private void recordUserAction(final long time, final boolean isDeletion) { - final long delta = time - mLastTapTime; - if (isDeletion) { - if (delta < MIN_DELETION_INTERMISSION) { - if (mIsLastKeyDeleteKey) { - mDuringRepeatedDeleteKeysCounter.add(delta); - } else { - mBeforeDeleteKeyCounter.add(delta); - } - } else { - ResearchLogger.onUserPause(delta); - } - } else { - if (mIsLastKeyDeleteKey && delta < MIN_DELETION_INTERMISSION) { - mAfterDeleteKeyCounter.add(delta); - } else if (!mIsLastKeyDeleteKey && delta < MIN_TYPING_INTERMISSION) { - mKeyCounter.add(delta); - } else { - ResearchLogger.onUserPause(delta); - } - } - mIsLastKeyDeleteKey = isDeletion; - mLastTapTime = time; - } - - public void recordPublishabilityResultCode(final int publishabilityResultCode) { - // Keep consistent with publishability result code list in MainLogBuffer - switch (publishabilityResultCode) { - case MainLogBuffer.PUBLISHABILITY_PUBLISHABLE: - mPublishableCount++; - break; - case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_STOPPING: - mUnpublishableStoppingCount++; - break; - case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_INCORRECT_WORD_COUNT: - mUnpublishableIncorrectWordCount++; - break; - case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_SAMPLED_TOO_RECENTLY: - mUnpublishableSampledTooRecently++; - break; - case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_DICTIONARY_UNAVAILABLE: - mUnpublishableDictionaryUnavailable++; - break; - case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_MAY_CONTAIN_DIGIT: - mUnpublishableMayContainDigit++; - break; - case MainLogBuffer.PUBLISHABILITY_UNPUBLISHABLE_NOT_IN_DICTIONARY: - mUnpublishableNotInDictionary++; - break; - } - } -} diff --git a/java/src/com/android/inputmethod/research/Uploader.java b/java/src/com/android/inputmethod/research/Uploader.java deleted file mode 100644 index c7ea3e69d..000000000 --- a/java/src/com/android/inputmethod/research/Uploader.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.Manifest; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.os.BatteryManager; -import android.text.TextUtils; -import android.util.Log; - -import com.android.inputmethod.latin.R; -import com.android.inputmethod.latin.define.ProductionFlag; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.URL; - -/** - * Manages the uploading of ResearchLog files. - */ -public final class Uploader { - private static final String TAG = Uploader.class.getSimpleName(); - private static final boolean DEBUG = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - // Set IS_INHIBITING_AUTO_UPLOAD to true for local testing - private static final boolean IS_INHIBITING_UPLOAD = false - && ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS_DEBUG; - private static final int BUF_SIZE = 1024 * 8; - - private final Context mContext; - private final ResearchLogDirectory mResearchLogDirectory; - private final URL mUrl; - - public Uploader(final Context context) { - mContext = context; - mResearchLogDirectory = new ResearchLogDirectory(context); - - final String urlString = context.getString(R.string.research_logger_upload_url); - if (TextUtils.isEmpty(urlString)) { - mUrl = null; - return; - } - URL url = null; - try { - url = new URL(urlString); - } catch (final MalformedURLException e) { - Log.e(TAG, "Bad URL for uploading", e); - } - mUrl = url; - } - - public boolean isPossibleToUpload() { - return hasUploadingPermission() && mUrl != null && !IS_INHIBITING_UPLOAD; - } - - private boolean hasUploadingPermission() { - final PackageManager packageManager = mContext.getPackageManager(); - return packageManager.checkPermission(Manifest.permission.INTERNET, - mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED; - } - - public boolean isConvenientToUpload() { - return isExternallyPowered() && hasWifiConnection(); - } - - private boolean isExternallyPowered() { - final Intent intent = mContext.registerReceiver(null, new IntentFilter( - Intent.ACTION_BATTERY_CHANGED)); - final int pluggedState = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); - return pluggedState == BatteryManager.BATTERY_PLUGGED_AC - || pluggedState == BatteryManager.BATTERY_PLUGGED_USB; - } - - private boolean hasWifiConnection() { - final ConnectivityManager manager = - (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); - final NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); - return wifiInfo.isConnected(); - } - - public void doUpload() { - final File[] files = mResearchLogDirectory.getUploadableLogFiles(); - if (files == null) return; - for (final File file : files) { - uploadFile(file); - } - } - - private void uploadFile(final File file) { - if (DEBUG) { - Log.d(TAG, "attempting upload of " + file.getAbsolutePath()); - } - final int contentLength = (int) file.length(); - HttpURLConnection connection = null; - InputStream fileInputStream = null; - try { - fileInputStream = new FileInputStream(file); - connection = (HttpURLConnection) mUrl.openConnection(); - connection.setRequestMethod("PUT"); - connection.setDoOutput(true); - connection.setFixedLengthStreamingMode(contentLength); - final OutputStream outputStream = connection.getOutputStream(); - uploadContents(fileInputStream, outputStream); - if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - Log.d(TAG, "upload failed: " + connection.getResponseCode()); - final InputStream netInputStream = connection.getInputStream(); - final BufferedReader reader = new BufferedReader(new InputStreamReader( - netInputStream)); - String line; - while ((line = reader.readLine()) != null) { - Log.d(TAG, "| " + reader.readLine()); - } - reader.close(); - return; - } - file.delete(); - if (DEBUG) { - Log.d(TAG, "upload successful"); - } - } catch (final IOException e) { - Log.e(TAG, "Exception uploading file", e); - } finally { - if (fileInputStream != null) { - try { - fileInputStream.close(); - } catch (final IOException e) { - Log.e(TAG, "Exception closing uploaded file", e); - } - } - if (connection != null) { - connection.disconnect(); - } - } - } - - private static void uploadContents(final InputStream is, final OutputStream os) - throws IOException { - // TODO: Switch to NIO. - final byte[] buf = new byte[BUF_SIZE]; - int numBytesRead; - while ((numBytesRead = is.read(buf)) != -1) { - os.write(buf, 0, numBytesRead); - } - } -} diff --git a/java/src/com/android/inputmethod/research/UploaderService.java b/java/src/com/android/inputmethod/research/UploaderService.java deleted file mode 100644 index fd3f2f60e..000000000 --- a/java/src/com/android/inputmethod/research/UploaderService.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research; - -import android.app.AlarmManager; -import android.app.IntentService; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.SystemClock; - -/** - * Service to invoke the uploader. - * - * Can be regularly invoked, invoked on boot, etc. - */ -public final class UploaderService extends IntentService { - private static final String TAG = UploaderService.class.getSimpleName(); - public static final long RUN_INTERVAL = AlarmManager.INTERVAL_HOUR; - public static final String EXTRA_UPLOAD_UNCONDITIONALLY = UploaderService.class.getName() - + ".extra.UPLOAD_UNCONDITIONALLY"; - - public UploaderService() { - super("Research Uploader Service"); - } - - @Override - protected void onHandleIntent(final Intent intent) { - // We may reach this point either because the alarm fired, or because the system explicitly - // requested that an Upload occur. In the latter case, we want to cancel the alarm in case - // it's about to fire. - cancelAndRescheduleUploadingService(this, false /* needsRescheduling */); - - final Uploader uploader = new Uploader(this); - if (!uploader.isPossibleToUpload()) return; - if (isUploadingUnconditionally(intent.getExtras()) || uploader.isConvenientToUpload()) { - uploader.doUpload(); - } - cancelAndRescheduleUploadingService(this, true /* needsRescheduling */); - } - - private boolean isUploadingUnconditionally(final Bundle bundle) { - if (bundle == null) return false; - if (bundle.containsKey(EXTRA_UPLOAD_UNCONDITIONALLY)) { - return bundle.getBoolean(EXTRA_UPLOAD_UNCONDITIONALLY); - } - return false; - } - - /** - * Arrange for the UploaderService to be run on a regular basis. - * - * Any existing scheduled invocation of UploaderService is removed and optionally rescheduled. - * This may cause problems if this method is called so often that no scheduled invocation is - * ever run. But if the delay is short enough that it will go off when the user is sleeping, - * then there should be no starvation. - * - * @param context {@link Context} object - * @param needsRescheduling whether to schedule a future intent to be delivered to this service - */ - public static void cancelAndRescheduleUploadingService(final Context context, - final boolean needsRescheduling) { - final Intent intent = new Intent(context, UploaderService.class); - final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0); - final AlarmManager alarmManager = (AlarmManager) context.getSystemService( - Context.ALARM_SERVICE); - alarmManager.cancel(pendingIntent); - if (needsRescheduling) { - alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() - + UploaderService.RUN_INTERVAL, pendingIntent); - } - } -} diff --git a/java/src/com/android/inputmethod/research/ui/SplashScreen.java b/java/src/com/android/inputmethod/research/ui/SplashScreen.java deleted file mode 100644 index 78ed668d1..000000000 --- a/java/src/com/android/inputmethod/research/ui/SplashScreen.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.inputmethod.research.ui; - -import android.app.AlertDialog.Builder; -import android.app.Dialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.Intent; -import android.inputmethodservice.InputMethodService; -import android.net.Uri; -import android.os.IBinder; -import android.view.Window; -import android.view.WindowManager.LayoutParams; - -import com.android.inputmethod.latin.R.string; - -/** - * Show a dialog when the user first opens the keyboard. - * - * The splash screen is a modal dialog box presented when the user opens this keyboard for the first - * time. It is useful for giving specific warnings that must be shown to the user before use. - * - * While the splash screen does share with the setup wizard the common goal of presenting - * information to the user before use, they are presented at different times and with different - * capabilities. The setup wizard is launched by tapping on the icon, and walks the user through - * the setup process. It can, however, be bypassed by enabling the keyboard from Settings directly. - * The splash screen cannot be bypassed, and is therefore more appropriate for obtaining user - * consent. - */ -public class SplashScreen { - public interface UserConsentListener { - public void onSplashScreenUserClickedOk(); - } - - final UserConsentListener mListener; - final Dialog mSplashDialog; - - public SplashScreen(final InputMethodService inputMethodService, - final UserConsentListener listener) { - mListener = listener; - final Builder builder = new Builder(inputMethodService) - .setTitle(string.research_splash_title) - .setMessage(string.research_splash_content) - .setPositiveButton(android.R.string.yes, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mListener.onSplashScreenUserClickedOk(); - mSplashDialog.dismiss(); - } - }) - .setNegativeButton(android.R.string.no, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - final String packageName = inputMethodService.getPackageName(); - final Uri packageUri = Uri.parse("package:" + packageName); - final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, - packageUri); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - inputMethodService.startActivity(intent); - } - }) - .setCancelable(true) - .setOnCancelListener( - new OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - inputMethodService.requestHideSelf(0); - } - }); - mSplashDialog = builder.create(); - } - - /** - * Show the splash screen. - * - * The user must consent to the terms presented in the SplashScreen before they can use the - * keyboard. If they cancel instead, they are given the option to uninstall the keybard. - * - * @param windowToken {@link IBinder} to attach dialog to - */ - public void showSplashScreen(final IBinder windowToken) { - final Window window = mSplashDialog.getWindow(); - final LayoutParams lp = window.getAttributes(); - lp.token = windowToken; - lp.type = LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; - window.setAttributes(lp); - window.addFlags(LayoutParams.FLAG_ALT_FOCUSABLE_IM); - mSplashDialog.show(); - } - - public boolean isShowing() { - return mSplashDialog.isShowing(); - } -} |