InputTestsBase.java revision d5781eef628c2cd4ac38029040746daa4679d637
1/* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import android.content.Context; 20import android.content.SharedPreferences; 21import android.os.Looper; 22import android.preference.PreferenceManager; 23import android.test.ServiceTestCase; 24import android.text.InputType; 25import android.text.SpannableStringBuilder; 26import android.text.style.CharacterStyle; 27import android.text.style.SuggestionSpan; 28import android.view.LayoutInflater; 29import android.view.View; 30import android.view.ViewGroup; 31import android.view.inputmethod.EditorInfo; 32import android.view.inputmethod.InputConnection; 33import android.widget.EditText; 34import android.widget.FrameLayout; 35 36import com.android.inputmethod.keyboard.Key; 37import com.android.inputmethod.keyboard.Keyboard; 38import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 39 40import java.util.Locale; 41 42public class InputTestsBase extends ServiceTestCase<LatinIME> { 43 44 private static final String PREF_DEBUG_MODE = "debug_mode"; 45 46 // The message that sets the underline is posted with a 100 ms delay 47 protected static final int DELAY_TO_WAIT_FOR_UNDERLINE = 200; 48 49 protected LatinIME mLatinIME; 50 protected Keyboard mKeyboard; 51 protected MyEditText mEditText; 52 protected View mInputView; 53 protected InputConnection mInputConnection; 54 55 // A helper class to ease span tests 56 public static class SpanGetter { 57 final SpannableStringBuilder mInputText; 58 final CharacterStyle mSpan; 59 final int mStart; 60 final int mEnd; 61 // The supplied CharSequence should be an instance of SpannableStringBuilder, 62 // and it should contain exactly zero or one span. Otherwise, an exception 63 // is thrown. 64 public SpanGetter(final CharSequence inputText, 65 final Class<? extends CharacterStyle> spanType) { 66 mInputText = (SpannableStringBuilder)inputText; 67 final CharacterStyle[] spans = 68 mInputText.getSpans(0, mInputText.length(), spanType); 69 if (0 == spans.length) { 70 mSpan = null; 71 mStart = -1; 72 mEnd = -1; 73 } else if (1 == spans.length) { 74 mSpan = spans[0]; 75 mStart = mInputText.getSpanStart(mSpan); 76 mEnd = mInputText.getSpanEnd(mSpan); 77 } else { 78 throw new RuntimeException("Expected one span, found " + spans.length); 79 } 80 } 81 public boolean isAutoCorrectionIndicator() { 82 return (mSpan instanceof SuggestionSpan) && 83 0 != (SuggestionSpan.FLAG_AUTO_CORRECTION & ((SuggestionSpan)mSpan).getFlags()); 84 } 85 public String[] getSuggestions() { 86 return ((SuggestionSpan)mSpan).getSuggestions(); 87 } 88 } 89 90 // A helper class to increase control over the EditText 91 public static class MyEditText extends EditText { 92 public Locale mCurrentLocale; 93 public MyEditText(final Context c) { 94 super(c); 95 } 96 97 // overriding hidden API in EditText 98 public Locale getTextServicesLocale() { 99 // This method is necessary because EditText is asking this method for the language 100 // to check the spell in. If we don't override this, the spell checker will run in 101 // whatever language the keyboard is currently set on the test device, ignoring any 102 // settings we do inside the tests. 103 return mCurrentLocale; 104 } 105 106 // overriding hidden API in EditText 107 public Locale getSpellCheckerLocale() { 108 // This method is necessary because EditText is asking this method for the language 109 // to check the spell in. If we don't override this, the spell checker will run in 110 // whatever language the keyboard is currently set on the test device, ignoring any 111 // settings we do inside the tests. 112 return mCurrentLocale; 113 } 114 115 } 116 117 public InputTestsBase() { 118 super(LatinIME.class); 119 } 120 121 // TODO: Isn't there a way to make this generic somehow? We can take a <T> and return a <T> 122 // but we'd have to dispatch types on editor.put...() functions 123 protected boolean setBooleanPreference(final String key, final boolean value, 124 final boolean defaultValue) { 125 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mLatinIME); 126 final boolean previousSetting = prefs.getBoolean(key, defaultValue); 127 final SharedPreferences.Editor editor = prefs.edit(); 128 editor.putBoolean(key, value); 129 editor.commit(); 130 return previousSetting; 131 } 132 133 // returns the previous setting value 134 protected boolean setDebugMode(final boolean value) { 135 return setBooleanPreference(PREF_DEBUG_MODE, value, false); 136 } 137 138 @Override 139 protected void setUp() throws Exception { 140 super.setUp(); 141 mEditText = new MyEditText(getContext()); 142 final int inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT 143 | InputType.TYPE_TEXT_FLAG_MULTI_LINE; 144 mEditText.setInputType(inputType); 145 mEditText.setEnabled(true); 146 setupService(); 147 mLatinIME = getService(); 148 final boolean previousDebugSetting = setDebugMode(true); 149 mLatinIME.onCreate(); 150 setDebugMode(previousDebugSetting); 151 final EditorInfo ei = new EditorInfo(); 152 final InputConnection ic = mEditText.onCreateInputConnection(ei); 153 final LayoutInflater inflater = 154 (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 155 final ViewGroup vg = new FrameLayout(getContext()); 156 mInputView = inflater.inflate(R.layout.input_view, vg); 157 mLatinIME.onCreateInputMethodInterface().startInput(ic, ei); 158 mLatinIME.setInputView(mInputView); 159 mLatinIME.onBindInput(); 160 mLatinIME.onCreateInputView(); 161 mLatinIME.onStartInputView(ei, false); 162 mInputConnection = ic; 163 changeLanguage("en_US"); 164 } 165 166 // We need to run the messages added to the handler from LatinIME. The only way to do 167 // that is to call Looper#loop() on the right looper, so we're going to get the looper 168 // object and call #loop() here. The messages in the handler actually run on the UI 169 // thread of the keyboard by design of the handler, so we want to call it synchronously 170 // on the same thread that the tests are running on to mimic the actual environment as 171 // closely as possible. 172 // Now, Looper#loop() never exits in normal operation unless the Looper#quit() method 173 // is called, which has a lot of bad side effects. We can however just throw an exception 174 // in the runnable which will unwind the stack and allow us to exit. 175 private final class InterruptRunMessagesException extends RuntimeException { 176 // Empty class 177 } 178 protected void runMessages() { 179 mLatinIME.mHandler.post(new Runnable() { 180 @Override 181 public void run() { 182 throw new InterruptRunMessagesException(); 183 } 184 }); 185 try { 186 Looper.loop(); 187 } catch (InterruptRunMessagesException e) { 188 // Resume normal operation 189 } 190 } 191 192 // type(int) and type(String): helper methods to send a code point resp. a string to LatinIME. 193 protected void type(final int codePoint) { 194 // onPressKey and onReleaseKey are explicitly deactivated here, but they do happen in the 195 // code (although multitouch/slide input and other factors make the sequencing complicated). 196 // They are supposed to be entirely deconnected from the input logic from LatinIME point of 197 // view and only delegates to the parts of the code that care. So we don't include them here 198 // to keep these tests as pinpoint as possible and avoid bringing it too many dependencies, 199 // but keep them in mind if something breaks. Commenting them out as is should work. 200 //mLatinIME.onPressKey(codePoint); 201 for (final Key key : mKeyboard.mKeys) { 202 if (key.mCode == codePoint) { 203 final int x = key.mX + key.mWidth / 2; 204 final int y = key.mY + key.mHeight / 2; 205 mLatinIME.onCodeInput(codePoint, x, y); 206 return; 207 } 208 } 209 mLatinIME.onCodeInput(codePoint, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE); 210 //mLatinIME.onReleaseKey(codePoint, false); 211 } 212 213 protected void type(final String stringToType) { 214 for (int i = 0; i < stringToType.length(); i = stringToType.offsetByCodePoints(i, 1)) { 215 type(stringToType.codePointAt(i)); 216 } 217 } 218 219 protected void waitForDictionaryToBeLoaded() { 220 int remainingAttempts = 300; 221 while (remainingAttempts > 0 && mLatinIME.mSuggest.isCurrentlyWaitingForMainDictionary()) { 222 try { 223 Thread.sleep(200); 224 } catch (InterruptedException e) { 225 // Don't do much 226 } finally { 227 --remainingAttempts; 228 } 229 } 230 if (!mLatinIME.mSuggest.hasMainDictionary()) { 231 throw new RuntimeException("Can't initialize the main dictionary"); 232 } 233 } 234 235 protected void changeLanguage(final String locale) { 236 mEditText.mCurrentLocale = LocaleUtils.constructLocaleFromString(locale); 237 SubtypeSwitcher.getInstance().forceLocale(mEditText.mCurrentLocale); 238 mLatinIME.loadKeyboard(); 239 mKeyboard = mLatinIME.mKeyboardSwitcher.getKeyboard(); 240 waitForDictionaryToBeLoaded(); 241 } 242 243 protected void pickSuggestionManually(final int index, final String suggestion) { 244 mLatinIME.pickSuggestionManually(index, new SuggestedWordInfo(suggestion, 1, 245 SuggestedWordInfo.KIND_CORRECTION, "main")); 246 } 247 248 // Helper to avoid writing the try{}catch block each time 249 protected static void sleep(final int milliseconds) { 250 try { 251 Thread.sleep(milliseconds); 252 } catch (InterruptedException e) {} 253 } 254} 255