diff --git a/tests/src/com/android/inputmethod/latin/InputLogicTests.java b/tests/src/com/android/inputmethod/latin/InputLogicTests.java index 19e1c3d2b..cbfe8b212 100644 --- a/tests/src/com/android/inputmethod/latin/InputLogicTests.java +++ b/tests/src/com/android/inputmethod/latin/InputLogicTests.java @@ -16,194 +16,9 @@ package com.android.inputmethod.latin; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Looper; -import android.os.MessageQueue; -import android.preference.PreferenceManager; -import android.test.ServiceTestCase; -import android.text.InputType; -import android.text.SpannableStringBuilder; -import android.text.style.SuggestionSpan; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import android.view.View; -import android.view.inputmethod.BaseInputConnection; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputConnection; -import android.widget.FrameLayout; -import android.widget.TextView; - -import com.android.inputmethod.keyboard.Key; import com.android.inputmethod.keyboard.Keyboard; -import com.android.inputmethod.keyboard.KeyboardActionListener; -import com.android.inputmethod.latin.spellcheck.AndroidSpellCheckerService; // for proximity info -import com.android.inputmethod.latin.spellcheck.SpellCheckerProximityInfo; -import java.util.Arrays; -import java.util.HashMap; - -public class InputLogicTests extends ServiceTestCase { - - private static final String PREF_DEBUG_MODE = "debug_mode"; - - private LatinIME mLatinIME; - private Keyboard mKeyboard; - private TextView mTextView; - private InputConnection mInputConnection; - - public InputLogicTests() { - super(LatinIME.class); - } - - // returns the previous setting value - private boolean setDebugMode(final boolean mode) { - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME); - final boolean previousDebugSetting = prefs.getBoolean(PREF_DEBUG_MODE, false); - final SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(PREF_DEBUG_MODE, true); - editor.commit(); - return previousDebugSetting; - } - - @Override - protected void setUp() { - try { - super.setUp(); - } catch (Exception e) { - e.printStackTrace(); - } - mTextView = new TextView(getContext()); - mTextView.setInputType(InputType.TYPE_CLASS_TEXT); - mTextView.setEnabled(true); - setupService(); - mLatinIME = getService(); - final boolean previousDebugSetting = setDebugMode(true); - mLatinIME.onCreate(); - setDebugMode(previousDebugSetting); - final EditorInfo ei = new EditorInfo(); - ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; - final InputConnection ic = mTextView.onCreateInputConnection(ei); - ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; - final LayoutInflater inflater = - (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); - final ViewGroup vg = new FrameLayout(getContext()); - final View inputView = inflater.inflate(R.layout.input_view, vg); - mLatinIME.setInputView(inputView); - mLatinIME.onBindInput(); - mLatinIME.onCreateInputView(); - mLatinIME.onStartInputView(ei, false); - mLatinIME.onCreateInputMethodInterface().startInput(ic, ei); - mInputConnection = ic; - mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard(); - changeLanguage("en_US"); - } - - // We need to run the messages added to the handler from LatinIME. The only way to do - // that is to call Looper#loop() on the right looper, so we're going to get the looper - // object and call #loop() here. The messages in the handler actually run on the UI - // thread of the keyboard by design of the handler, so we want to call it synchronously - // on the same thread that the tests are running on to mimic the actual environment as - // closely as possible. - // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method - // is called, so we need to do that at the right time so that #loop() returns at some - // point and we don't end up in an infinite loop. - // After we quit, the looper is still technically ready to process more messages but - // the handler will refuse to enqueue any because #quit() has been called and it - // explicitly tests for it on message enqueuing, so we'll have to reset it so that - // it lets us continue normal operation. - private void runMessages() { - // Here begins deep magic. - final Looper looper = mLatinIME.mHandler.getLooper(); - mLatinIME.mHandler.post(new Runnable() { - @Override - public void run() { - looper.quit(); - } - }); - // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue). - // Once #quit() is called remaining messages are not processed, which is why we post - // a message that calls it instead of calling it directly. - looper.loop(); - - // Once #quit() has been called, the message queue has an "mQuiting" field that prevents - // any subsequent post in this queue. However the queue itself is still fully functional! - // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal, - // coming back to this method to run the messages. - MessageQueue queue = looper.getQueue(); - try { - // However there is no way of doing it externally, and mQuiting is private. - // So... get out the big guns. - java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting"); - f.setAccessible(true); // What do you mean "private"? - f.setBoolean(queue, false); - } catch (NoSuchFieldException e) { - throw new RuntimeException(e); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } - } - - // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME. - private void type(final int codePoint) { - // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the - // code (although multitouch/slide input and other factors make the sequencing complicated). - // They are supposed to be entirely deconnected from the input logic from LatinIME point of - // view and only delegates to the parts of the code that care. So we don't include them here - // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies, - // but keep them in mind if something breaks. Commenting them out as is should work. - //mLatinIME.onPressKey(codePoint); - for (final Key key : mKeyboard.mKeys) { - if (key.mCode == codePoint) { - final int x = key.mX + key.mWidth / 2; - final int y = key.mY + key.mHeight / 2; - mLatinIME.onCodeInput(codePoint, x, y); - return; - } - } - mLatinIME.onCodeInput(codePoint, - KeyboardActionListener.SPELL_CHECKER_COORDINATE, - KeyboardActionListener.SPELL_CHECKER_COORDINATE); - //mLatinIME.onReleaseKey(codePoint, false); - } - - private void type(final String stringToType) { - for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) { - type(stringToType.codePointAt(i)); - } - } - - private void waitForDictionaryToBeLoaded() { - int remainingAttempts = 10; - while (remainingAttempts > 0 && !mLatinIME.mSuggest.hasMainDictionary()) { - try { - Thread.sleep(200); - } catch (InterruptedException e) { - // Don't do much - } finally { - --remainingAttempts; - } - } - if (!mLatinIME.mSuggest.hasMainDictionary()) { - throw new RuntimeException("Can't initialize the main dictionary"); - } - } - - private void changeLanguage(final String locale) { - SubtypeSwitcher.getInstance().updateSubtype( - new ArbitrarySubtype(locale, LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE)); - waitForDictionaryToBeLoaded(); - } - - - // Helper to avoid writing the try{}catch block each time - private static void sleep(final int milliseconds) { - try { - Thread.sleep(milliseconds); - } catch (InterruptedException e) {} - } +public class InputLogicTests extends InputTestsBase { public void testTypeWord() { final String WORD_TO_TYPE = "abcd"; @@ -581,37 +396,6 @@ public class InputLogicTests extends ServiceTestCase { EXPECTED_RESULT, mTextView.getText().toString()); } - // A helper class to ease span tests - private static class Span { - final SpannableStringBuilder mInputText; - final SuggestionSpan mSpan; - final int mStart; - final int mEnd; - // The supplied CharSequence should be an instance of SpannableStringBuilder, - // and it should contain exactly zero or one SuggestionSpan. Otherwise, an exception - // is thrown. - public Span(final CharSequence inputText) { - mInputText = (SpannableStringBuilder)inputText; - final SuggestionSpan[] spans = - mInputText.getSpans(0, mInputText.length(), SuggestionSpan.class); - if (0 == spans.length) { - mSpan = null; - mStart = -1; - mEnd = -1; - } else if (1 == spans.length) { - mSpan = spans[0]; - mStart = mInputText.getSpanStart(mSpan); - mEnd = mInputText.getSpanEnd(mSpan); - } else { - throw new RuntimeException("Expected one SuggestionSpan, found " + spans.length); - } - } - public boolean isAutoCorrectionIndicator() { - return 0 != (SuggestionSpan.FLAG_AUTO_CORRECTION & mSpan.getFlags()); - } - } - - static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200; // The message is posted with a 100 ms delay public void testBlueUnderline() { final String STRING_TO_TYPE = "tgis"; final int EXPECTED_SPAN_START = 0; diff --git a/tests/src/com/android/inputmethod/latin/InputTestsBase.java b/tests/src/com/android/inputmethod/latin/InputTestsBase.java new file mode 100644 index 000000000..2c63993d7 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/InputTestsBase.java @@ -0,0 +1,235 @@ +/* + * 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.latin; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Looper; +import android.os.MessageQueue; +import android.preference.PreferenceManager; +import android.test.ServiceTestCase; +import android.text.InputType; +import android.text.SpannableStringBuilder; +import android.text.style.SuggestionSpan; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.widget.FrameLayout; +import android.widget.TextView; + +import com.android.inputmethod.keyboard.Key; +import com.android.inputmethod.keyboard.Keyboard; +import com.android.inputmethod.keyboard.KeyboardActionListener; + +import java.util.Arrays; +import java.util.HashMap; + +public class InputTestsBase extends ServiceTestCase { + + private static final String PREF_DEBUG_MODE = "debug_mode"; + + // The message that sets the underline is posted with a 100 ms delay + protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200; + + protected LatinIME mLatinIME; + protected Keyboard mKeyboard; + protected TextView mTextView; + protected InputConnection mInputConnection; + + // A helper class to ease span tests + public static class Span { + final SpannableStringBuilder mInputText; + final SuggestionSpan mSpan; + final int mStart; + final int mEnd; + // The supplied CharSequence should be an instance of SpannableStringBuilder, + // and it should contain exactly zero or one SuggestionSpan. Otherwise, an exception + // is thrown. + public Span(final CharSequence inputText) { + mInputText = (SpannableStringBuilder)inputText; + final SuggestionSpan[] spans = + mInputText.getSpans(0, mInputText.length(), SuggestionSpan.class); + if (0 == spans.length) { + mSpan = null; + mStart = -1; + mEnd = -1; + } else if (1 == spans.length) { + mSpan = spans[0]; + mStart = mInputText.getSpanStart(mSpan); + mEnd = mInputText.getSpanEnd(mSpan); + } else { + throw new RuntimeException("Expected one SuggestionSpan, found " + spans.length); + } + } + public boolean isAutoCorrectionIndicator() { + return 0 != (SuggestionSpan.FLAG_AUTO_CORRECTION & mSpan.getFlags()); + } + } + + public InputTestsBase() { + super(LatinIME.class); + } + + // returns the previous setting value + protected boolean setDebugMode(final boolean mode) { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME); + final boolean previousDebugSetting = prefs.getBoolean(PREF_DEBUG_MODE, false); + final SharedPreferences.Editor editor = prefs.edit(); + editor.putBoolean(PREF_DEBUG_MODE, true); + editor.commit(); + return previousDebugSetting; + } + + @Override + protected void setUp() { + try { + super.setUp(); + } catch (Exception e) { + e.printStackTrace(); + } + mTextView = new TextView(getContext()); + mTextView.setInputType(InputType.TYPE_CLASS_TEXT); + mTextView.setEnabled(true); + setupService(); + mLatinIME = getService(); + final boolean previousDebugSetting = setDebugMode(true); + mLatinIME.onCreate(); + setDebugMode(previousDebugSetting); + final EditorInfo ei = new EditorInfo(); + ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; + final InputConnection ic = mTextView.onCreateInputConnection(ei); + ei.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT; + final LayoutInflater inflater = + (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final ViewGroup vg = new FrameLayout(getContext()); + final View inputView = inflater.inflate(R.layout.input_view, vg); + mLatinIME.setInputView(inputView); + mLatinIME.onBindInput(); + mLatinIME.onCreateInputView(); + mLatinIME.onStartInputView(ei, false); + mLatinIME.onCreateInputMethodInterface().startInput(ic, ei); + mInputConnection = ic; + mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard(); + changeLanguage("en_US"); + } + + // We need to run the messages added to the handler from LatinIME. The only way to do + // that is to call Looper#loop() on the right looper, so we're going to get the looper + // object and call #loop() here. The messages in the handler actually run on the UI + // thread of the keyboard by design of the handler, so we want to call it synchronously + // on the same thread that the tests are running on to mimic the actual environment as + // closely as possible. + // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method + // is called, so we need to do that at the right time so that #loop() returns at some + // point and we don't end up in an infinite loop. + // After we quit, the looper is still technically ready to process more messages but + // the handler will refuse to enqueue any because #quit() has been called and it + // explicitly tests for it on message enqueuing, so we'll have to reset it so that + // it lets us continue normal operation. + protected void runMessages() { + // Here begins deep magic. + final Looper looper = mLatinIME.mHandler.getLooper(); + mLatinIME.mHandler.post(new Runnable() { + @Override + public void run() { + looper.quit(); + } + }); + // The only way to get out of Looper#loop() is to call #quit() on it (or on its queue). + // Once #quit() is called remaining messages are not processed, which is why we post + // a message that calls it instead of calling it directly. + looper.loop(); + + // Once #quit() has been called, the message queue has an "mQuiting" field that prevents + // any subsequent post in this queue. However the queue itself is still fully functional! + // If we have a way of resetting "queue.mQuiting" then we can continue using it as normal, + // coming back to this method to run the messages. + MessageQueue queue = looper.getQueue(); + try { + // However there is no way of doing it externally, and mQuiting is private. + // So... get out the big guns. + java.lang.reflect.Field f = MessageQueue.class.getDeclaredField("mQuiting"); + f.setAccessible(true); // What do you mean "private"? + f.setBoolean(queue, false); + } catch (NoSuchFieldException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME. + protected void type(final int codePoint) { + // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the + // code (although multitouch/slide input and other factors make the sequencing complicated). + // They are supposed to be entirely deconnected from the input logic from LatinIME point of + // view and only delegates to the parts of the code that care. So we don't include them here + // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies, + // but keep them in mind if something breaks. Commenting them out as is should work. + //mLatinIME.onPressKey(codePoint); + for (final Key key : mKeyboard.mKeys) { + if (key.mCode == codePoint) { + final int x = key.mX + key.mWidth / 2; + final int y = key.mY + key.mHeight / 2; + mLatinIME.onCodeInput(codePoint, x, y); + return; + } + } + mLatinIME.onCodeInput(codePoint, + KeyboardActionListener.SPELL_CHECKER_COORDINATE, + KeyboardActionListener.SPELL_CHECKER_COORDINATE); + //mLatinIME.onReleaseKey(codePoint, false); + } + + protected void type(final String stringToType) { + for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) { + type(stringToType.codePointAt(i)); + } + } + + protected void waitForDictionaryToBeLoaded() { + int remainingAttempts = 10; + while (remainingAttempts > 0 && !mLatinIME.mSuggest.hasMainDictionary()) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + // Don't do much + } finally { + --remainingAttempts; + } + } + if (!mLatinIME.mSuggest.hasMainDictionary()) { + throw new RuntimeException("Can't initialize the main dictionary"); + } + } + + protected void changeLanguage(final String locale) { + SubtypeSwitcher.getInstance().updateSubtype( + new ArbitrarySubtype(locale, LatinIME.SUBTYPE_EXTRA_VALUE_ASCII_CAPABLE)); + waitForDictionaryToBeLoaded(); + } + + + // Helper to avoid writing the try{}catch block each time + protected static void sleep(final int milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (InterruptedException e) {} + } +}