diff options
Diffstat (limited to 'java/src')
143 files changed, 1198 insertions, 7340 deletions
diff --git a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java index d50dd3ee6..27896fd11 100644 --- a/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java +++ b/java/src/com/android/inputmethod/accessibility/AccessibilityUtils.java @@ -114,7 +114,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..58672ace7 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java +++ b/java/src/com/android/inputmethod/accessibility/KeyCodeDescriptionMapper.java @@ -285,15 +285,14 @@ public final class KeyCodeDescriptionMapper { if (index >= 0) { return context.getString(mKeyCodeMap.valueAt(index)); } - final String accentedLetter = getSpokenAccentedLetterDescriptionId(context, code); + final String accentedLetter = getSpokenAccentedLetterDescription(context, code); 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 emojiDescription = getSpokenEmojiDescription(context, code); + if (emojiDescription != null) { + return emojiDescription; } if (isDefinedNonCtrl) { return Character.toString((char) code); @@ -304,7 +303,7 @@ public final class KeyCodeDescriptionMapper { return context.getString(R.string.spoken_description_unknown, code); } - private String getSpokenAccentedLetterDescriptionId(final Context context, final int code) { + 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,7 +313,17 @@ 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; + } + + 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); + return TextUtils.isEmpty(spokenText) ? context.getString(R.string.spoken_emoji_unknown) : spokenText; } diff --git a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityDelegate.java index eed40f4a9..7cae9861c 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,6 +31,7 @@ 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; public class KeyboardAccessibilityDelegate<KV extends KeyboardView> extends AccessibilityDelegateCompat { @@ -37,7 +39,7 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView> protected final KeyDetector mKeyDetector; private Keyboard mKeyboard; private KeyboardAccessibilityNodeProvider mAccessibilityNodeProvider; - private Key mLastHoverKey; + private Key mCurrentHoverKey; public KeyboardAccessibilityDelegate(final KV keyboardView, final KeyDetector keyDetector) { super(); @@ -65,10 +67,31 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView> mKeyboard = keyboard; } - protected Keyboard getKeyboard() { + protected final Keyboard getKeyboard() { return mKeyboard; } + protected final void setCurrentHoverKey(final Key key) { + mCurrentHoverKey = key; + } + + protected final Key getCurrentHoverKey() { + return mCurrentHoverKey; + } + + /** + * 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 +137,157 @@ public class KeyboardAccessibilityDelegate<KV extends KeyboardView> } /** + * Get a key that a hover event is on. + * + * @param event The hover event. + * @return key The key that the <code>event</code> is on. + */ + protected final Key getHoverKey(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); + } + + /** * 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 + * @return {@code true} if the event is handled. */ - 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; - - 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$ + 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 = getHoverKey(event); + if (key != null) { + onHoverEnterKey(key); + } + setCurrentHoverKey(key); + } + + /** + * Process {@link MotionEvent#ACTION_HOVER_MOVE} event. + * + * @param event A hover move event. + */ + protected void onHoverMove(final MotionEvent event) { + final Key previousKey = getCurrentHoverKey(); + final Key key = getHoverKey(event); + if (key != previousKey) { + if (previousKey != null) { + onHoverExitKey(previousKey); } - return onHoverKey(key, event); + if (key != null) { + onHoverEnterKey(key); + } + } + if (key != null) { + onHoverMoveKey(key); } - return false; + setCurrentHoverKey(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 key = getHoverKey(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) { + simulateTouchEvent(MotionEvent.ACTION_DOWN, event); + simulateTouchEvent(MotionEvent.ACTION_UP, event); + onHoverExitKey(key); + } + setCurrentHoverKey(null); } /** - * 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. + * @param touchAction The action of the synthesizing touch event. + * @param event 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>. */ - private boolean onTransitionKey(final Key currentKey, final Key previousKey, + protected static MotionEvent synthesizeTouchEvent(final int touchAction, 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; + final long downTime = event.getDownTime(); + final long eventTime = event.getEventTime(); + final int actionIndex = event.getActionIndex(); + final float x = event.getX(actionIndex); + final float y = event.getY(actionIndex); + final int pointerId = event.getPointerId(actionIndex); + return MotionEvent.obtain(downTime, eventTime, touchAction, x, y, pointerId); } /** - * Handles a hover event on a key. If {@link Key} extended View, this would be analogous to - * calling View.onHoverEvent(MotionEvent). + * 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 onHoverEnterKey(final 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; - } - return true; + /** + * Handles a hover move event on a key. + * + * @param key The currently hovered key. + */ + protected void onHoverMoveKey(final Key key) { } + + /** + * Handles a hover exit event on a key. + * + * @param key The currently hovered key. + */ + protected void onHoverExitKey(final Key key) { + 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..a6997e2f9 100644 --- a/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java +++ b/java/src/com/android/inputmethod/accessibility/KeyboardAccessibilityNodeProvider.java @@ -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..ec6bb0156 100644 --- a/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java +++ b/java/src/com/android/inputmethod/accessibility/MainKeyboardAccessibilityDelegate.java @@ -17,13 +17,8 @@ package com.android.inputmethod.accessibility; import android.content.Context; -import android.os.SystemClock; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.util.SparseIntArray; -import android.view.MotionEvent; -import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.KeyDetector; import com.android.inputmethod.keyboard.Keyboard; import com.android.inputmethod.keyboard.KeyboardId; @@ -168,17 +163,13 @@ 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(text); + sendWindowStateChanged(R.string.announce_keyboard_hidden); } } 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/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/KeyboardLayoutSet.java b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java index cde5091c4..aa646994c 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardLayoutSet.java @@ -41,7 +41,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 +80,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") @@ -117,7 +116,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 +180,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 +191,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) { diff --git a/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java b/java/src/com/android/inputmethod/keyboard/KeyboardSwitcher.java index 61d51d1c9..ef5fabb41 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; @@ -127,7 +126,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..60d9a054d 100644 --- a/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java +++ b/java/src/com/android/inputmethod/keyboard/KeyboardTheme.java @@ -81,20 +81,6 @@ 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) { // TODO: This search algorithm isn't optimal if there are many themes. for (final KeyboardTheme theme : KEYBOARD_THEMES) { 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..95258ccdc 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 @@ -389,10 +383,6 @@ 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); } @@ -554,24 +544,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 +585,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 +697,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); @@ -765,19 +732,14 @@ public final class MainKeyboardView extends KeyboardView implements PointerTrack } /** - * 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) { + if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { + return mAccessibilityDelegate.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/PointerTracker.java b/java/src/com/android/inputmethod/keyboard/PointerTracker.java index 4777166ea..f80761132 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(); } @@ -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); } 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..0166802a4 100644 --- a/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java +++ b/java/src/com/android/inputmethod/keyboard/emoji/EmojiPageKeyboardView.java @@ -83,13 +83,15 @@ final class EmojiPageKeyboardView extends KeyboardView implements mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */); } + /** + * {@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) { + if (AccessibilityUtils.getInstance().isTouchExplorationEnabled()) { + return mAccessibilityDelegate.onHoverEvent(event); } - return mAccessibilityDelegate.dispatchHoverEvent(event); + return super.onHoverEvent(event); } /** @@ -102,7 +104,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 +121,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 +130,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 +168,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 +194,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 7b4bd3d36..0c4f902f4 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; @@ -68,6 +69,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; @@ -114,6 +120,16 @@ public final class EmojiPalettesView extends LinearLayout implements OnTabChange 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); } @@ -152,7 +168,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); @@ -165,6 +189,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 */); @@ -360,6 +386,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/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..40c915c8d 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); 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/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index e7ab02ac1..096b946d2 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,14 @@ 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 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 +199,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 +246,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 +302,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 +318,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; @@ -364,12 +361,13 @@ public final class BinaryDictionary extends Dictionary { } 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 +379,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 +417,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 +493,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..05d34767c 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. 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/DictionaryCollection.java b/java/src/com/android/inputmethod/latin/DictionaryCollection.java index e6e4e0938..53be28139 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, diff --git a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java index 301b832b6..7fa3d0479 100644 --- a/java/src/com/android/inputmethod/latin/DictionaryFacilitatorForSuggest.java +++ b/java/src/com/android/inputmethod/latin/DictionaryFacilitator.java @@ -19,14 +19,17 @@ 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.ExecutorUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; import com.android.inputmethod.latin.utils.SuggestionResults; @@ -37,16 +40,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,6 +61,7 @@ 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 = new String[] { @@ -69,7 +74,7 @@ public class DictionaryFacilitatorForSuggest { }; private static final Map<String, Class<? extends ExpandableBinaryDictionary>> - DICT_TYPE_TO_CLASS = CollectionUtils.newHashMap(); + DICT_TYPE_TO_CLASS = new HashMap<>(); static { DICT_TYPE_TO_CLASS.put(Dictionary.TYPE_USER_HISTORY, UserHistoryDictionary.class); @@ -94,7 +99,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 +167,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 +211,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 +230,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 +303,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)) { @@ -321,6 +336,7 @@ public class DictionaryFacilitatorForSuggest { for (final String dictType : DICT_TYPES_ORDERED_TO_GET_SUGGESTION) { dictionaries.closeDict(dictType); } + mDistracterFilter.close(); } // The main dictionary could have been loaded asynchronously. Don't cache the return value @@ -392,7 +408,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 +448,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, @@ -500,7 +516,7 @@ public class DictionaryFacilitatorForSuggest { return false; } - private int getMaxFrequency(final String word) { + public int getFrequency(final String word) { if (TextUtils.isEmpty(word)) { return Dictionary.NOT_A_PROBABILITY; } @@ -537,9 +553,16 @@ 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 ArrayList<LanguageModelParam> languageModelParams = + LanguageModelParam.createLanguageModelParamsFrom( + personalizationDataChunk.mTokens, + personalizationDataChunk.mTimestampInSeconds, + this /* dictionaryFacilitator */, spacingAndPunctuations, + mDistracterFilter); final ExpandableBinaryDictionary personalizationDict = mDictionaries.getSubDict(Dictionary.TYPE_PERSONALIZATION); if (personalizationDict == null || languageModelParams == null 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..b10bae01a 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; @@ -53,7 +54,6 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { 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; @@ -114,7 +114,8 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private boolean needsToMigrateDictionary(final int formatVersion) { // When we bump up the dictionary format version, the old version should be added to here // for supporting migration. Note that native code has to support reading such formats. - return formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING; + return formatVersion == FormatSpec.VERSION4_ONLY_FOR_TESTING + || formatVersion == FormatSpec.VERSION401; } public boolean isValidDictionaryLocked() { @@ -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,7 +324,11 @@ 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)) { + Log.e(TAG, "Cannot add n-gram entry."); + Log.e(TAG, " PrevWordsInfo: " + prevWordsInfo); + Log.e(TAG, " word: " + word); + } } /** diff --git a/java/src/com/android/inputmethod/latin/InputAttributes.java b/java/src/com/android/inputmethod/latin/InputAttributes.java index df4948322..e778a14f6 100644 --- a/java/src/com/android/inputmethod/latin/InputAttributes.java +++ b/java/src/com/android/inputmethod/latin/InputAttributes.java @@ -20,7 +20,6 @@ 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; @@ -214,7 +213,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)) 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..d329d2ce6 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.DistracterFilterUsingSuggestion; 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 DistracterFilterUsingSuggestion(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; @@ -256,7 +255,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (latinIme == null) { return; } - if (!latinIme.mSettings.getCurrent().isSuggestionStripVisible()) { + if (!latinIme.mSettings.getCurrent() + .isCurrentOrientationAllowingSuggestionsPerUserSettings()) { return; } removeMessages(MSG_RESUME_SUGGESTIONS); @@ -491,12 +491,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(); @@ -538,13 +532,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 +553,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,10 +598,8 @@ 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) { @@ -622,27 +612,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(); } @@ -666,9 +649,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mInputLogic.mConnection.finishComposingText(); mInputLogic.mConnection.endBatchEdit(); } - final DistracterFilter distracterFilter = createDistracterFilter(); PersonalizationDictionarySessionRegistrar.onConfigurationChanged(this, conf, - mInputLogic.mSuggest.mDictionaryFacilitator, distracterFilter); + mDictionaryFacilitator); super.onConfigurationChanged(conf); } @@ -687,9 +669,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (hasSuggestionStripView()) { mSuggestionStripView.setListener(this, view); } - if (LatinImeLogger.sVISUALDEBUG) { - mKeyPreviewBackingView.setBackgroundColor(0x10FF0000); - } } @Override @@ -762,10 +741,6 @@ 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); - } 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 +750,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; @@ -841,7 +815,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen currentSettingsValues = mSettings.getCurrent(); if (currentSettingsValues.mCorrectionEnabled) { - suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); + suggest.setAutoCorrectionThreshold( + currentSettingsValues.mAutoCorrectionThreshold); } switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), @@ -870,7 +845,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 +870,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 +883,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 +896,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 +956,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen @Override public void hideWindow() { - LatinImeLogger.commit(); mKeyboardSwitcher.onHideWindow(); if (TRACE) Debug.stopMethodTracing(); @@ -1017,9 +981,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } if (applicationSpecifiedCompletions == null) { setNeutralSuggestionStrip(); - if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { - ResearchLogger.latinIME_onDisplayCompletions(null); - } return; } @@ -1031,9 +992,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen 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); - } } private int getAdjustedBackingViewHeight() { @@ -1167,8 +1125,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 @@ -1352,7 +1309,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen currentSettings.mInputAttributes)) { return true; } - if (!currentSettings.isSuggestionStripVisible()) { + if (!currentSettings.isCurrentOrientationAllowingSuggestionsPerUserSettings()) { return false; } if (currentSettings.isApplicationSpecifiedCompletionsOn()) { @@ -1397,8 +1354,14 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final SettingsValues currentSettings = mSettings.getCurrent(); final boolean showSuggestions; - if (SuggestedWords.EMPTY == suggestedWords || suggestedWords.isPunctuationSuggestions() - || !currentSettings.isSuggestionsRequested()) { + // May show the important notice when there are no suggestions to show, + if (SuggestedWords.EMPTY == suggestedWords + // or the suggestion strip is expected to show punctuation suggestions, + || suggestedWords.isPunctuationSuggestions() + // or it's not requested to show suggestions by the input field, + || !currentSettings.isSuggestionsRequested() + // or the "show correction suggestions" settings is off by users preference. + || !currentSettings.isCurrentOrientationAllowingSuggestionsPerUserSettings()) { showSuggestions = !mSuggestionStripView.maybeShowImportantNoticeTitle( currentSettings.mInputAttributes); } else { @@ -1725,15 +1688,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 +1703,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/RichInputConnection.java b/java/src/com/android/inputmethod/latin/RichInputConnection.java index 2c54e10aa..9d72c64fc 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; } 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..9da0f8451 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; } @@ -124,7 +124,7 @@ public final class Suggest { suggestionResults.first(), suggestionResults.mLocale, isAllUpperCase, isFirstCharCapitalized, trailingSingleQuotesCount); firstSuggestion = firstSuggestedWordInfo.mWord; - if (SuggestedWordInfo.KIND_WHITELIST != firstSuggestedWordInfo.mKind) { + if (!firstSuggestedWordInfo.isKindOf(SuggestedWordInfo.KIND_WHITELIST)) { whitelistedWord = null; } else { whitelistedWord = firstSuggestion; @@ -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,7 +171,7 @@ public final class Suggest { } final ArrayList<SuggestedWordInfo> suggestionsContainer = - CollectionUtils.newArrayList(suggestionResults); + new ArrayList<>(suggestionResults); final int suggestionsCount = suggestionsContainer.size(); if (isFirstCharCapitalized || isAllUpperCase || 0 != trailingSingleQuotesCount) { for (int i = 0; i < suggestionsCount; ++i) { @@ -183,12 +183,6 @@ public final class Suggest { } } - 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. @@ -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..864942d04 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; @@ -79,13 +78,13 @@ public final class WordComposer { 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); + mPrevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; refreshTypedWordCache(); } @@ -117,7 +116,7 @@ public final class WordComposer { mIsBatchMode = false; mCursorPositionWithinWord = 0; mRejectedBatchModeSuggestion = null; - mPrevWordsInfo = new PrevWordsInfo(null); + mPrevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; refreshTypedWordCache(); } @@ -445,7 +444,7 @@ public final class WordComposer { // 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); + mPrevWordsInfo = PrevWordsInfo.EMPTY_PREV_WORDS_INFO; } public void resumeSuggestionOnLastComposedWord(final LastComposedWord lastComposedWord, 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..f36b42a40 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; } /** @@ -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. @@ -400,9 +376,6 @@ public final class InputLogic { 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 +397,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 +502,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. @@ -675,19 +641,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 +704,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 +718,15 @@ 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. + // We pass 2 to getPreviousWordForSuggestion when the previous code point is a word + // connector. Otherwise, we pass 1 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 */)); + settingsValues.mSpacingAndPunctuations, + settingsValues.mSpacingAndPunctuations.isWordConnector( + mConnection.getCodePointBeforeCursor()) ? 2 : 1)); } mConnection.setComposingText(getTextWithUnderline( mWordComposer.getTypedWord()), 1); @@ -786,10 +744,6 @@ public final class InputLogic { mSuggestionStripViewAccessor.dismissAddToDictionaryHint(); } inputTransaction.setRequiresUpdateSuggestions(); - if (settingsValues.mIsInternal) { - LatinImeLoggerUtils.onNonSeparator((char)codePoint, inputTransaction.mEvent.mX, - inputTransaction.mEvent.mY); - } } /** @@ -852,9 +806,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 +814,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 +884,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 +898,6 @@ public final class InputLogic { inputTransaction.setRequiresUpdateSuggestions(); } else { if (mLastComposedWord.canRevertCommit()) { - if (inputTransaction.mSettingsValues.mIsInternal) { - LatinImeLoggerUtils.onAutoCorrectionCancellation(); - } revertCommit(inputTransaction); return; } @@ -961,9 +906,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 +917,11 @@ 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.setCapitalizedModeAndPreviousWordAtStartComposingTime( + WordComposer.CAPS_MODE_OFF, + getPrevWordsInfoFromNthPreviousWordForSuggestion( + inputTransaction.mSettingsValues.mSpacingAndPunctuations, 1)); return; } } else if (SpaceState.SWAP_PUNCTUATION == inputTransaction.mSpaceState) { @@ -993,10 +940,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 +974,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,15 +981,12 @@ 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( @@ -1082,9 +1018,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,10 +1101,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; } @@ -1212,15 +1141,21 @@ public final class InputLogic { if (!mConnection.hasSelection()) { return; // No selection } + 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())) { + || !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.initialize(selectionStart, selectionEnd, selectedText.toString(), settingsValues.mLocale, settingsValues.mSpacingAndPunctuations.mSortedWordSeparators); // We trim leading and trailing whitespace. @@ -1228,11 +1163,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()); @@ -1250,7 +1182,7 @@ public final class InputLogic { 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 +1201,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 @@ -1349,7 +1281,7 @@ 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) { suggestions.add(new SuggestedWordInfo(typedWord, @@ -1462,8 +1394,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 +1404,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) { @@ -1515,17 +1446,7 @@ public final class InputLogic { mLatinIME.getCoordinatesForCurrentKeyboard(codePoints), prevWordsInfo); 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(); @@ -1609,8 +1530,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 +1710,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 +1741,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 +1782,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 +1808,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 +1847,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)) { 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..be658ceff 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 */ 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/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..67ad54fb7 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; @@ -60,10 +61,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 +73,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..9e7e07e11 100644 --- a/java/src/com/android/inputmethod/latin/settings/DebugSettings.java +++ b/java/src/com/android/inputmethod/latin/settings/DebugSettings.java @@ -29,7 +29,6 @@ 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.R; import com.android.inputmethod.latin.debug.ExternalDictionaryGetterForDebug; import com.android.inputmethod.latin.utils.ApplicationUtils; @@ -40,8 +39,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 = @@ -58,11 +55,8 @@ public final class DebugSettings extends PreferenceFragment 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 +65,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) { @@ -163,27 +142,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..af46aad96 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsFragment.java @@ -41,7 +41,6 @@ import com.android.inputmethod.keyboard.KeyboardTheme; import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.SubtypeSwitcher; -import com.android.inputmethod.latin.define.ProductionFlag; import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager; import com.android.inputmethod.latin.userdictionary.UserDictionaryList; import com.android.inputmethod.latin.userdictionary.UserDictionarySettings; @@ -59,7 +58,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 +151,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 +164,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 +201,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 = @@ -299,9 +298,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..6c6e79e0f 100644 --- a/java/src/com/android/inputmethod/latin/settings/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/settings/SettingsValues.java @@ -125,9 +125,11 @@ public final class SettingsValues { 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); @@ -171,7 +173,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) { @@ -188,10 +190,11 @@ public final class SettingsValues { public boolean isSuggestionsRequested() { return mInputAttributes.mIsSettingsSuggestionStripOn - && (mCorrectionEnabled || isSuggestionStripVisible()); + && (mCorrectionEnabled + || isCurrentOrientationAllowingSuggestionsPerUserSettings()); } - public boolean isSuggestionStripVisible() { + public boolean isCurrentOrientationAllowingSuggestionsPerUserSettings() { return (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_VALUE) || (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE && mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT); 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..cc80e6f21 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerSession.java @@ -24,7 +24,6 @@ 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; @@ -44,10 +43,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); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidWordLevelSpellCheckerSession.java index cf26000d5..d7953e6e7 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) { 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..1331d52d5 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. 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..aa59db678 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/MoreSuggestionsView.java @@ -22,11 +22,12 @@ import android.util.Log; 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 +36,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); } diff --git a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java index 619804afa..4a5a7f004 100644 --- a/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java +++ b/java/src/com/android/inputmethod/latin/suggestions/SuggestionStripView.java @@ -47,12 +47,9 @@ 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.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,13 +87,16 @@ 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 ImageButton voiceKey, + final ViewGroup addToDictionaryStrip, final View importantNoticeStrip) { + mSuggestionStripView = suggestionStripView; mSuggestionsStrip = suggestionsStrip; mVoiceKey = voiceKey; mAddToDictionaryStrip = addToDictionaryStrip; @@ -104,7 +104,10 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick showSuggestionsStrip(false /* voiceKeyEnabled */); } - 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); @@ -156,7 +159,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, mVoiceKey, mAddToDictionaryStrip, mImportantNoticeStrip); for (int pos = 0; pos < SuggestedWords.MAX_SUGGESTIONS; pos++) { @@ -217,16 +220,10 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick 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()); } diff --git a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java index 21426d1eb..80d4cc44f 100644 --- a/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java +++ b/java/src/com/android/inputmethod/latin/userdictionary/UserDictionaryAddWordContents.java @@ -256,7 +256,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/DistracterFilterUsingSuggestion.java b/java/src/com/android/inputmethod/latin/utils/DistracterFilterUsingSuggestion.java new file mode 100644 index 000000000..b9c7f5671 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/DistracterFilterUsingSuggestion.java @@ -0,0 +1,243 @@ +/* + * 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.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.Dictionary; +import com.android.inputmethod.latin.DictionaryFacilitator; +import com.android.inputmethod.latin.PrevWordsInfo; +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 DistracterFilterUsingSuggestion implements DistracterFilter { + private static final String TAG = DistracterFilterUsingSuggestion.class.getSimpleName(); + private static final boolean DEBUG = false; + + 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 DictionaryFacilitator mDictionaryFacilitator; + private Keyboard mKeyboard; + private final Object mLock = new Object(); + + /** + * Create a DistracterFilter instance. + * + * @param context the context. + */ + public DistracterFilterUsingSuggestion(final Context context) { + mContext = context; + mLocaleToSubtypeMap = new HashMap<>(); + mLocaleToKeyboardMap = new HashMap<>(); + mDictionaryFacilitator = new DictionaryFacilitator(); + mKeyboard = null; + } + + @Override + public void close() { + mDictionaryFacilitator.closeDictionaries(); + } + + @Override + public void updateEnabledSubtypes(final List<InputMethodSubtype> enabledSubtypes) { + final Map<Locale, InputMethodSubtype> newLocaleToSubtypeMap = new HashMap<>(); + if (enabledSubtypes != null) { + for (final InputMethodSubtype subtype : enabledSubtypes) { + final Locale locale = SubtypeLocaleUtils.getSubtypeLocale(subtype); + if (newLocaleToSubtypeMap.containsKey(locale)) { + // Multiple subtypes are enabled for one locale. + // TODO: Investigate what we should do for this case. + continue; + } + newLocaleToSubtypeMap.put(locale, subtype); + } + } + if (mLocaleToSubtypeMap.equals(newLocaleToSubtypeMap)) { + // Enabled subtypes have not been changed. + return; + } + synchronized (mLock) { + mLocaleToSubtypeMap.clear(); + mLocaleToSubtypeMap.putAll(newLocaleToSubtypeMap); + mLocaleToKeyboardMap.clear(); + } + } + + private boolean isDistracter( + final SuggestionResults suggestionResults, final String consideredWord) { + int perfectMatchProbability = Dictionary.NOT_A_PROBABILITY; + for (final SuggestedWordInfo suggestedWordInfo : suggestionResults) { + if (suggestedWordInfo.mWord.equals(consideredWord)) { + perfectMatchProbability = mDictionaryFacilitator.getFrequency(consideredWord); + continue; + } + // Exact match can include case errors, accent errors, digraph conversions. + final boolean isExactMatch = suggestedWordInfo.isExactMatch(); + final boolean isExactMatchWithIntentionalOmission = + suggestedWordInfo.isExactMatchWithIntentionalOmission(); + + if (DEBUG) { + final float normalizedScore = BinaryDictionaryUtils.calcNormalizedScore( + consideredWord, suggestedWordInfo.mWord, suggestedWordInfo.mScore); + Log.d(TAG, "consideredWord: " + consideredWord); + Log.d(TAG, "top suggestion: " + suggestedWordInfo.mWord); + Log.d(TAG, "suggestionScore: " + suggestedWordInfo.mScore); + Log.d(TAG, "normalizedScore: " + normalizedScore); + Log.d(TAG, "isExactMatch: " + isExactMatch); + Log.d(TAG, "isExactMatchWithIntentionalOmission: " + + isExactMatchWithIntentionalOmission); + } + if (perfectMatchProbability != Dictionary.NOT_A_PROBABILITY) { + final int topNonPerfectProbability = mDictionaryFacilitator.getFrequency( + suggestedWordInfo.mWord); + if (DEBUG) { + Log.d(TAG, "perfectMatchProbability: " + perfectMatchProbability); + Log.d(TAG, "topNonPerfectProbability: " + topNonPerfectProbability); + } + if (perfectMatchProbability > topNonPerfectProbability) { + return false; + } + } + return isExactMatch || isExactMatchWithIntentionalOmission; + } + return false; + } + + private void loadKeyboardForLocale(final Locale newLocale) { + final Keyboard cachedKeyboard = mLocaleToKeyboardMap.get(newLocale); + if (cachedKeyboard != null) { + mKeyboard = cachedKeyboard; + return; + } + final InputMethodSubtype subtype; + synchronized (mLock) { + 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 { + 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) { + 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) { + return false; + } + final WordComposer composer = new WordComposer(); + final int[] codePoints = StringUtils.toCodePointArray(testedWord); + final int[] coordinates = mKeyboard.getCoordinates(codePoints); + composer.setComposingWord(codePoints, coordinates, PrevWordsInfo.EMPTY_PREV_WORDS_INFO); + + final int trailingSingleQuotesCount = StringUtils.getTrailingSingleQuotesCount(testedWord); + final String consideredWord = trailingSingleQuotesCount > 0 ? + testedWord.substring(0, testedWord.length() - trailingSingleQuotesCount) : + testedWord; + final AsyncResultHolder<Boolean> holder = new AsyncResultHolder<>(); + ExecutorUtils.getExecutor("check distracters").execute(new Runnable() { + @Override + public void run() { + final SuggestionResults suggestionResults = + mDictionaryFacilitator.getSuggestionResults( + composer, PrevWordsInfo.EMPTY_PREV_WORDS_INFO, + mKeyboard.getProximityInfo(), true /* blockOffensiveWords */, + null /* additionalFeaturesOptions */, 0 /* sessionId */, + null /* rawSuggestions */); + if (suggestionResults.isEmpty()) { + holder.set(false); + return; + } + holder.set(isDistracter(suggestionResults, consideredWord)); + } + }); + // It's OK to block the distracter filtering, but the dictionary lookup should be done + // sequentially using ExecutorUtils. + return holder.get(false /* defaultValue */, Constants.GET_SUGGESTED_WORDS_TIMEOUT); + } +} 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/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/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..4ed0f0a94 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); 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(); - } -} |