diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin')
8 files changed, 338 insertions, 141 deletions
diff --git a/java/src/com/android/inputmethod/latin/InputView.java b/java/src/com/android/inputmethod/latin/InputView.java new file mode 100644 index 000000000..0dcb811b5 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/InputView.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.widget.LinearLayout; + +public class InputView extends LinearLayout { + private View mSuggestionsContainer; + private View mKeyboardView; + private int mKeyboardTopPadding; + + private boolean mIsForwardingEvent; + private final Rect mInputViewRect = new Rect(); + private final Rect mEventForwardingRect = new Rect(); + private final Rect mEventReceivingRect = new Rect(); + + public InputView(Context context, AttributeSet attrs) { + super(context, attrs, 0); + } + + public void setKeyboardGeometry(int keyboardTopPadding) { + mKeyboardTopPadding = keyboardTopPadding; + } + + @Override + protected void onFinishInflate() { + mSuggestionsContainer = findViewById(R.id.suggestions_container); + mKeyboardView = findViewById(R.id.keyboard_view); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent me) { + if (mSuggestionsContainer.getVisibility() == VISIBLE + && mKeyboardView.getVisibility() == VISIBLE + && forwardTouchEvent(me)) { + return true; + } + return super.dispatchTouchEvent(me); + } + + // The touch events that hit the top padding of keyboard should be forwarded to SuggestionsView. + private boolean forwardTouchEvent(MotionEvent me) { + final Rect rect = mInputViewRect; + this.getGlobalVisibleRect(rect); + final int x = (int)me.getX() + rect.left; + final int y = (int)me.getY() + rect.top; + + final Rect forwardingRect = mEventForwardingRect; + mKeyboardView.getGlobalVisibleRect(forwardingRect); + if (!mIsForwardingEvent && !forwardingRect.contains(x, y)) { + return false; + } + + final int forwardingLimitY = forwardingRect.top + mKeyboardTopPadding; + boolean sendToTarget = false; + + switch (me.getAction()) { + case MotionEvent.ACTION_DOWN: + if (y < forwardingLimitY) { + // This down event and further move and up events should be forwarded to the target. + mIsForwardingEvent = true; + sendToTarget = true; + } + break; + case MotionEvent.ACTION_MOVE: + sendToTarget = mIsForwardingEvent; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + sendToTarget = mIsForwardingEvent; + mIsForwardingEvent = false; + break; + } + + if (!sendToTarget) { + return false; + } + + final Rect receivingRect = mEventReceivingRect; + mSuggestionsContainer.getGlobalVisibleRect(receivingRect); + final int translatedX = x - receivingRect.left; + final int translatedY; + if (y < forwardingLimitY) { + // The forwarded event should have coordinates that are inside of the target. + translatedY = Math.min(y - receivingRect.top, receivingRect.height() - 1); + } else { + translatedY = y - receivingRect.top; + } + me.setLocation(translatedX, translatedY); + mSuggestionsContainer.dispatchTouchEvent(me); + return true; + } +} diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index ff0226684..32649d5a1 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -28,6 +28,7 @@ import android.content.res.Resources; import android.inputmethodservice.InputMethodService; import android.media.AudioManager; import android.net.ConnectivityManager; +import android.os.Build; import android.os.Debug; import android.os.Message; import android.os.SystemClock; @@ -56,6 +57,7 @@ import com.android.inputmethod.compat.InputMethodManagerCompatWrapper; import com.android.inputmethod.compat.InputMethodServiceCompatWrapper; import com.android.inputmethod.compat.InputTypeCompatUtils; import com.android.inputmethod.compat.SuggestionSpanUtils; +import com.android.inputmethod.compat.VibratorCompatWrapper; import com.android.inputmethod.deprecated.LanguageSwitcherProxy; import com.android.inputmethod.deprecated.VoiceProxy; import com.android.inputmethod.deprecated.recorrection.Recorrection; @@ -207,9 +209,12 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private long mLastKeyTime; private AudioManager mAudioManager; - private static float mFxVolume = -1.0f; // just a default value to be updated runtime + private float mFxVolume = -1.0f; // default volume private boolean mSilentModeOn; // System-wide current configuration + private VibratorCompatWrapper mVibrator; + private long mKeypressVibrationDuration = -1; + // TODO: Move this flag to VoiceProxy private boolean mConfigurationChanging; @@ -236,7 +241,8 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar private static final int MSG_SET_BIGRAM_PREDICTIONS = 7; private static final int MSG_START_ORIENTATION_CHANGE = 8; private static final int MSG_START_INPUT_VIEW = 9; - private static final int MSG_RESTORE_KEYBOARD_LAYOUT = 10; + private static final int MSG_DISPLAY_COMPLETIONS = 10; + private static final int MSG_RESTORE_KEYBOARD_LAYOUT = 11; public UIHandler(LatinIME outerInstance) { super(outerInstance); @@ -288,6 +294,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar case MSG_START_INPUT_VIEW: latinIme.onStartInputView((EditorInfo)msg.obj, false); break; + case MSG_DISPLAY_COMPLETIONS: + latinIme.onDisplayCompletions((CompletionInfo[])msg.obj); + break; case MSG_RESTORE_KEYBOARD_LAYOUT: removeMessages(MSG_UPDATE_SHIFT_STATE); ((KeyboardLayoutState)msg.obj).restore(); @@ -412,6 +421,18 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } return false; } + + public boolean postDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) { + if (hasMessages(MSG_START_INPUT_VIEW) || hasMessages(MSG_DISPLAY_COMPLETIONS)) { + removeMessages(MSG_DISPLAY_COMPLETIONS); + // Postpone onDisplayCompletions by ACCUMULATE_START_INPUT_VIEW_DELAY. + sendMessageDelayed( + obtainMessage(MSG_DISPLAY_COMPLETIONS, applicationSpecifiedCompletions), + ACCUMULATE_START_INPUT_VIEW_DELAY); + return true; + } + return false; + } } @Override @@ -433,13 +454,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mSubtypeSwitcher = SubtypeSwitcher.getInstance(); mKeyboardSwitcher = KeyboardSwitcher.getInstance(); mRecorrection = Recorrection.getInstance(); + mVibrator = VibratorCompatWrapper.getInstance(this); DEBUG = LatinImeLogger.sDBG; - loadSettings(); - final Resources res = getResources(); mResources = res; + loadSettings(); + Utils.GCUtils.getInstance().reset(); boolean tryGC = true; for (int i = 0; i < Utils.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) { @@ -480,6 +502,7 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mSettingsValues = new Settings.Values(mPrefs, this, mSubtypeSwitcher.getInputLocaleStr()); resetContactsDictionary(null == mSuggest ? null : mSuggest.getContactsDictionary()); updateSoundEffectVolume(); + updateKeypressVibrationDuration(); } private void initSuggest() { @@ -611,6 +634,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mSuggestionsView = (SuggestionsView) view.findViewById(R.id.suggestions_view); if (mSuggestionsView != null) mSuggestionsView.setListener(this, view); + if (LatinImeLogger.sVISUALDEBUG) { + mKeyPreviewBackingView.setBackgroundColor(0x10FF0000); + } } @Override @@ -913,6 +939,9 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar @Override public void onDisplayCompletions(CompletionInfo[] applicationSpecifiedCompletions) { + if (mHandler.postDisplayCompletions(applicationSpecifiedCompletions)) { + return; + } if (DEBUG) { Log.i(TAG, "Received completions:"); if (applicationSpecifiedCompletions != null) { @@ -1496,8 +1525,6 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (!TextUtils.isEmpty(typedWord) && !typedWord.equals(mBestWord)) { InputConnectionCompatUtils.commitCorrection( ic, mLastSelectionEnd - typedWord.length(), typedWord, mBestWord); - if (mSuggestionsView != null) - mSuggestionsView.onAutoCorrectionInverted(mBestWord); } } if (Keyboard.CODE_SPACE == primaryCode) { @@ -2052,13 +2079,14 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar // update sound effect volume private void updateSoundEffectVolume() { - if (mAudioManager == null) { - mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - if (mAudioManager == null) return; + final String[] volumePerHardwareList = mResources.getStringArray(R.array.keypress_volumes); + final String hardwarePrefix = Build.HARDWARE + ","; + for (final String element : volumePerHardwareList) { + if (element.startsWith(hardwarePrefix)) { + mFxVolume = Float.parseFloat(element.substring(element.lastIndexOf(',') + 1)); + break; + } } - // This aligns with the current media volume minus 6dB - mFxVolume = (float) mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC) - / (float) mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) / 4.0f; } // update flags for silent mode @@ -2070,6 +2098,19 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar mSilentModeOn = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL); } + private void updateKeypressVibrationDuration() { + final String[] durationPerHardwareList = mResources.getStringArray( + R.array.keypress_vibration_durations); + final String hardwarePrefix = Build.HARDWARE + ","; + for (final String element : durationPerHardwareList) { + if (element.startsWith(hardwarePrefix)) { + mKeypressVibrationDuration = + Long.parseLong(element.substring(element.lastIndexOf(',') + 1)); + break; + } + } + } + private void playKeyClick(int primaryCode) { // if mAudioManager is null, we don't have the ringer state yet // mAudioManager will be set by updateRingerMode @@ -2079,17 +2120,20 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar } } if (isSoundOn()) { - int sound = AudioManager.FX_KEYPRESS_STANDARD; + final int sound; switch (primaryCode) { - case Keyboard.CODE_DELETE: - sound = AudioManager.FX_KEYPRESS_DELETE; - break; - case Keyboard.CODE_ENTER: - sound = AudioManager.FX_KEYPRESS_RETURN; - break; - case Keyboard.CODE_SPACE: - sound = AudioManager.FX_KEYPRESS_SPACEBAR; - break; + case Keyboard.CODE_DELETE: + sound = AudioManager.FX_KEYPRESS_DELETE; + break; + case Keyboard.CODE_ENTER: + sound = AudioManager.FX_KEYPRESS_RETURN; + break; + case Keyboard.CODE_SPACE: + sound = AudioManager.FX_KEYPRESS_SPACEBAR; + break; + default: + sound = AudioManager.FX_KEYPRESS_STANDARD; + break; } mAudioManager.playSoundEffect(sound, mFxVolume); } @@ -2099,11 +2143,16 @@ public class LatinIME extends InputMethodServiceCompatWrapper implements Keyboar if (!mSettingsValues.mVibrateOn) { return; } - LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); - if (inputView != null) { - inputView.performHapticFeedback( - HapticFeedbackConstants.KEYBOARD_TAP, - HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + if (mKeypressVibrationDuration < 0) { + // Go ahead with the system default + LatinKeyboardView inputView = mKeyboardSwitcher.getKeyboardView(); + if (inputView != null) { + inputView.performHapticFeedback( + HapticFeedbackConstants.KEYBOARD_TAP, + HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + } + } else if (mVibrator != null) { + mVibrator.vibrate(mKeypressVibrationDuration); } } diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestions.java b/java/src/com/android/inputmethod/latin/MoreSuggestions.java index 24011c41e..9a59ef2e0 100644 --- a/java/src/com/android/inputmethod/latin/MoreSuggestions.java +++ b/java/src/com/android/inputmethod/latin/MoreSuggestions.java @@ -92,8 +92,9 @@ public class MoreSuggestions extends Keyboard { } mNumColumnsInRow[row] = pos - rowStartPos; mNumRows = row + 1; - mWidth = mOccupiedWidth = Math.max(minWidth, calcurateMaxRowWidth(fromPos, pos)); - mHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; + mBaseWidth = mOccupiedWidth = Math.max( + minWidth, calcurateMaxRowWidth(fromPos, pos)); + mBaseHeight = mOccupiedHeight = mNumRows * mDefaultRowHeight + mVerticalGap; return pos - fromPos; } @@ -149,26 +150,22 @@ public class MoreSuggestions extends Keyboard { public int getWidth(int pos) { final int numColumnInRow = getNumColumnInRow(pos); - return (mWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; + return (mOccupiedWidth - mDividerWidth * (numColumnInRow - 1)) / numColumnInRow; } - public int getFlags(int pos) { - int rowFlags = 0; - + public void markAsEdgeKey(Key key, int pos) { final int row = mRowNumbers[pos]; if (row == 0) - rowFlags |= Keyboard.EDGE_BOTTOM; + key.markAsBottomEdge(this); if (row == mNumRows - 1) - rowFlags |= Keyboard.EDGE_TOP; + key.markAsTopEdge(this); final int numColumnInRow = mNumColumnsInRow[row]; final int column = getColumnNumber(pos); if (column == 0) - rowFlags |= Keyboard.EDGE_LEFT; + key.markAsLeftEdge(this); if (column == numColumnInRow - 1) - rowFlags |= Keyboard.EDGE_RIGHT; - - return rowFlags; + key.markAsRightEdge(this); } } @@ -213,7 +210,8 @@ public class MoreSuggestions extends Keyboard { final int index = pos + SUGGESTION_CODE_BASE; final Key key = new Key( params, word, info, null, index, null, x, y, width, - params.mDefaultRowHeight, params.getFlags(pos)); + params.mDefaultRowHeight); + params.markAsEdgeKey(key, pos); params.onAddKey(key); final int columnNumber = params.getColumnNumber(pos); final int numColumnInRow = params.getNumColumnInRow(pos); diff --git a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java b/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java index 15a0cec2e..5a2eb1632 100644 --- a/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/MoreSuggestionsView.java @@ -145,13 +145,6 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel { // Nothing to do with. } - private final View.OnTouchListener mMotionEventDelegate = new View.OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent me) { - return MoreSuggestionsView.this.dispatchTouchEvent(me); - } - }; - @Override public void showMoreKeysPanel(View parentView, Controller controller, int pointX, int pointY, PopupWindow window, KeyboardActionListener listener) { @@ -170,9 +163,7 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel { - (container.getMeasuredHeight() - container.getPaddingBottom()) + parentView.getPaddingTop() + mCoordinates[1]; - container.setOnTouchListener(mMotionEventDelegate); window.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED); - window.setFocusable(true); window.setOutsideTouchable(true); window.setContentView(container); window.setWidth(container.getMeasuredWidth()); @@ -193,6 +184,7 @@ public class MoreSuggestionsView extends KeyboardView implements MoreKeysPanel { @Override public boolean dismissMoreKeysPanel() { + if (mController == null) return false; return mController.dismissMoreKeysPanel(); } diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index c97f56712..d706cd0a4 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -171,7 +171,8 @@ public class Settings extends InputMethodSettingsActivity // Get the settings preferences final boolean hasVibrator = VibratorCompatWrapper.getInstance(context).hasVibrator(); - mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, false); + mVibrateOn = hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, + res.getBoolean(R.bool.config_default_vibration_enabled)); mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, res.getBoolean(R.bool.config_default_sound_enabled)); mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res); diff --git a/java/src/com/android/inputmethod/latin/SuggestionsView.java b/java/src/com/android/inputmethod/latin/SuggestionsView.java index 13beb4479..9d0e42a18 100644 --- a/java/src/com/android/inputmethod/latin/SuggestionsView.java +++ b/java/src/com/android/inputmethod/latin/SuggestionsView.java @@ -19,8 +19,14 @@ package com.android.inputmethod.latin; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Rect; import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Message; import android.os.SystemClock; @@ -29,12 +35,11 @@ import android.text.SpannableString; import android.text.Spanned; import android.text.TextPaint; import android.text.TextUtils; -import android.text.style.BackgroundColorSpan; import android.text.style.CharacterStyle; -import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.UnderlineSpan; import android.util.AttributeSet; +import android.view.GestureDetector; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -86,7 +91,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, private Listener mListener; private SuggestedWords mSuggestions = SuggestedWords.EMPTY; - private boolean mShowingAutoCorrectionInverted; private final SuggestionsViewParams mParams; private static final float MIN_TEXT_XSCALE = 0.70f; @@ -95,10 +99,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, private static class UiHandler extends StaticInnerHandlerWrapper<SuggestionsView> { private static final int MSG_HIDE_PREVIEW = 0; - private static final int MSG_UPDATE_SUGGESTION = 1; private static final long DELAY_HIDE_PREVIEW = 1300; - private static final long DELAY_UPDATE_SUGGESTION = 300; public UiHandler(SuggestionsView outerInstance) { super(outerInstance); @@ -111,9 +113,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, case MSG_HIDE_PREVIEW: suggestionsView.hidePreview(); break; - case MSG_UPDATE_SUGGESTION: - suggestionsView.updateSuggestions(); - break; } } @@ -126,19 +125,8 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, removeMessages(MSG_HIDE_PREVIEW); } - public void postUpdateSuggestions() { - cancelUpdateSuggestions(); - sendMessageDelayed(obtainMessage(MSG_UPDATE_SUGGESTION), - DELAY_UPDATE_SUGGESTION); - } - - public void cancelUpdateSuggestions() { - removeMessages(MSG_UPDATE_SUGGESTION); - } - public void cancelAllMessages() { cancelHidePreview(); - cancelUpdateSuggestions(); } } @@ -167,15 +155,13 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, private final float mCenterSuggestionWeight; private final int mCenterSuggestionIndex; private final Drawable mMoreSuggestionsHint; + private static final String MORE_SUGGESTIONS_HINT = "\u2026"; private static final CharacterStyle BOLD_SPAN = new StyleSpan(Typeface.BOLD); private static final CharacterStyle UNDERLINE_SPAN = new UnderlineSpan(); - private final CharacterStyle mInvertedForegroundColorSpan; - private final CharacterStyle mInvertedBackgroundColorSpan; private static final int AUTO_CORRECT_BOLD = 0x01; private static final int AUTO_CORRECT_UNDERLINE = 0x02; - private static final int AUTO_CORRECT_INVERT = 0x04; - private static final int VALID_TYPED_WORD_BOLD = 0x08; + private static final int VALID_TYPED_WORD_BOLD = 0x04; private final int mSuggestionStripOption; @@ -225,7 +211,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, mCenterSuggestionWeight = getPercent(a, R.styleable.SuggestionsView_centerSuggestionPercentile, DEFAULT_CENTER_SUGGESTION_PERCENTILE); - mMoreSuggestionsHint = a.getDrawable(R.styleable.SuggestionsView_moreSuggestionsHint); mMaxMoreSuggestionsRow = a.getInt( R.styleable.SuggestionsView_maxMoreSuggestionsRow, DEFAULT_MAX_MORE_SUGGESTIONS_ROW); @@ -233,19 +218,35 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, R.styleable.SuggestionsView_minMoreSuggestionsWidth); a.recycle(); + mMoreSuggestionsHint = getMoreSuggestionsHint(res, + res.getDimension(R.dimen.more_suggestions_hint_text_size), mColorAutoCorrect); mCenterSuggestionIndex = mSuggestionsCountInStrip / 2; mMoreSuggestionsBottomGap = res.getDimensionPixelOffset( R.dimen.more_suggestions_bottom_gap); - mInvertedForegroundColorSpan = new ForegroundColorSpan(mColorTypedWord ^ 0x00ffffff); - mInvertedBackgroundColorSpan = new BackgroundColorSpan(mColorTypedWord); - final LayoutInflater inflater = LayoutInflater.from(context); mWordToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); mHintToSaveView = (TextView)inflater.inflate(R.layout.suggestion_word, null); mHintToSaveText = context.getText(R.string.hint_add_to_dictionary); } + private static Drawable getMoreSuggestionsHint(Resources res, float textSize, int color) { + final Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setTextAlign(Align.CENTER); + paint.setTextSize(textSize); + paint.setColor(color); + final Rect bounds = new Rect(); + paint.getTextBounds(MORE_SUGGESTIONS_HINT, 0, MORE_SUGGESTIONS_HINT.length(), bounds); + final int width = Math.round(bounds.width() + 0.5f); + final int height = Math.round(bounds.height() + 0.5f); + final Bitmap buffer = Bitmap.createBitmap( + width, (height * 3 / 2), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(buffer); + canvas.drawText(MORE_SUGGESTIONS_HINT, width / 2, height, paint); + return new BitmapDrawable(res, buffer); + } + // Read integer value in TypedArray as percent. private static float getPercent(TypedArray a, int index, int defValue) { return a.getInt(index, defValue) / 100.0f; @@ -320,16 +321,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, return Color.argb(newAlpha, Color.red(color), Color.green(color), Color.blue(color)); } - public CharSequence getInvertedText(CharSequence text) { - if ((mSuggestionStripOption & AUTO_CORRECT_INVERT) == 0) - return null; - final int len = text.length(); - final Spannable word = new SpannableString(text); - word.setSpan(mInvertedBackgroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - word.setSpan(mInvertedForegroundColorSpan, 0, len, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); - return word; - } - public void layout(SuggestedWords suggestions, ViewGroup stripView, ViewGroup placer, int stripWidth) { if (suggestions.isPunctuationSuggestions()) { @@ -522,8 +513,25 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, final Resources res = context.getResources(); mMoreSuggestionsModalTolerance = res.getDimensionPixelOffset( R.dimen.more_suggestions_modal_tolerance); + mMoreSuggestionsSlidingDetector = new GestureDetector( + context, mMoreSuggestionsSlidingListener); } + private final View.OnTouchListener mMoreSuggestionsCanceller = new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent me) { + if (!mMoreSuggestionsWindow.isShowing()) return false; + + switch (me.getAction()) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + return mMoreSuggestionsView.dismissMoreKeysPanel(); + default: + return true; + } + } + }; + /** * A connection back to the input method. * @param listener @@ -534,21 +542,11 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, } public void setSuggestions(SuggestedWords suggestions) { - if (suggestions == null) + if (suggestions == null || suggestions.size() == 0) return; - mSuggestions = suggestions; - if (mShowingAutoCorrectionInverted) { - mHandler.postUpdateSuggestions(); - } else { - updateSuggestions(); - } - } - private void updateSuggestions() { clear(); - if (mSuggestions.size() == 0) - return; - + mSuggestions = suggestions; mParams.layout(mSuggestions, mSuggestionsStrip, this, getWidth()); } @@ -637,15 +635,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, } } - public void onAutoCorrectionInverted(CharSequence autoCorrectedWord) { - final CharSequence inverted = mParams.getInvertedText(autoCorrectedWord); - if (inverted == null) - return; - final TextView tv = mWords.get(1); - tv.setText(inverted); - mShowingAutoCorrectionInverted = true; - } - public boolean isShowingAddToDictionaryHint() { return mSuggestionsStrip.getChildCount() > 0 && mSuggestionsStrip.getChildAt(0) == mParams.mWordToSaveView; @@ -669,7 +658,6 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, } public void clear() { - mShowingAutoCorrectionInverted = false; mSuggestionsStrip.removeAllViews(); removeAllViews(); addView(mSuggestionsStrip); @@ -739,6 +727,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, if (mMoreSuggestionsWindow.isShowing()) { mMoreSuggestionsWindow.dismiss(); mKeyboardView.dimEntireKeyboard(false); + mKeyboardView.setOnTouchListener(null); return true; } return false; @@ -750,6 +739,10 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, @Override public boolean onLongClick(View view) { + return showMoreSuggestions(); + } + + private boolean showMoreSuggestions() { final SuggestionsViewParams params = mParams; if (params.mMoreSuggestionsAvailable) { final int stripWidth = getWidth(); @@ -770,29 +763,51 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, moreKeysPanel.showMoreKeysPanel( this, mMoreSuggestionsController, pointX, pointY, mMoreSuggestionsWindow, mMoreSuggestionsListener); - mCheckingIfModalOrSlidingMode = true; + mMoreSuggestionsMode = MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING; mOriginX = mLastX; mOriginY = mLastY; - view.setPressed(false); mKeyboardView.dimEntireKeyboard(true); + mKeyboardView.setOnTouchListener(mMoreSuggestionsCanceller); + for (int i = 0; i < params.mSuggestionsCountInStrip; i++) { + mWords.get(i).setPressed(false); + } return true; } return false; } // Working variables for onLongClick and dispatchTouchEvent. - private boolean mCheckingIfModalOrSlidingMode; + private int mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE; + private static final int MORE_SUGGESTIONS_IN_MODAL_MODE = 0; + private static final int MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING = 1; + private static final int MORE_SUGGESTIONS_IN_SLIDING_MODE = 2; private int mLastX; private int mLastY; private int mOriginX; private int mOriginY; private final int mMoreSuggestionsModalTolerance; + private final GestureDetector mMoreSuggestionsSlidingDetector; + private final GestureDetector.OnGestureListener mMoreSuggestionsSlidingListener = + new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onScroll(MotionEvent down, MotionEvent me, float deltaX, float deltaY) { + final float dy = me.getY() - down.getY(); + if (deltaY > 0 && dy < 0) { + return showMoreSuggestions(); + } + return false; + } + }; @Override public boolean dispatchTouchEvent(MotionEvent me) { - if (!mMoreSuggestionsWindow.isShowing()) { + if (!mMoreSuggestionsWindow.isShowing() + || mMoreSuggestionsMode == MORE_SUGGESTIONS_IN_MODAL_MODE) { mLastX = (int)me.getX(); mLastY = (int)me.getY(); + if (mMoreSuggestionsSlidingDetector.onTouchEvent(me)) { + return true; + } return super.dispatchTouchEvent(me); } @@ -807,22 +822,22 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, final int translatedX = moreKeysPanel.translateX(x); final int translatedY = moreKeysPanel.translateY(y); - if (mCheckingIfModalOrSlidingMode) { + if (mMoreSuggestionsMode == MORE_SUGGESTIONS_CHECKING_MODAL_OR_SLIDING) { if (Math.abs(x - mOriginX) >= mMoreSuggestionsModalTolerance || mOriginY - y >= mMoreSuggestionsModalTolerance) { // Decided to be in the sliding input mode only when the touch point has been moved // upward. - mCheckingIfModalOrSlidingMode = false; + mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_SLIDING_MODE; tracker.onShowMoreKeysPanel( translatedX, translatedY, SystemClock.uptimeMillis(), moreKeysPanel); } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) { // Decided to be in the modal input mode - mCheckingIfModalOrSlidingMode = false; + mMoreSuggestionsMode = MORE_SUGGESTIONS_IN_MODAL_MODE; } return true; } - // Process sliding motion events + // MORE_SUGGESTIONS_IN_SLIDING_MODE tracker.processMotionEvent(action, translatedX, translatedY, eventTime, moreKeysPanel); return true; } @@ -847,7 +862,7 @@ public class SuggestionsView extends RelativeLayout implements OnClickListener, } @Override - public void onDetachedFromWindow() { + protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mHandler.cancelAllMessages(); hidePreview(); diff --git a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java index 1e5b87763..2546df0a2 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/AndroidSpellCheckerService.java @@ -60,8 +60,11 @@ public class AndroidSpellCheckerService extends SpellCheckerService { private static final int CAPITALIZE_ALL = 2; // All caps private final static String[] EMPTY_STRING_ARRAY = new String[0]; - private final static SuggestionsInfo EMPTY_SUGGESTIONS_INFO = + private final static SuggestionsInfo NOT_IN_DICT_EMPTY_SUGGESTIONS = new SuggestionsInfo(0, EMPTY_STRING_ARRAY); + private final static SuggestionsInfo IN_DICT_EMPTY_SUGGESTIONS = + new SuggestionsInfo(SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY, + EMPTY_STRING_ARRAY); private Map<String, DictionaryPool> mDictionaryPools = Collections.synchronizedMap(new TreeMap<String, DictionaryPool>()); private Map<String, Dictionary> mUserDictionaries = @@ -82,10 +85,10 @@ public class AndroidSpellCheckerService extends SpellCheckerService { private static class SuggestionsGatherer implements WordCallback { public static class Result { public final String[] mSuggestions; - public final boolean mLooksLikeTypo; - public Result(final String[] gatheredSuggestions, final boolean looksLikeTypo) { + public final boolean mHasLikelySuggestions; + public Result(final String[] gatheredSuggestions, final boolean hasLikelySuggestions) { mSuggestions = gatheredSuggestions; - mLooksLikeTypo = looksLikeTypo; + mHasLikelySuggestions = hasLikelySuggestions; } } @@ -146,19 +149,19 @@ public class AndroidSpellCheckerService extends SpellCheckerService { public Result getResults(final CharSequence originalText, final double threshold, final int capitalizeType, final Locale locale) { final String[] gatheredSuggestions; - final boolean looksLikeTypo; + final boolean hasLikelySuggestions; if (0 == mLength) { // Either we found no suggestions, or we found some BUT the max length was 0. // If we found some mBestSuggestion will not be null. If it is null, then // we found none, regardless of the max length. if (null == mBestSuggestion) { gatheredSuggestions = null; - looksLikeTypo = false; + hasLikelySuggestions = false; } else { gatheredSuggestions = EMPTY_STRING_ARRAY; final double normalizedScore = Utils.calcNormalizedScore(originalText, mBestSuggestion, mBestScore); - looksLikeTypo = (normalizedScore > threshold); + hasLikelySuggestions = (normalizedScore > threshold); } } else { if (DBG) { @@ -192,14 +195,14 @@ public class AndroidSpellCheckerService extends SpellCheckerService { final CharSequence bestSuggestion = mSuggestions.get(0); final double normalizedScore = Utils.calcNormalizedScore(originalText, bestSuggestion, bestScore); - looksLikeTypo = (normalizedScore > threshold); + hasLikelySuggestions = (normalizedScore > threshold); if (DBG) { Log.i(TAG, "Best suggestion : " + bestSuggestion + ", score " + bestScore); Log.i(TAG, "Normalized score = " + normalizedScore + " (threshold " + threshold - + ") => looksLikeTypo = " + looksLikeTypo); + + ") => hasLikelySuggestions = " + hasLikelySuggestions); } } - return new Result(gatheredSuggestions, looksLikeTypo); + return new Result(gatheredSuggestions, hasLikelySuggestions); } } @@ -330,8 +333,23 @@ public class AndroidSpellCheckerService extends SpellCheckerService { try { final String text = textInfo.getText(); - if (shouldFilterOut(text)) return EMPTY_SUGGESTIONS_INFO; + if (shouldFilterOut(text)) { + DictAndProximity dictInfo = null; + try { + dictInfo = mDictionaryPool.takeOrGetNull(); + if (null == dictInfo) return NOT_IN_DICT_EMPTY_SUGGESTIONS; + return dictInfo.mDictionary.isValidWord(text) ? IN_DICT_EMPTY_SUGGESTIONS + : NOT_IN_DICT_EMPTY_SUGGESTIONS; + } finally { + if (null != dictInfo) { + if (!mDictionaryPool.offer(dictInfo)) { + Log.e(TAG, "Can't re-insert a dictionary into its pool"); + } + } + } + } + // TODO: Don't gather suggestions if the limit is <= 0 unless necessary final SuggestionsGatherer suggestionsGatherer = new SuggestionsGatherer(suggestionsLimit); final WordComposer composer = new WordComposer(); @@ -353,8 +371,10 @@ public class AndroidSpellCheckerService extends SpellCheckerService { final int capitalizeType = getCapitalizationType(text); boolean isInDict = true; + DictAndProximity dictInfo = null; try { - final DictAndProximity dictInfo = mDictionaryPool.take(); + dictInfo = mDictionaryPool.takeOrGetNull(); + if (null == dictInfo) return NOT_IN_DICT_EMPTY_SUGGESTIONS; dictInfo.mDictionary.getWords(composer, suggestionsGatherer, dictInfo.mProximityInfo); isInDict = dictInfo.mDictionary.isValidWord(text); @@ -364,12 +384,12 @@ public class AndroidSpellCheckerService extends SpellCheckerService { // want to test a lowercase version of it. isInDict = dictInfo.mDictionary.isValidWord(text.toLowerCase(mLocale)); } - if (!mDictionaryPool.offer(dictInfo)) { - Log.e(TAG, "Can't re-insert a dictionary into its pool"); + } finally { + if (null != dictInfo) { + if (!mDictionaryPool.offer(dictInfo)) { + Log.e(TAG, "Can't re-insert a dictionary into its pool"); + } } - } catch (InterruptedException e) { - // I don't think this can happen. - return EMPTY_SUGGESTIONS_INFO; } final SuggestionsGatherer.Result result = suggestionsGatherer.getResults(text, @@ -378,17 +398,18 @@ public class AndroidSpellCheckerService extends SpellCheckerService { if (DBG) { Log.i(TAG, "Spell checking results for " + text + " with suggestion limit " + suggestionsLimit); - Log.i(TAG, "IsInDict = " + result.mLooksLikeTypo); - Log.i(TAG, "LooksLikeTypo = " + result.mLooksLikeTypo); + Log.i(TAG, "IsInDict = " + isInDict); + Log.i(TAG, "LooksLikeTypo = " + (!isInDict)); + Log.i(TAG, "HasLikelySuggestions = " + result.mHasLikelySuggestions); for (String suggestion : result.mSuggestions) { Log.i(TAG, suggestion); } } + // TODO: actually use result.mHasLikelySuggestions final int flags = - (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY : 0) - | (result.mLooksLikeTypo - ? SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO : 0); + (isInDict ? SuggestionsInfo.RESULT_ATTR_IN_THE_DICTIONARY + : SuggestionsInfo.RESULT_ATTR_LOOKS_LIKE_TYPO); return new SuggestionsInfo(flags, result.mSuggestions); } catch (RuntimeException e) { // Don't kill the keyboard if there is a bug in the spell checker @@ -396,7 +417,7 @@ public class AndroidSpellCheckerService extends SpellCheckerService { throw e; } else { Log.e(TAG, "Exception while spellcheking: " + e); - return EMPTY_SUGGESTIONS_INFO; + return NOT_IN_DICT_EMPTY_SUGGESTIONS; } } } diff --git a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java index ee294f6b0..dec18c1d5 100644 --- a/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java +++ b/java/src/com/android/inputmethod/latin/spellcheck/DictionaryPool.java @@ -56,6 +56,15 @@ public class DictionaryPool extends LinkedBlockingQueue<DictAndProximity> { } } + // Convenience method + public DictAndProximity takeOrGetNull() { + try { + return take(); + } catch (InterruptedException e) { + return null; + } + } + public void close() { synchronized(this) { mClosed = true; |