diff options
Diffstat (limited to 'java/src')
8 files changed, 412 insertions, 200 deletions
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index cd4143e78..5d48d6b36 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -133,14 +133,17 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private Resources mResources; private SharedPreferences mPrefs; + // These variables are initialized according to the {@link EditorInfo#inputType}. + private boolean mAutoSpace; + private boolean mInputTypeNoAutoCorrect; + private boolean mIsSettingsSuggestionStripOn; + private boolean mApplicationSpecifiedCompletionOn; + private final StringBuilder mComposing = new StringBuilder(); private WordComposer mWord = new WordComposer(); private CharSequence mBestWord; private boolean mHasValidSuggestions; - private boolean mIsSettingsSuggestionStripOn; - private boolean mApplicationSpecifiedCompletionOn; private boolean mHasDictionary; - private boolean mAutoSpace; private boolean mJustAddedAutoSpace; private boolean mAutoCorrectEnabled; private boolean mReCorrectionEnabled; @@ -164,9 +167,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen private int mLastSelectionEnd; private SuggestedWords mSuggestPuncList; - // Input type is such that we should not auto-correct - private boolean mInputTypeNoAutoCorrect; - // Indicates whether the suggestion strip is to be on in landscape private boolean mJustAccepted; private boolean mJustReverted; @@ -353,8 +353,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final Resources res = getResources(); mResources = res; - mReCorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED, - res.getBoolean(R.bool.default_recorrection_enabled)); + + // If the option should not be shown, do not read the recorrection preference + // but always use the default setting defined in the resources. + if (res.getBoolean(R.bool.config_enable_show_recorrection_option)) { + mReCorrectionEnabled = prefs.getBoolean(Settings.PREF_RECORRECTION_ENABLED, + res.getBoolean(R.bool.default_recorrection_enabled)); + } else { + mReCorrectionEnabled = res.getBoolean(R.bool.default_recorrection_enabled); + } + mConfigSwipeDownDismissKeyboardEnabled = res.getBoolean( R.bool.config_swipe_down_dismiss_keyboard_enabled); mConfigDelayBeforeFadeoutLanguageOnSpacebar = res.getInteger( @@ -456,7 +464,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mConfigurationChanging = true; super.onConfigurationChanged(conf); - mVoiceConnector.onConfigurationChanged(mConfigurationChanging); + mVoiceConnector.onConfigurationChanged(conf); mConfigurationChanging = false; } @@ -507,24 +515,62 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen if (mRefreshKeyboardRequired) { mRefreshKeyboardRequired = false; - onKeyboardLanguageChanged(); + onRefreshKeyboard(); } TextEntryState.newSession(this); - // Most such things we decide below in the switch statement, but we need to know - // now whether this is a password text field, because we need to know now (before - // the switch statement) whether we want to enable the voice button. - int variation = attribute.inputType & InputType.TYPE_MASK_VARIATION; - mVoiceConnector.resetVoiceStates(isPasswordVariation(variation)); + // Most such things we decide below in initializeInputAttributesAndGetMode, but we need to + // know now whether this is a password text field, because we need to know now whether we + // want to enable the voice button. + mVoiceConnector.resetVoiceStates(isPasswordVariation( + attribute.inputType & InputType.TYPE_MASK_VARIATION)); + + final int mode = initializeInputAttributesAndGetMode(attribute.inputType); + + inputView.closing(); + mEnteredText = null; + mComposing.setLength(0); + mHasValidSuggestions = false; + mDeleteCount = 0; + mJustAddedAutoSpace = false; + + loadSettings(attribute); + if (mSubtypeSwitcher.isKeyboardMode()) { + switcher.loadKeyboard(mode, attribute.imeOptions, + mVoiceConnector.isVoiceButtonEnabled(), + mVoiceConnector.isVoiceButtonOnPrimary()); + switcher.updateShiftState(); + } + + setCandidatesViewShownInternal(isCandidateStripVisible(), + false /* needsInputViewShown */ ); + // Delay updating suggestions because keyboard input view may not be shown at this point. + mHandler.postUpdateSuggestions(); + + updateCorrectionMode(); + + inputView.setPreviewEnabled(mPopupOn); + inputView.setProximityCorrectionEnabled(true); + // If we just entered a text field, maybe it has some old text that requires correction + checkReCorrectionOnStart(); + inputView.setForeground(true); + + mVoiceConnector.onStartInputView(inputView.getWindowToken()); + + if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); + } + + private int initializeInputAttributesAndGetMode(int inputType) { + final int variation = inputType & InputType.TYPE_MASK_VARIATION; + mAutoSpace = false; mInputTypeNoAutoCorrect = false; mIsSettingsSuggestionStripOn = false; mApplicationSpecifiedCompletionOn = false; mApplicationSpecifiedCompletions = null; - mEnteredText = null; final int mode; - switch (attribute.inputType & InputType.TYPE_MASK_CLASS) { + switch (inputType & InputType.TYPE_MASK_CLASS) { case InputType.TYPE_CLASS_NUMBER: case InputType.TYPE_CLASS_DATETIME: mode = KeyboardId.MODE_NUMBER; @@ -559,7 +605,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mode = KeyboardId.MODE_WEB; // If it's a browser edit field and auto correct is not ON explicitly, then // disable auto correction, but keep suggestions on. - if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { + if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) { mInputTypeNoAutoCorrect = true; } } else { @@ -567,16 +613,16 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // If NO_SUGGESTIONS is set, don't do prediction. - if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { + if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) { mIsSettingsSuggestionStripOn = false; mInputTypeNoAutoCorrect = true; } // If it's not multiline and the autoCorrect flag is not set, then don't correct - if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 && - (attribute.inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { + if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0 && + (inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == 0) { mInputTypeNoAutoCorrect = true; } - if ((attribute.inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { + if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) { mIsSettingsSuggestionStripOn = false; mApplicationSpecifiedCompletionOn = isFullscreenMode(); } @@ -585,40 +631,7 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen mode = KeyboardId.MODE_TEXT; break; } - inputView.closing(); - mComposing.setLength(0); - mHasValidSuggestions = false; - mDeleteCount = 0; - mJustAddedAutoSpace = false; - - loadSettings(attribute); - if (mSubtypeSwitcher.isKeyboardMode()) { - switcher.loadKeyboard(mode, attribute.imeOptions, - mVoiceConnector.isVoiceButtonEnabled(), - mVoiceConnector.isVoiceButtonOnPrimary()); - switcher.updateShiftState(); - } - - setCandidatesViewShownInternal(isCandidateStripVisible(), - false /* needsInputViewShown */ ); - // Delay updating suggestions because keyboard input view may not be shown at this point. - mHandler.postUpdateSuggestions(); - - // If the dictionary is not big enough, don't auto correct - mHasDictionary = mSuggest.hasMainDictionary(); - - updateCorrectionMode(); - - inputView.setPreviewEnabled(mPopupOn); - inputView.setProximityCorrectionEnabled(true); - mIsSettingsSuggestionStripOn &= (mCorrectionMode > 0 || isShowingSuggestionsStrip()); - // If we just entered a text field, maybe it has some old text that requires correction - checkReCorrectionOnStart(); - inputView.setForeground(true); - - mVoiceConnector.onStartInputView(inputView.getWindowToken()); - - if (TRACE) Debug.startMethodTracing("/data/trace/latinime"); + return mode; } private void checkReCorrectionOnStart() { @@ -1387,7 +1400,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } private boolean isSuggestionsRequested() { - return mIsSettingsSuggestionStripOn; + return mIsSettingsSuggestionStripOn + && (mCorrectionMode > 0 || isShowingSuggestionsStrip()); } private boolean isShowingPunctuationList() { @@ -1805,7 +1819,6 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen final int length = mComposing.length(); if (!mHasValidSuggestions && length > 0) { final InputConnection ic = getCurrentInputConnection(); - mHasValidSuggestions = true; mJustReverted = true; final CharSequence punctuation = ic.getTextBeforeCursor(1, 0); if (deleteChar) ic.deleteSurroundingText(1, 0); @@ -1815,14 +1828,19 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen toDelete--; } ic.deleteSurroundingText(toDelete, 0); - if (deleteChar) { + // Re-insert punctuation only when the deleted character was word separator and the + // composing text wasn't equal to the auto-corrected text. + if (deleteChar + && !TextUtils.isEmpty(punctuation) && isWordSeparator(punctuation.charAt(0)) + && !TextUtils.equals(mComposing, toTheLeft)) { ic.commitText(mComposing, 1); TextEntryState.acceptedTyped(mComposing); - if (!TextUtils.isEmpty(punctuation) && isWordSeparator(punctuation.charAt(0))) { - ic.commitText(punctuation, 1); - TextEntryState.typedCharacter(punctuation.charAt(0), true); - } + ic.commitText(punctuation, 1); + TextEntryState.typedCharacter(punctuation.charAt(0), true); + // Clear composing text + mComposing.setLength(0); } else { + mHasValidSuggestions = true; ic.setComposingText(mComposing, 1); TextEntryState.backspace(); } @@ -1855,9 +1873,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen return mWord.isFirstCharCapitalized(); } - // Notify that Language has been changed and toggleLanguage will update KeyboaredID according - // to new Language. - public void onKeyboardLanguageChanged() { + // Notify that language or mode have been changed and toggleLanguage will update KeyboaredID + // according to new language or mode. + public void onRefreshKeyboard() { toggleLanguage(true, true); } @@ -1868,8 +1886,9 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen } // Reload keyboard because the current language has been changed. KeyboardSwitcher switcher = mKeyboardSwitcher; - final int mode = switcher.getKeyboardMode(); final EditorInfo attribute = getCurrentInputEditorInfo(); + final int mode = initializeInputAttributesAndGetMode((attribute != null) + ? attribute.inputType : 0); final int imeOptions = (attribute != null) ? attribute.imeOptions : 0; switcher.loadKeyboard(mode, imeOptions, mVoiceConnector.isVoiceButtonEnabled(), mVoiceConnector.isVoiceButtonOnPrimary()); diff --git a/java/src/com/android/inputmethod/latin/Settings.java b/java/src/com/android/inputmethod/latin/Settings.java index 5db63b719..75ebbe7e5 100644 --- a/java/src/com/android/inputmethod/latin/Settings.java +++ b/java/src/com/android/inputmethod/latin/Settings.java @@ -140,6 +140,12 @@ public class Settings extends PreferenceActivity if (!showPopupOption) { getPreferenceScreen().removePreference(findPreference(PREF_POPUP_ON)); } + + final boolean showRecorrectionOption = getResources().getBoolean( + R.bool.config_enable_show_recorrection_option); + if (!showRecorrectionOption) { + getPreferenceScreen().removePreference(findPreference(PREF_RECORRECTION_ENABLED)); + } } @Override diff --git a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java index b765fad3f..a9bd114f3 100644 --- a/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java +++ b/java/src/com/android/inputmethod/latin/SubtypeSwitcher.java @@ -187,7 +187,7 @@ public class SubtypeSwitcher { // fallback to the default locale and mode. Log.w(TAG, "Couldn't get the current subtype."); newLocale = "en_US"; - newMode =KEYBOARD_MODE; + newMode = KEYBOARD_MODE; } else { newLocale = newSubtype.getLocale(); newMode = newSubtype.getMode(); @@ -217,8 +217,8 @@ public class SubtypeSwitcher { mVoiceInput.cancel(); } } - if (languageChanged) { - mService.onKeyboardLanguageChanged(); + if (modeChanged || languageChanged) { + mService.onRefreshKeyboard(); } } else if (isVoiceMode()) { // If needsToShowWarningDialog is true, voice input need to show warning before @@ -439,7 +439,7 @@ public class SubtypeSwitcher { private void triggerVoiceIME() { if (!mService.isInputViewShown()) return; VoiceIMEConnector.getInstance().startListening(false, - KeyboardSwitcher.getInstance().getInputView().getWindowToken(), false); + KeyboardSwitcher.getInstance().getInputView().getWindowToken()); } ////////////////////////////////////// diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 5398b77b2..0fbbcdd91 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -124,7 +124,7 @@ public class SuggestedWords { addWord(previousSuggestions.getWord(pos)); mIsCompletions = false; mTypedWordValid = false; - mHasMinimalSuggestion = (previousSize > 1); + mHasMinimalSuggestion = false; return this; } diff --git a/java/src/com/android/inputmethod/voice/RecognitionView.java b/java/src/com/android/inputmethod/voice/RecognitionView.java index d6d0721e2..98db9365f 100644 --- a/java/src/com/android/inputmethod/voice/RecognitionView.java +++ b/java/src/com/android/inputmethod/voice/RecognitionView.java @@ -16,9 +16,6 @@ package com.android.inputmethod.voice; -import com.android.inputmethod.latin.R; - -import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; @@ -29,20 +26,21 @@ import android.graphics.Path; import android.graphics.PathEffect; import android.graphics.drawable.Drawable; import android.os.Handler; -import android.util.TypedValue; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; -import android.view.ViewGroup.MarginLayoutParams; +import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; +import com.android.inputmethod.latin.R; + import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.ShortBuffer; -import java.util.ArrayList; import java.util.List; /** @@ -58,79 +56,55 @@ public class RecognitionView { private View mView; private Context mContext; - private ImageView mImage; private TextView mText; - private View mButton; - private TextView mButtonText; + private ImageView mImage; private View mProgress; + private SoundIndicator mSoundIndicator; + private Button mButton; private Drawable mInitializing; private Drawable mError; - private List<Drawable> mSpeakNow; - private float mVolume = 0.0f; - private int mLevel = 0; - - private enum State {LISTENING, WORKING, READY} - private State mState = State.READY; - - private float mMinMicrophoneLevel; - private float mMaxMicrophoneLevel; - - /** Updates the microphone icon to show user their volume.*/ - private Runnable mUpdateVolumeRunnable = new Runnable() { - @Override - public void run() { - if (mState != State.LISTENING) { - return; - } - - final float min = mMinMicrophoneLevel; - final float max = mMaxMicrophoneLevel; - final int maxLevel = mSpeakNow.size() - 1; + private static final int INIT = 0; + private static final int LISTENING = 1; + private static final int WORKING = 2; + private static final int READY = 3; + + private int mState = INIT; - int index = (int) ((mVolume - min) / (max - min) * maxLevel); - final int level = Math.min(Math.max(0, index), maxLevel); + private final View mPopupLayout; - if (level != mLevel) { - mImage.setImageDrawable(mSpeakNow.get(level)); - mLevel = level; - } - mUiHandler.postDelayed(mUpdateVolumeRunnable, 50); - } - }; + private final Drawable mListeningBorder; + private final Drawable mWorkingBorder; + private final Drawable mErrorBorder; public RecognitionView(Context context, OnClickListener clickListener) { mUiHandler = new Handler(); - mView = LayoutInflater.from(context).inflate(R.layout.recognition_status, null); - ContentResolver cr = context.getContentResolver(); - mMinMicrophoneLevel = SettingsUtil.getSettingsFloat( - cr, SettingsUtil.LATIN_IME_MIN_MICROPHONE_LEVEL, 15.f); - mMaxMicrophoneLevel = SettingsUtil.getSettingsFloat( - cr, SettingsUtil.LATIN_IME_MAX_MICROPHONE_LEVEL, 30.f); + LayoutInflater inflater = (LayoutInflater) context.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + + mView = inflater.inflate(R.layout.recognition_status, null); + + mPopupLayout= mView.findViewById(R.id.popup_layout); // Pre-load volume level images Resources r = context.getResources(); - mSpeakNow = new ArrayList<Drawable>(); - mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level0)); - mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level1)); - mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level2)); - mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level3)); - mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level4)); - mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level5)); - mSpeakNow.add(r.getDrawable(R.drawable.speak_now_level6)); + mListeningBorder = r.getDrawable(R.drawable.vs_dialog_red); + mWorkingBorder = r.getDrawable(R.drawable.vs_dialog_blue); + mErrorBorder = r.getDrawable(R.drawable.vs_dialog_yellow); mInitializing = r.getDrawable(R.drawable.mic_slash); mError = r.getDrawable(R.drawable.caution); mImage = (ImageView) mView.findViewById(R.id.image); - mButton = mView.findViewById(R.id.button); + mProgress = mView.findViewById(R.id.progress); + mSoundIndicator = (SoundIndicator) mView.findViewById(R.id.sound_indicator); + + mButton = (Button) mView.findViewById(R.id.button); mButton.setOnClickListener(clickListener); mText = (TextView) mView.findViewById(R.id.text); - mButtonText = (TextView) mView.findViewById(R.id.button_text); - mProgress = mView.findViewById(R.id.progress); mContext = context; } @@ -144,9 +118,9 @@ public class RecognitionView { @Override public void run() { // Restart the spinner - if (mState == State.WORKING) { - ((ProgressBar)mProgress).setIndeterminate(false); - ((ProgressBar)mProgress).setIndeterminate(true); + if (mState == WORKING) { + ((ProgressBar) mProgress).setIndeterminate(false); + ((ProgressBar) mProgress).setIndeterminate(true); } } }); @@ -156,48 +130,48 @@ public class RecognitionView { mUiHandler.post(new Runnable() { @Override public void run() { - prepareDialog(false, mContext.getText(R.string.voice_initializing), mInitializing, - mContext.getText(R.string.cancel)); + mState = INIT; + prepareDialog(mContext.getText(R.string.voice_initializing), mInitializing, + mContext.getText(R.string.cancel)); } }); } public void showListening() { + Log.d(TAG, "#showListening"); mUiHandler.post(new Runnable() { @Override public void run() { - mState = State.LISTENING; - prepareDialog(false, mContext.getText(R.string.voice_listening), mSpeakNow.get(0), + mState = LISTENING; + prepareDialog(mContext.getText(R.string.voice_listening), null, mContext.getText(R.string.cancel)); } }); - mUiHandler.postDelayed(mUpdateVolumeRunnable, 50); } - public void updateVoiceMeter(final float rmsdB) { - mVolume = rmsdB; + public void updateVoiceMeter(float rmsdB) { + mSoundIndicator.setRmsdB(rmsdB); } public void showError(final String message) { mUiHandler.post(new Runnable() { @Override public void run() { - mState = State.READY; - prepareDialog(false, message, mError, mContext.getText(R.string.ok)); + mState = READY; + prepareDialog(message, mError, mContext.getText(R.string.ok)); } - }); + }); } public void showWorking( final ByteArrayOutputStream waveBuffer, final int speechStartPosition, final int speechEndPosition) { - mUiHandler.post(new Runnable() { @Override public void run() { - mState = State.WORKING; - prepareDialog(true, mContext.getText(R.string.voice_working), null, mContext + mState = WORKING; + prepareDialog(mContext.getText(R.string.voice_working), null, mContext .getText(R.string.cancel)); final ShortBuffer buf = ByteBuffer.wrap(waveBuffer.toByteArray()).order( ByteOrder.nativeOrder()).asShortBuffer(); @@ -205,21 +179,71 @@ public class RecognitionView { waveBuffer.reset(); showWave(buf, speechStartPosition / 2, speechEndPosition / 2); } - }); + }); } - private void prepareDialog(boolean spinVisible, CharSequence text, Drawable image, + private void prepareDialog(CharSequence text, Drawable image, CharSequence btnTxt) { - if (spinVisible) { - mProgress.setVisibility(View.VISIBLE); - mImage.setVisibility(View.GONE); - } else { - mProgress.setVisibility(View.GONE); - mImage.setImageDrawable(image); - mImage.setVisibility(View.VISIBLE); + switch (mState) { + case INIT: + mText.setVisibility(View.GONE); + + mProgress.setVisibility(View.GONE); + + mImage.setVisibility(View.VISIBLE); + mImage.setImageResource(R.drawable.mic_slash); + + mSoundIndicator.setVisibility(View.GONE); + mSoundIndicator.stop(); + + mPopupLayout.setBackgroundDrawable(mListeningBorder); + break; + case LISTENING: + mText.setVisibility(View.VISIBLE); + mText.setText(text); + + mProgress.setVisibility(View.GONE); + + mImage.setVisibility(View.GONE); + + mSoundIndicator.setVisibility(View.VISIBLE); + mSoundIndicator.start(); + + mPopupLayout.setBackgroundDrawable(mListeningBorder); + break; + case WORKING: + + mText.setVisibility(View.VISIBLE); + mText.setText(text); + + mProgress.setVisibility(View.VISIBLE); + + mImage.setVisibility(View.VISIBLE); + + mSoundIndicator.setVisibility(View.GONE); + mSoundIndicator.stop(); + + mPopupLayout.setBackgroundDrawable(mWorkingBorder); + break; + case READY: + mText.setVisibility(View.VISIBLE); + mText.setText(text); + + mProgress.setVisibility(View.GONE); + + mImage.setVisibility(View.VISIBLE); + mImage.setImageResource(R.drawable.caution); + + mSoundIndicator.setVisibility(View.GONE); + mSoundIndicator.stop(); + + mPopupLayout.setBackgroundDrawable(mErrorBorder); + break; + default: + Log.w(TAG, "Unknown state " + mState); } - mText.setText(text); - mButtonText.setText(btnTxt); + mPopupLayout.requestLayout(); + mButton.setText(btnTxt); } /** @@ -246,7 +270,7 @@ public class RecognitionView { */ private void showWave(ShortBuffer waveBuffer, int startPosition, int endPosition) { final int w = ((View) mImage.getParent()).getWidth(); - final int h = mImage.getHeight(); + final int h = ((View) mImage.getParent()).getHeight(); if (w <= 0 || h <= 0) { // view is not visible this time. Skip drawing. return; @@ -257,7 +281,7 @@ public class RecognitionView { paint.setColor(0xFFFFFFFF); // 0xAARRGGBB paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); - paint.setAlpha(0x90); + paint.setAlpha(80); final PathEffect effect = new CornerPathEffect(3); paint.setPathEffect(effect); @@ -279,7 +303,7 @@ public class RecognitionView { final int count = (endIndex - startIndex) / numSamplePerWave; final float deltaX = 1.0f * w / count; - int yMax = h / 2 - 8; + int yMax = h / 2; Path path = new Path(); c.translate(0, yMax); float x = 0; @@ -293,37 +317,20 @@ public class RecognitionView { path.lineTo(x, y); } if (deltaX > 4) { - paint.setStrokeWidth(3); + paint.setStrokeWidth(2); } else { - paint.setStrokeWidth(Math.max(1, (int) (deltaX -.05))); + paint.setStrokeWidth(Math.max(0, (int) (deltaX -.05))); } c.drawPath(path, paint); mImage.setImageBitmap(b); - mImage.setVisibility(View.VISIBLE); - MarginLayoutParams mProgressParams = (MarginLayoutParams)mProgress.getLayoutParams(); - mProgressParams.topMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, - -h , mContext.getResources().getDisplayMetrics()); - - // Tweak the padding manually to fill out the whole view horizontally. - // TODO: Do this in the xml layout instead. - ((View) mImage.getParent()).setPadding(4, ((View) mImage.getParent()).getPaddingTop(), 3, - ((View) mImage.getParent()).getPaddingBottom()); - mProgress.setLayoutParams(mProgressParams); } - public void finish() { mUiHandler.post(new Runnable() { @Override public void run() { - mState = State.READY; - exitWorking(); + mSoundIndicator.stop(); } - }); - } - - private void exitWorking() { - mProgress.setVisibility(View.GONE); - mImage.setVisibility(View.VISIBLE); + }); } } diff --git a/java/src/com/android/inputmethod/voice/SoundIndicator.java b/java/src/com/android/inputmethod/voice/SoundIndicator.java new file mode 100644 index 000000000..543290b32 --- /dev/null +++ b/java/src/com/android/inputmethod/voice/SoundIndicator.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * 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.voice; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.android.inputmethod.latin.R; + +/** + * A widget which shows the volume of audio using a microphone icon + */ +public class SoundIndicator extends ImageView { + @SuppressWarnings("unused") + private static final String TAG = "SoundIndicator"; + + private static final float UP_SMOOTHING_FACTOR = 0.9f; + private static final float DOWN_SMOOTHING_FACTOR = 0.4f; + + private static final float AUDIO_METER_MIN_DB = 7.0f; + private static final float AUDIO_METER_DB_RANGE = 20.0f; + + private static final long FRAME_DELAY = 50; + + private Bitmap mDrawingBuffer; + private Canvas mBufferCanvas; + private Bitmap mEdgeBitmap; + private float mLevel = 0.0f; + private Drawable mFrontDrawable; + private Paint mClearPaint; + private Paint mMultPaint; + private int mEdgeBitmapOffset; + + private Handler mHandler; + + private Runnable mDrawFrame = new Runnable() { + public void run() { + invalidate(); + mHandler.postDelayed(mDrawFrame, FRAME_DELAY); + } + }; + + public SoundIndicator(Context context) { + this(context, null); + } + + public SoundIndicator(Context context, AttributeSet attrs) { + super(context, attrs); + + mFrontDrawable = getDrawable(); + BitmapDrawable edgeDrawable = + (BitmapDrawable) context.getResources().getDrawable(R.drawable.vs_popup_mic_edge); + mEdgeBitmap = edgeDrawable.getBitmap(); + mEdgeBitmapOffset = mEdgeBitmap.getHeight() / 2; + + mDrawingBuffer = + Bitmap.createBitmap(mFrontDrawable.getIntrinsicWidth(), + mFrontDrawable.getIntrinsicHeight(), Config.ARGB_8888); + + mBufferCanvas = new Canvas(mDrawingBuffer); + + // Initialize Paints. + mClearPaint = new Paint(); + mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + + mMultPaint = new Paint(); + mMultPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); + + mHandler = new Handler(); + } + + @Override + public void onDraw(Canvas canvas) { + //super.onDraw(canvas); + + float w = getWidth(); + float h = getHeight(); + + // Clear the buffer canvas + mBufferCanvas.drawRect(0, 0, w, h, mClearPaint); + + // Set its clip so we don't draw the front image all the way to the top + Rect clip = new Rect(0, + (int) ((1.0 - mLevel) * (h + mEdgeBitmapOffset)) - mEdgeBitmapOffset, + (int) w, + (int) h); + + mBufferCanvas.save(); + mBufferCanvas.clipRect(clip); + + // Draw the front image + mFrontDrawable.setBounds(new Rect(0, 0, (int) w, (int) h)); + mFrontDrawable.draw(mBufferCanvas); + + mBufferCanvas.restore(); + + // Draw the edge image on top of the buffer image with a multiply mode + mBufferCanvas.drawBitmap(mEdgeBitmap, 0, clip.top, mMultPaint); + + // Draw the buffer image (on top of the background image) + canvas.drawBitmap(mDrawingBuffer, 0, 0, null); + } + + /** + * Sets the sound level + * + * @param rmsdB The level of the sound, in dB. + */ + public void setRmsdB(float rmsdB) { + float level = ((rmsdB - AUDIO_METER_MIN_DB) / AUDIO_METER_DB_RANGE); + + level = Math.min(Math.max(0.0f, level), 1.0f); + + // We smooth towards the new level + if (level > mLevel) { + mLevel = (level - mLevel) * UP_SMOOTHING_FACTOR + mLevel; + } else { + mLevel = (level - mLevel) * DOWN_SMOOTHING_FACTOR + mLevel; + } + invalidate(); + } + + public void start() { + mHandler.post(mDrawFrame); + } + + public void stop() { + mHandler.removeCallbacks(mDrawFrame); + } +} diff --git a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java index 9a6c3a83a..a02dbcb55 100644 --- a/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java +++ b/java/src/com/android/inputmethod/voice/VoiceIMEConnector.java @@ -30,6 +30,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.net.Uri; import android.os.IBinder; import android.preference.PreferenceManager; @@ -78,6 +79,9 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { // dialog is already showing a voice search button. private static final String IME_OPTION_NO_MICROPHONE = "nm"; + @SuppressWarnings("unused") + private static final String TAG = "VoiceIMEConnector"; + private boolean mAfterVoiceInput; private boolean mHasUsedVoiceInput; private boolean mHasUsedVoiceInputUnsupportedLocale; @@ -164,8 +168,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { } } - private void showVoiceWarningDialog(final boolean swipe, IBinder token, - final boolean configurationChanging) { + private void showVoiceWarningDialog(final boolean swipe, IBinder token) { if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) { return; } @@ -176,7 +179,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { @Override public void onClick(DialogInterface dialog, int whichButton) { mVoiceInput.logKeyboardWarningDialogOk(); - reallyStartListening(swipe, configurationChanging); + reallyStartListening(swipe); } }); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @@ -519,22 +522,38 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { onCancelVoice(); } - public void switchToRecognitionStatusView(final boolean configurationChanging) { - final boolean configChanged = configurationChanging; + public void switchToRecognitionStatusView(final Configuration configuration) { mHandler.post(new Runnable() { @Override public void run() { mService.setCandidatesViewShown(false); mRecognizing = true; + mVoiceInput.newView(); View v = mVoiceInput.getView(); + ViewParent p = v.getParent(); if (p != null && p instanceof ViewGroup) { - ((ViewGroup)p).removeView(v); + ((ViewGroup) p).removeView(v); + } + + View keyboardView = KeyboardSwitcher.getInstance().getInputView(); + + // The full height of the keyboard is difficult to calculate + // as the dimension is expressed in "mm" and not in "pixel" + // As we add mm, we don't know how the rounding is going to work + // thus we may end up with few pixels extra (or less). + if (keyboardView != null) { + int h = keyboardView.getHeight(); + if (h > 0) { + View popupLayout = v.findViewById(R.id.popup_layout); + popupLayout.getLayoutParams().height = h; + } } mService.setInputView(v); mService.updateInputViewShown(); - if (configChanged) { - mVoiceInput.onConfigurationChanged(); + + if (configuration != null) { + mVoiceInput.onConfigurationChanged(configuration); } }}); } @@ -544,7 +563,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { mImm.switchToLastInputMethod(token); } - private void reallyStartListening(boolean swipe, final boolean configurationChanging) { + private void reallyStartListening(boolean swipe) { if (!VOICE_INSTALLED) { return; } @@ -573,22 +592,21 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { FieldContext context = makeFieldContext(); mVoiceInput.startListening(context, swipe); - switchToRecognitionStatusView(configurationChanging); + switchToRecognitionStatusView(null); } - public void startListening(final boolean swipe, IBinder token, - final boolean configurationChanging) { + public void startListening(final boolean swipe, IBinder token) { + // TODO: remove swipe which is no longer used. if (VOICE_INSTALLED) { if (needsToShowWarningDialog()) { // Calls reallyStartListening if user clicks OK, does nothing if user clicks Cancel. - showVoiceWarningDialog(swipe, token, configurationChanging); + showVoiceWarningDialog(swipe, token); } else { - reallyStartListening(swipe, configurationChanging); + reallyStartListening(swipe); } } } - private boolean fieldCanDoVoice(FieldContext fieldContext) { return !mPasswordText && mVoiceInput != null @@ -632,7 +650,7 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { // Close keyboard view if it is been shown. if (KeyboardSwitcher.getInstance().isInputViewShown()) KeyboardSwitcher.getInstance().getInputView().purgeKeyboardAndClosing(); - startListening(false, token, false); + startListening(false, token); } // If we have no token, onAttachedToWindow will take care of showing dialog and start // listening. @@ -644,9 +662,9 @@ public class VoiceIMEConnector implements VoiceInput.UiListener { mSubtypeSwitcher.setVoiceInput(mVoiceInput); } - public void onConfigurationChanged(boolean configurationChanging) { + public void onConfigurationChanged(Configuration configuration) { if (mRecognizing) { - switchToRecognitionStatusView(configurationChanging); + switchToRecognitionStatusView(configuration); } } diff --git a/java/src/com/android/inputmethod/voice/VoiceInput.java b/java/src/com/android/inputmethod/voice/VoiceInput.java index f77b4dd0d..ffa349fde 100644 --- a/java/src/com/android/inputmethod/voice/VoiceInput.java +++ b/java/src/com/android/inputmethod/voice/VoiceInput.java @@ -22,6 +22,7 @@ import com.android.inputmethod.latin.R; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -129,12 +130,17 @@ public class VoiceInput implements OnClickListener { private final static int MSG_CLOSE_ERROR_DIALOG = 1; + private final static int MSG_RESET = 2; + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { - if (msg.what == MSG_CLOSE_ERROR_DIALOG) { + if (msg.what == MSG_RESET || msg.what == MSG_CLOSE_ERROR_DIALOG) { mState = DEFAULT; mRecognitionView.finish(); + } + + if (msg.what == MSG_CLOSE_ERROR_DIALOG) { mUiListener.onCancelVoice(); } } @@ -277,8 +283,9 @@ public class VoiceInput implements OnClickListener { * The configuration of the IME changed and may have caused the views to be layed out * again. Restore the state of the recognition view. */ - public void onConfigurationChanged() { + public void onConfigurationChanged(Configuration configuration) { mRecognitionView.restoreState(); + mRecognitionView.getView().dispatchConfigurationChanged(configuration); } /** @@ -509,7 +516,7 @@ public class VoiceInput implements OnClickListener { mState = DEFAULT; // Remove all pending tasks (e.g., timers to cancel voice input) - mHandler.removeMessages(MSG_CLOSE_ERROR_DIALOG); + mHandler.removeMessages(MSG_RESET); mSpeechRecognizer.cancel(); mUiListener.onCancelVoice(); |