PageRenderTime 62ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/ime/latinime/src/com/googlecode/eyesfree/inputmethod/latin/LatinIME.java

http://eyes-free.googlecode.com/
Java | 3197 lines | 2536 code | 347 blank | 314 comment | 755 complexity | d20e6071ec0049b14d3d7f255612dd4e MD5 | raw file
Possible License(s): GPL-3.0, Apache-2.0
  1. /*
  2. * Copyright (C) 2008 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * 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, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.googlecode.eyesfree.inputmethod.latin;
  17. import com.google.android.marvin.aime.AccessibleInputConnection;
  18. import android.app.AlertDialog;
  19. import android.content.BroadcastReceiver;
  20. import android.content.Context;
  21. import android.content.DialogInterface;
  22. import android.content.Intent;
  23. import android.content.IntentFilter;
  24. import android.content.SharedPreferences;
  25. import android.content.res.Configuration;
  26. import android.content.res.Resources;
  27. import android.content.res.XmlResourceParser;
  28. import android.inputmethodservice.InputMethodService;
  29. import android.inputmethodservice.Keyboard;
  30. import android.inputmethodservice.Keyboard.Key;
  31. import android.media.AudioManager;
  32. import android.os.Build;
  33. import android.os.Debug;
  34. import android.os.Handler;
  35. import android.os.Message;
  36. import android.os.SystemClock;
  37. import android.preference.PreferenceActivity;
  38. import android.preference.PreferenceManager;
  39. import android.provider.Settings;
  40. import android.speech.SpeechRecognizer;
  41. import android.telephony.PhoneStateListener;
  42. import android.telephony.TelephonyManager;
  43. import android.text.TextUtils;
  44. import android.util.DisplayMetrics;
  45. import android.util.Log;
  46. import android.util.PrintWriterPrinter;
  47. import android.util.Printer;
  48. import android.view.HapticFeedbackConstants;
  49. import android.view.KeyCharacterMap;
  50. import android.view.KeyEvent;
  51. import android.view.LayoutInflater;
  52. import android.view.View;
  53. import android.view.ViewConfiguration;
  54. import android.view.ViewGroup;
  55. import android.view.ViewParent;
  56. import android.view.Window;
  57. import android.view.WindowManager;
  58. import android.view.inputmethod.CompletionInfo;
  59. import android.view.inputmethod.EditorInfo;
  60. import android.view.inputmethod.ExtractedText;
  61. import android.view.inputmethod.ExtractedTextRequest;
  62. import android.view.inputmethod.InputConnection;
  63. import android.view.inputmethod.InputMethodManager;
  64. import android.widget.LinearLayout;
  65. import com.googlecode.eyesfree.inputmethod.FeedbackUtil;
  66. import com.googlecode.eyesfree.inputmethod.PersistentInputMethodService;
  67. import com.googlecode.eyesfree.inputmethod.latin.LatinIMEUtil.RingCharBuffer;
  68. import com.googlecode.eyesfree.inputmethod.latin.tutorial.LatinTutorialDialog;
  69. import com.googlecode.eyesfree.inputmethod.voice.FieldContext;
  70. import com.googlecode.eyesfree.inputmethod.voice.SettingsUtil;
  71. import com.googlecode.eyesfree.inputmethod.voice.VoiceInput;
  72. import org.xmlpull.v1.XmlPullParserException;
  73. import java.io.FileDescriptor;
  74. import java.io.IOException;
  75. import java.io.PrintWriter;
  76. import java.util.ArrayList;
  77. import java.util.Collections;
  78. import java.util.HashMap;
  79. import java.util.List;
  80. import java.util.Locale;
  81. import java.util.Map;
  82. /**
  83. * Input method implementation for Qwerty'ish keyboard.
  84. */
  85. public class LatinIME extends PersistentInputMethodService implements VoiceInput.UiListener,
  86. SharedPreferences.OnSharedPreferenceChangeListener {
  87. private static final String TAG = "LatinIME";
  88. private static final boolean PERF_DEBUG = false;
  89. static final boolean DEBUG = false;
  90. static final boolean TRACE = false;
  91. static final boolean VOICE_INSTALLED = supportsVoiceInput();
  92. static final boolean ENABLE_VOICE_BUTTON = supportsVoiceInput();
  93. /**
  94. * Permission to send {@link Intent} broadcast requests to LatinIME.
  95. */
  96. public static final String PERMISSION_REQUEST = "com.googlecode.eyesfree.inputmethod.latin.PERMISSION_REQUEST";
  97. // Mode change request broadcast constants
  98. public static final String REQUEST_KEYBOARD_MODE_CHANGE = "com.googlecode.eyesfree.inputmethod.latin.REQUEST_KEYBOARD_MODE_CHANGE";
  99. public static final String REQUEST_KEYBOARD_MODE_UPDATE = "com.googlecode.eyesfree.inputmethod.latin.REQUEST_KEYBOARD_MODE_UPDATE";
  100. // Mode change broadcast constants
  101. public static final String BROADCAST_KEYBOARD_MODE_CHANGE = "com.googlecode.eyesfree.inputmethod.latin.KEYBOARD_MODE_CHANGE";
  102. public static final String BROADCAST_KEYBOARD_MODE_UPDATE = "com.googlecode.eyesfree.inputmethod.latin.KEYBOARD_MODE_UPDATE";
  103. public static final String BROADCAST_LEFT_KEYBOARD_AREA = "com.googlecode.eyesfree.inputmethod.latin.LEFT_KEYBOARD_AREA";
  104. public static final String BROADCAST_ENTERED_KEYBOARD_AREA = "com.googlecode.eyesfree.inputmethod.latin.ENTERED_KEYBOARD_AREA";
  105. public static final String BROADCAST_UP_OUTSIDE_KEYBOARD = "com.googlecode.eyesfree.inputmethod.latin.TYPED_OUTSIDE_KEYBOARD";
  106. public static final String BROADCAST_GRANULARITY_CHANGE = "com.googlecode.eyesfree.inputmethod.latin.GRANULARITY_CHANGE";
  107. public static final String EXTRA_MODE = "mode";
  108. public static final String EXTRA_GRANULARITY = "granularity";
  109. // Vibration pattern constants
  110. private static final long[] VIBRATE_PATTERN_SELECTED = new long[] {
  111. 0, 30
  112. };
  113. private static final long[] VIBRATE_PATTERN_SWIPE = new long[] {
  114. 0, 20, 0, 20
  115. };
  116. private static final long[] VIBRATE_PATTERN_TAP = new long[] {
  117. 0, 20
  118. };
  119. private static final long[] VIBRATE_PATTERN_DOUBLE_TAP = new long[] {
  120. 0, 20, 0, 20
  121. };
  122. private static final long[] VIBRATE_PATTERN_MODE_CHANGE = new long[] {
  123. 0, 50
  124. };
  125. private static final long[] VIBRATE_PATTERN_ENTERED = new long[] {
  126. 0, 20, 0, 50
  127. };
  128. private static final long[] VIBRATE_PATTERN_LEFT = new long[] {
  129. 0, 50, 0, 20
  130. };
  131. private static final long[] VIBRATE_PATTERN_LONG_PRESS = new long[] {
  132. 0, 20, 40, 20, 40, 20, 40, 20
  133. };
  134. // Sound resource constants
  135. private static final int SOUND_RESOURCE_SELECTED = R.raw.tick;
  136. private static final int SOUND_RESOURCE_SWIPE = R.raw.tick;
  137. private static final int SOUND_RESOURCE_TAP = R.raw.tick;
  138. private static final int SOUND_RESOURCE_DOUBLE_TAP = R.raw.tick;
  139. private static final int SOUND_RESOURCE_LONG_PRESS = R.raw.tick;
  140. private static final String PREF_AUTO_SWITCH = "auto_switch";
  141. private static final String PREF_VIBRATE_ON = "vibrate_on";
  142. private static final String PREF_SOUND_ON = "sound_on";
  143. private static final String PREF_POPUP_ON = "popup_on";
  144. private static final String PREF_AUTO_CAP = "auto_cap";
  145. private static final String PREF_QUICK_FIXES = "quick_fixes";
  146. private static final String PREF_SHOW_SUGGESTIONS = "show_suggestions";
  147. private static final String PREF_AUTO_COMPLETE = "auto_complete";
  148. private static final String PREF_BIGRAM_SUGGESTIONS = "bigram_suggestion";
  149. private static final String PREF_VOICE_MODE = "voice_mode";
  150. private static final String PREF_LINEAR_NAVIGATION = "linear_navigation";
  151. // Whether or not the user has used voice input before (and thus, whether to
  152. // show the first-run warning dialog or not).
  153. private static final String PREF_HAS_USED_VOICE_INPUT = "has_used_voice_input";
  154. // Whether or not the user has used voice input from an unsupported locale
  155. // UI before. For example, the user has a Chinese UI but activates voice
  156. // input.
  157. private static final String PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE = "has_used_voice_input_unsupported_locale";
  158. // A list of locales which are supported by default for voice input, unless
  159. // we get a different list from Gservices.
  160. public static final String DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES = "en " + "en_US " + "en_GB "
  161. + "en_AU " + "en_CA " + "en_IE " + "en_IN " + "en_NZ " + "en_SG " + "en_ZA ";
  162. // The private IME option used to indicate that no microphone should be
  163. // shown for a given text field. For instance this is specified by the
  164. // search dialog
  165. // when the dialog is already showing a voice search button.
  166. private static final String IME_OPTION_NO_MICROPHONE = "nm";
  167. public static final String PREF_HAS_RUN_TUTORIAL = "has_run_tutorial";
  168. public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
  169. public static final String PREF_INPUT_LANGUAGE = "input_language";
  170. private static final String PREF_RECORRECTION_ENABLED = "recorrection_enabled";
  171. private static final int MSG_UPDATE_SUGGESTIONS = 0;
  172. private static final int MSG_UPDATE_SHIFT_STATE = 1;
  173. private static final int MSG_VOICE_RESULTS = 2;
  174. private static final int MSG_UPDATE_OLD_SUGGESTIONS = 3;
  175. // How many continuous deletes at which to start deleting at a higher speed.
  176. private static final int DELETE_ACCELERATE_AT = 20;
  177. // Key events coming any faster than this are long-presses.
  178. private static final int QUICK_PRESS = 200;
  179. static final int KEYCODE_ENTER = '\n';
  180. static final int KEYCODE_SPACE = ' ';
  181. static final int KEYCODE_PERIOD = '.';
  182. // Contextual menu positions
  183. private static final int POS_METHOD = 0;
  184. private static final int POS_SETTINGS = 1;
  185. private static final int POS_ACCESSIBILITY_SETTINGS = 2;
  186. // Receive phone call status updates.
  187. private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
  188. @Override
  189. public void onCallStateChanged(int state, String incomingNumber) {
  190. super.onCallStateChanged(state, incomingNumber);
  191. switch (state) {
  192. case TelephonyManager.CALL_STATE_IDLE:
  193. // This is a no-op if the cache has been invalidated.
  194. mInCallScreen = false;
  195. requestKeyboardMode(mCachedForcedMode);
  196. break;
  197. case TelephonyManager.CALL_STATE_RINGING:
  198. // Cache the current forced mode and switch to FORCE_HIDDEN.
  199. int currentMode = getKeyboardMode();
  200. requestKeyboardMode(FORCE_HIDDEN);
  201. mCachedForcedMode = currentMode;
  202. mInCallScreen = true;
  203. break;
  204. case TelephonyManager.CALL_STATE_OFFHOOK:
  205. // The user picked up the call. Do nothing.
  206. break;
  207. }
  208. }
  209. };
  210. // Receive ringer mode changes to detect silent mode and broadcast requests.
  211. private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
  212. @Override
  213. public void onReceive(Context context, Intent intent) {
  214. String action = intent.getAction();
  215. if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(action)) {
  216. updateRingerMode();
  217. } else if (REQUEST_KEYBOARD_MODE_CHANGE.equals(action)) {
  218. // Don't make changes unless accessibility is enabled.
  219. if (isAccessibilityEnabled()) {
  220. requestKeyboardMode(intent.getIntExtra(EXTRA_MODE, FORCE_DPAD));
  221. }
  222. } else if (REQUEST_KEYBOARD_MODE_UPDATE.equals(action)) {
  223. Intent broadcast = new Intent(BROADCAST_KEYBOARD_MODE_UPDATE);
  224. broadcast.putExtra(EXTRA_MODE, mForcedMode);
  225. sendBroadcast(broadcast, PERMISSION_REQUEST);
  226. }
  227. }
  228. };
  229. // private LatinKeyboardView mInputView;
  230. private LinearLayout mCandidateViewContainer;
  231. private CandidateView mCandidateView;
  232. private Suggest mSuggest;
  233. private CompletionInfo[] mCompletions;
  234. private AlertDialog mOptionsDialog;
  235. private AlertDialog mVoiceWarningDialog;
  236. /* package */KeyboardSwitcher mKeyboardSwitcher;
  237. private UserDictionary mUserDictionary;
  238. private UserBigramDictionary mUserBigramDictionary;
  239. private ContactsDictionary mContactsDictionary;
  240. private AutoDictionary mAutoDictionary;
  241. private Hints mHints;
  242. private Resources mResources;
  243. private TelephonyManager mTelephonyManager;
  244. private AccessibilityUtils mAccessibilityUtils;
  245. private FeedbackUtil mFeedbackUtil;
  246. public static final int FORCE_HIDDEN = 0;
  247. public static final int FORCE_DPAD = 1;
  248. public static final int FORCE_CACHED = 2;
  249. private static final int NUM_FORCE_MODES = 3;
  250. private static final int FORCE_NONE = -1;
  251. private int mForcedMode = FORCE_DPAD;
  252. private int mCachedMode = KeyboardSwitcher.MODE_NONE;
  253. private boolean mCachedPredictionOn = false;
  254. private int mCachedForcedMode = FORCE_NONE;
  255. private boolean mInCallScreen = false;
  256. private String mInputLocale;
  257. private String mSystemLocale;
  258. private LanguageSwitcher mLanguageSwitcher;
  259. private StringBuilder mComposing = new StringBuilder();
  260. private WordComposer mWord = new WordComposer();
  261. private int mCommittedLength;
  262. private boolean mPredicting;
  263. private boolean mRecognizing;
  264. private boolean mAfterVoiceInput;
  265. private boolean mImmediatelyAfterVoiceInput;
  266. private boolean mShowingVoiceSuggestions;
  267. private boolean mVoiceInputHighlighted;
  268. private boolean mEnableVoiceButton;
  269. private CharSequence mBestWord;
  270. private boolean mPredictionOn;
  271. private boolean mCompletionOn;
  272. private boolean mHasDictionary;
  273. private boolean mAutoSpace;
  274. private boolean mJustAddedAutoSpace;
  275. private boolean mAutoCorrectEnabled;
  276. private boolean mReCorrectionEnabled;
  277. private boolean mBigramSuggestionEnabled;
  278. private boolean mAutoCorrectOn;
  279. // TODO move this state variable outside LatinIME
  280. private boolean mCapsLock;
  281. private boolean mPasswordText;
  282. private boolean mAutoSwitch;
  283. private boolean mVibrateOn;
  284. private boolean mSoundOn;
  285. private boolean mPopupOn;
  286. private boolean mAutoCap;
  287. private boolean mQuickFixes;
  288. private boolean mHasUsedVoiceInput;
  289. private boolean mHasUsedVoiceInputUnsupportedLocale;
  290. private boolean mLocaleSupportedForVoiceInput;
  291. private boolean mShowSuggestions;
  292. private boolean mIsShowingHint;
  293. private int mCorrectionMode;
  294. private boolean mEnableVoice = true;
  295. private boolean mVoiceOnPrimary;
  296. private int mOrientation;
  297. private List<CharSequence> mSuggestPuncList;
  298. // Keep track of the last selection range to decide if we need to show word
  299. // alternatives
  300. private int mLastSelectionStart;
  301. private int mLastSelectionEnd;
  302. // Input type is such that we should not auto-correct
  303. private boolean mInputTypeNoAutoCorrect;
  304. // Indicates whether the suggestion strip is to be on in landscape
  305. private boolean mJustAccepted;
  306. private CharSequence mJustRevertedSeparator;
  307. private int mDeleteCount;
  308. private long mLastKeyTime;
  309. private int mPressedKey = LatinKeyboardView.KEYCODE_UNKNOWN;
  310. // Modifier keys state
  311. private ModifierKeyState mShiftKeyState = new ModifierKeyState();
  312. private ModifierKeyState mSymbolKeyState = new ModifierKeyState();
  313. private AudioManager mAudioManager;
  314. // Align sound effect volume on music volume
  315. private final float FX_VOLUME = -1.0f;
  316. private boolean mSilentMode;
  317. /* package */String mWordSeparators;
  318. private String mSentenceSeparators;
  319. private String mSuggestPuncs;
  320. private VoiceInput mVoiceInput;
  321. private VoiceResults mVoiceResults = new VoiceResults();
  322. private boolean mConfigurationChanging;
  323. // Keeps track of most recently inserted text (multi-character key) for
  324. // reverting
  325. private CharSequence mEnteredText;
  326. private boolean mRefreshKeyboardRequired;
  327. // For each word, a list of potential replacements, usually from voice.
  328. private Map<String, List<CharSequence>> mWordToSuggestions = new HashMap<String, List<CharSequence>>();
  329. private ArrayList<WordAlternatives> mWordHistory = new ArrayList<WordAlternatives>();
  330. private class VoiceResults {
  331. List<String> candidates;
  332. Map<String, List<CharSequence>> alternatives;
  333. }
  334. public abstract static class WordAlternatives {
  335. protected CharSequence mChosenWord;
  336. public WordAlternatives() {
  337. // Nothing
  338. }
  339. public WordAlternatives(CharSequence chosenWord) {
  340. mChosenWord = chosenWord;
  341. }
  342. @Override
  343. public int hashCode() {
  344. return mChosenWord.hashCode();
  345. }
  346. public abstract CharSequence getOriginalWord();
  347. public CharSequence getChosenWord() {
  348. return mChosenWord;
  349. }
  350. public abstract List<CharSequence> getAlternatives();
  351. }
  352. public class TypedWordAlternatives extends WordAlternatives {
  353. private WordComposer word;
  354. public TypedWordAlternatives() {
  355. // Nothing
  356. }
  357. public TypedWordAlternatives(CharSequence chosenWord, WordComposer wordComposer) {
  358. super(chosenWord);
  359. word = wordComposer;
  360. }
  361. @Override
  362. public CharSequence getOriginalWord() {
  363. return word.getTypedWord();
  364. }
  365. @Override
  366. public List<CharSequence> getAlternatives() {
  367. return getTypedSuggestions(word);
  368. }
  369. }
  370. /**
  371. * This class sends a key event when it runs. This is useful for sending delayed key events,
  372. * such as an UP event for a simulated long-press.
  373. *
  374. * @author alanv@google.com (Alan Viverette)
  375. */
  376. private class KeyEventRunnable implements Runnable {
  377. private final int mKeyCode;
  378. private final int mMetaState;
  379. private final long mDownTime;
  380. public KeyEventRunnable(int keyCode, int metaState, long downTime) {
  381. mKeyCode = keyCode;
  382. mMetaState = metaState;
  383. mDownTime = downTime;
  384. }
  385. @Override
  386. public void run() {
  387. final KeyEvent event = new KeyEvent(SystemClock.uptimeMillis(), mDownTime,
  388. KeyEvent.ACTION_UP, mKeyCode, 0, mMetaState, KeyCharacterMap.BUILT_IN_KEYBOARD,
  389. 0, KeyEvent.FLAG_SOFT_KEYBOARD);
  390. // First attempt to send the key to the IME, then to the input
  391. // connection.
  392. if (!onKeyUp(mKeyCode, event)) {
  393. final InputConnection ic = getCurrentInputConnection();
  394. if (ic != null) {
  395. ic.sendKeyEvent(event);
  396. }
  397. }
  398. }
  399. }
  400. /* package */Handler mHandler = new Handler() {
  401. @Override
  402. public void handleMessage(Message msg) {
  403. switch (msg.what) {
  404. case MSG_UPDATE_SUGGESTIONS:
  405. updateSuggestions();
  406. break;
  407. case MSG_UPDATE_OLD_SUGGESTIONS:
  408. setOldSuggestions();
  409. break;
  410. case MSG_UPDATE_SHIFT_STATE:
  411. updateShiftKeyState(getCurrentInputEditorInfo());
  412. break;
  413. case MSG_VOICE_RESULTS:
  414. handleVoiceResults();
  415. break;
  416. }
  417. }
  418. };
  419. /**
  420. * Returns <code>true<code> if this Android build supports voice input. * *
  421. * * * @return <code>true<code>if this Android build supports voice input
  422. */
  423. public static boolean supportsVoiceInput() {
  424. return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO);
  425. }
  426. @Override
  427. public void onCreate() {
  428. super.onCreate();
  429. LatinImeLogger.init(this);
  430. // setStatusIcon(R.drawable.ime_qwerty);
  431. mResources = getResources();
  432. final Configuration conf = mResources.getConfiguration();
  433. final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
  434. mLanguageSwitcher = new LanguageSwitcher(this);
  435. mLanguageSwitcher.loadLocales(prefs);
  436. mKeyboardSwitcher = new KeyboardSwitcher(this);
  437. mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
  438. mAccessibilityUtils = new AccessibilityUtils(this);
  439. mFeedbackUtil = new FeedbackUtil(this);
  440. mSystemLocale = conf.locale.toString();
  441. mLanguageSwitcher.setSystemLocale(conf.locale);
  442. String inputLanguage = mLanguageSwitcher.getInputLanguage();
  443. if (inputLanguage == null) {
  444. inputLanguage = conf.locale.toString();
  445. }
  446. mReCorrectionEnabled = prefs.getBoolean(PREF_RECORRECTION_ENABLED, getResources()
  447. .getBoolean(R.bool.default_recorrection_enabled));
  448. LatinIMEUtil.GCUtils.getInstance().reset();
  449. boolean tryGC = true;
  450. for (int i = 0; i < LatinIMEUtil.GCUtils.GC_TRY_LOOP_MAX && tryGC; ++i) {
  451. try {
  452. initSuggest(inputLanguage);
  453. tryGC = false;
  454. } catch (OutOfMemoryError e) {
  455. tryGC = LatinIMEUtil.GCUtils.getInstance().tryGCOrWait(inputLanguage, e);
  456. }
  457. }
  458. mOrientation = conf.orientation;
  459. initSuggestPuncList();
  460. // Register to receive phone status changes. We need to switch to hidden
  461. // mode when the user is receiving a phone call, then switch back when
  462. // they're done.
  463. mTelephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
  464. mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
  465. // Register to receive ringer mode changes for silent mode and keyboard
  466. // mode requests.
  467. IntentFilter filter = new IntentFilter();
  468. filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
  469. filter.addAction(REQUEST_KEYBOARD_MODE_CHANGE);
  470. filter.addAction(REQUEST_KEYBOARD_MODE_UPDATE);
  471. registerReceiver(mReceiver, filter);
  472. if (VOICE_INSTALLED) {
  473. mVoiceInput = new VoiceInput(this, this);
  474. mHints = new Hints(this, new Hints.Display() {
  475. @Override
  476. public void showHint(int viewResource) {
  477. LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  478. View view = inflater.inflate(viewResource, null);
  479. setCandidatesView(view);
  480. setCandidatesViewShown(true);
  481. mIsShowingHint = true;
  482. }
  483. });
  484. }
  485. prefs.registerOnSharedPreferenceChangeListener(this);
  486. }
  487. @Override
  488. public void onWindowShown() {
  489. super.onWindowShown();
  490. SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
  491. boolean hasRunTutorial = prefs.getBoolean(PREF_HAS_RUN_TUTORIAL, false);
  492. if (!hasRunTutorial) {
  493. Intent showDialog = new Intent(this, LatinTutorialDialog.class);
  494. showDialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  495. startActivity(showDialog);
  496. SharedPreferences.Editor editor = prefs.edit();
  497. editor.putBoolean(PREF_HAS_RUN_TUTORIAL, true);
  498. editor.commit();
  499. }
  500. }
  501. /**
  502. * Loads a dictionary or multiple separated dictionary
  503. *
  504. * @return returns array of dictionary resource ids
  505. */
  506. /* package */static int[] getDictionary(Resources res) {
  507. String packageName = res.getResourcePackageName(R.xml.dictionary);
  508. XmlResourceParser xrp = res.getXml(R.xml.dictionary);
  509. ArrayList<Integer> dictionaries = new ArrayList<Integer>();
  510. try {
  511. int current = xrp.getEventType();
  512. while (current != XmlResourceParser.END_DOCUMENT) {
  513. if (current == XmlResourceParser.START_TAG) {
  514. String tag = xrp.getName();
  515. if (tag != null) {
  516. if (tag.equals("part")) {
  517. String dictFileName = xrp.getAttributeValue(null, "name");
  518. dictionaries.add(res.getIdentifier(dictFileName, "raw", packageName));
  519. }
  520. }
  521. }
  522. xrp.next();
  523. current = xrp.getEventType();
  524. }
  525. } catch (XmlPullParserException e) {
  526. Log.e(TAG, "Dictionary XML parsing failure");
  527. } catch (IOException e) {
  528. Log.e(TAG, "Dictionary XML IOException");
  529. }
  530. int count = dictionaries.size();
  531. int[] dict = new int[count];
  532. for (int i = 0; i < count; i++) {
  533. dict[i] = dictionaries.get(i);
  534. }
  535. return dict;
  536. }
  537. private void initSuggest(String locale) {
  538. mInputLocale = locale;
  539. Resources orig = getResources();
  540. Configuration conf = orig.getConfiguration();
  541. Locale saveLocale = conf.locale;
  542. conf.locale = new Locale(locale);
  543. orig.updateConfiguration(conf, orig.getDisplayMetrics());
  544. if (mSuggest != null) {
  545. mSuggest.close();
  546. }
  547. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
  548. mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES, true);
  549. int[] dictionaries = getDictionary(orig);
  550. mSuggest = new Suggest(this, dictionaries);
  551. updateAutoTextEnabled(saveLocale);
  552. if (mUserDictionary != null)
  553. mUserDictionary.close();
  554. mUserDictionary = new UserDictionary(this, mInputLocale);
  555. if (mContactsDictionary == null) {
  556. mContactsDictionary = new ContactsDictionary(this, Suggest.DIC_CONTACTS);
  557. }
  558. if (mAutoDictionary != null) {
  559. mAutoDictionary.close();
  560. }
  561. mAutoDictionary = new AutoDictionary(this, this, mInputLocale, Suggest.DIC_AUTO);
  562. if (mUserBigramDictionary != null) {
  563. mUserBigramDictionary.close();
  564. }
  565. mUserBigramDictionary = new UserBigramDictionary(this, this, mInputLocale, Suggest.DIC_USER);
  566. mSuggest.setUserBigramDictionary(mUserBigramDictionary);
  567. mSuggest.setUserDictionary(mUserDictionary);
  568. mSuggest.setContactsDictionary(mContactsDictionary);
  569. mSuggest.setAutoDictionary(mAutoDictionary);
  570. updateCorrectionMode();
  571. mWordSeparators = mResources.getString(R.string.word_separators);
  572. mSentenceSeparators = mResources.getString(R.string.sentence_separators);
  573. conf.locale = saveLocale;
  574. orig.updateConfiguration(conf, orig.getDisplayMetrics());
  575. }
  576. @Override
  577. public void onDestroy() {
  578. if (mUserDictionary != null) {
  579. mUserDictionary.close();
  580. }
  581. if (mContactsDictionary != null) {
  582. mContactsDictionary.close();
  583. }
  584. // Unregister the phone state listener.
  585. mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
  586. // Unregister the broadcast receiver.
  587. try {
  588. unregisterReceiver(mReceiver);
  589. } catch (IllegalArgumentException e) {
  590. // Ignore the case where broadcast receiver is already unregistered.
  591. }
  592. if (VOICE_INSTALLED && mVoiceInput != null) {
  593. mVoiceInput.destroy();
  594. }
  595. LatinImeLogger.commit();
  596. LatinImeLogger.onDestroy();
  597. super.onDestroy();
  598. }
  599. @Override
  600. public void onConfigurationChanged(Configuration conf) {
  601. // If the system locale changes and is different from the saved
  602. // locale (mSystemLocale), then reload the input locale list from the
  603. // latin ime settings (shared prefs) and reset the input locale
  604. // to the first one.
  605. final String systemLocale = conf.locale.toString();
  606. if (!TextUtils.equals(systemLocale, mSystemLocale)) {
  607. mSystemLocale = systemLocale;
  608. if (mLanguageSwitcher != null) {
  609. mLanguageSwitcher.loadLocales(PreferenceManager.getDefaultSharedPreferences(this));
  610. mLanguageSwitcher.setSystemLocale(conf.locale);
  611. toggleLanguage(true, true);
  612. } else {
  613. reloadKeyboards();
  614. }
  615. }
  616. // If orientation changed while predicting, commit the change
  617. if (conf.orientation != mOrientation) {
  618. InputConnection ic = getCurrentInputConnection();
  619. commitTyped(ic);
  620. if (ic != null)
  621. ic.finishComposingText(); // For voice input
  622. mOrientation = conf.orientation;
  623. reloadKeyboards();
  624. }
  625. mConfigurationChanging = true;
  626. super.onConfigurationChanged(conf);
  627. if (mRecognizing) {
  628. switchToRecognitionStatusView();
  629. }
  630. mConfigurationChanging = false;
  631. }
  632. @Override
  633. public View onCreateInputView() {
  634. mKeyboardSwitcher.recreateInputView();
  635. mKeyboardSwitcher.makeKeyboards(true);
  636. mKeyboardSwitcher.setKeyboardMode(KeyboardSwitcher.MODE_TEXT, 0,
  637. shouldShowVoiceButton(makeFieldContext(), getCurrentInputEditorInfo()));
  638. return mKeyboardSwitcher.getInputView();
  639. }
  640. @Override
  641. public View onCreateCandidatesView() {
  642. mKeyboardSwitcher.makeKeyboards(true);
  643. mCandidateViewContainer = (LinearLayout) getLayoutInflater().inflate(R.layout.candidates,
  644. null);
  645. mCandidateView = (CandidateView) mCandidateViewContainer.findViewById(R.id.candidates);
  646. mCandidateView.setService(this);
  647. setCandidatesViewShown(true);
  648. return mCandidateViewContainer;
  649. }
  650. @Override
  651. public void onStartInputView(EditorInfo attribute, boolean restarting) {
  652. LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
  653. // In landscape mode, this method gets called without the input view
  654. // being created.
  655. if (inputView == null) {
  656. return;
  657. }
  658. if (mRefreshKeyboardRequired) {
  659. mRefreshKeyboardRequired = false;
  660. toggleLanguage(true, true);
  661. }
  662. mKeyboardSwitcher.makeKeyboards(false);
  663. TextEntryState.newSession(this);
  664. // Most such things we decide below in the switch statement, but we need
  665. // to know now whether this is a password text field, because we need to
  666. // know now (before the switch statement) whether we want to enable the
  667. // voice button.
  668. mPasswordText = false;
  669. int variation = attribute.inputType & EditorInfo.TYPE_MASK_VARIATION;
  670. if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
  671. || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
  672. mPasswordText = true;
  673. }
  674. mEnableVoiceButton = shouldShowVoiceButton(makeFieldContext(), attribute);
  675. final boolean enableVoiceButton = mEnableVoiceButton && mEnableVoice;
  676. mAfterVoiceInput = false;
  677. mImmediatelyAfterVoiceInput = false;
  678. mShowingVoiceSuggestions = false;
  679. mVoiceInputHighlighted = false;
  680. mInputTypeNoAutoCorrect = false;
  681. mPredictionOn = false;
  682. mCompletionOn = false;
  683. mCompletions = null;
  684. mCapsLock = false;
  685. mEnteredText = null;
  686. mCachedPredictionOn = false;
  687. mCachedMode = KeyboardSwitcher.MODE_TEXT;
  688. switch (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) {
  689. case EditorInfo.TYPE_CLASS_NUMBER:
  690. case EditorInfo.TYPE_CLASS_DATETIME:
  691. // fall through
  692. // NOTE: For now, we use the phone keyboard for NUMBER and
  693. // DATETIME until we get
  694. // a dedicated number entry keypad.
  695. // TODO: Use a dedicated number entry keypad here when we get
  696. // one.
  697. case EditorInfo.TYPE_CLASS_PHONE:
  698. mCachedMode = KeyboardSwitcher.MODE_PHONE;
  699. break;
  700. case EditorInfo.TYPE_CLASS_TEXT:
  701. mCachedMode = KeyboardSwitcher.MODE_TEXT;
  702. mCachedPredictionOn = true;
  703. // Make sure that passwords are not displayed in candidate view
  704. if (variation == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
  705. || variation == EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) {
  706. mCachedPredictionOn = false;
  707. }
  708. if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
  709. || variation == EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME) {
  710. mAutoSpace = false;
  711. } else {
  712. mAutoSpace = true;
  713. }
  714. if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) {
  715. mCachedPredictionOn = false;
  716. mCachedMode = KeyboardSwitcher.MODE_EMAIL;
  717. } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_URI) {
  718. mCachedPredictionOn = false;
  719. mCachedMode = KeyboardSwitcher.MODE_URL;
  720. } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE) {
  721. mCachedMode = KeyboardSwitcher.MODE_IM;
  722. } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_FILTER) {
  723. mCachedPredictionOn = false;
  724. } else if (variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) {
  725. mCachedMode = KeyboardSwitcher.MODE_WEB;
  726. // If it's a browser edit field and auto correct is not ON
  727. // explicitly, then
  728. // disable auto correction, but keep suggestions on.
  729. if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0) {
  730. mInputTypeNoAutoCorrect = true;
  731. }
  732. }
  733. // If NO_SUGGESTIONS is set, don't do prediction.
  734. if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS) != 0) {
  735. mCachedPredictionOn = false;
  736. mInputTypeNoAutoCorrect = true;
  737. }
  738. // If it's not multiline and the autoCorrect flag is not set,
  739. // then don't correct
  740. if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) == 0
  741. && (attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
  742. mInputTypeNoAutoCorrect = true;
  743. }
  744. if ((attribute.inputType & EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE) != 0) {
  745. mCachedPredictionOn = false;
  746. mCompletionOn = isFullscreenMode();
  747. }
  748. break;
  749. }
  750. int keyboardMode = mCachedMode;
  751. boolean predictionOn = mCachedPredictionOn;
  752. if (isAccessibilityEnabled()) {
  753. // If auto-switch is on AND we're not already auto-switched AND we're
  754. // focused on an editable field THEN cache the current mode and force
  755. // typing mode.
  756. if (mAutoSwitch && mCachedForcedMode == FORCE_NONE
  757. && (attribute.inputType & EditorInfo.TYPE_MASK_CLASS) != EditorInfo.TYPE_NULL) {
  758. mCachedForcedMode = mForcedMode;
  759. mForcedMode = FORCE_CACHED;
  760. mAccessibilityUtils.speakDescription(getString(R.string.spoken_mode_switch_keyboard));
  761. } else if (mForcedMode == FORCE_HIDDEN) {
  762. keyboardMode = KeyboardSwitcher.MODE_HIDDEN;
  763. predictionOn = false;
  764. } else if (mForcedMode == FORCE_DPAD) {
  765. keyboardMode = KeyboardSwitcher.MODE_DPAD;
  766. predictionOn = false;
  767. }
  768. }
  769. mPredictionOn = predictionOn;
  770. mKeyboardSwitcher.setKeyboardMode(keyboardMode, attribute.imeOptions, enableVoiceButton);
  771. inputView.closing();
  772. mComposing.setLength(0);
  773. mPredicting = false;
  774. mDeleteCount = 0;
  775. mJustAddedAutoSpace = false;
  776. loadSettings();
  777. updateShiftKeyState(attribute);
  778. setCandidatesViewShownInternal(isCandidateStripVisible() || mCompletionOn, false /* needsInputViewShown */);
  779. updateSuggestions();
  780. // If the dictionary is not big enough, don't auto correct
  781. mHasDictionary = mSuggest.hasMainDictionary();
  782. updateCorrectionMode();
  783. inputView.setPreviewEnabled(mPopupOn);
  784. inputView.setProximityCorrectionEnabled(true);
  785. inputView.setAccessibilityEnabled(isAccessibilityEnabled());
  786. mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions);
  787. // If we just entered a text field, maybe it has some old text that
  788. // requires correction
  789. checkReCorrectionOnStart();
  790. if (TRACE)
  791. Debug.startMethodTracing("/data/trace/latinime");
  792. }
  793. private void checkReCorrectionOnStart() {
  794. if (mReCorrectionEnabled && isPredictionOn()) {
  795. // First get the cursor position. This is required by
  796. // setOldSuggestions(), so that
  797. // it can pass the correct range to setComposingRegion(). At this
  798. // point, we don't
  799. // have valid values for mLastSelectionStart/Stop because
  800. // onUpdateSelection() has
  801. // not been called yet.
  802. InputConnection ic = getCurrentInputConnection();
  803. if (ic == null)
  804. return;
  805. ExtractedTextRequest etr = new ExtractedTextRequest();
  806. etr.token = 0; // anything is fine here
  807. ExtractedText et = ic.getExtractedText(etr, 0);
  808. if (et == null)
  809. return;
  810. mLastSelectionStart = et.startOffset + et.selectionStart;
  811. mLastSelectionEnd = et.startOffset + et.selectionEnd;
  812. // Then look for possible corrections in a delayed fashion
  813. if (!TextUtils.isEmpty(et.text) && isCursorTouchingWord()) {
  814. postUpdateOldSuggestions();
  815. }
  816. }
  817. }
  818. @Override
  819. public void onFinishInput() {
  820. super.onFinishInput();
  821. LatinImeLogger.commit();
  822. onAutoCompletionStateChanged(false);
  823. if (VOICE_INSTALLED && !mConfigurationChanging) {
  824. if (mAfterVoiceInput) {
  825. mVoiceInput.flushAllTextModificationCounters();
  826. mVoiceInput.logInputEnded();
  827. }
  828. mVoiceInput.flushLogs();
  829. mVoiceInput.cancel();
  830. }
  831. if (mKeyboardSwitcher.getInputView() != null) {
  832. mKeyboardSwitcher.getInputView().closing();
  833. }
  834. if (mAutoDictionary != null)
  835. mAutoDictionary.flushPendingWrites();
  836. if (mUserBigramDictionary != null)
  837. mUserBigramDictionary.flushPendingWrites();
  838. }
  839. @Override
  840. public void onFinishInputView(boolean finishingInput) {
  841. super.onFinishInputView(finishingInput);
  842. // Remove pending messages related to update suggestions
  843. mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
  844. mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
  845. // If auto-switch is on and we're in auto-switched mode, then restore
  846. // the previous forced mode.
  847. if (mAutoSwitch && mCachedForcedMode != FORCE_NONE) {
  848. requestKeyboardMode(mCachedForcedMode);
  849. }
  850. }
  851. @Override
  852. public void onUpdateExtractedText(int token, ExtractedText text) {
  853. super.onUpdateExtractedText(token, text);
  854. InputConnection ic = getCurrentInputConnection();
  855. if (!mImmediatelyAfterVoiceInput && mAfterVoiceInput && ic != null) {
  856. if (mHints.showPunctuationHintIfNecessary(ic)) {
  857. mVoiceInput.logPunctuationHintDisplayed();
  858. }
  859. }
  860. mImmediatelyAfterVoiceInput = false;
  861. }
  862. @Override
  863. public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
  864. int candidatesStart, int candidatesEnd) {
  865. super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart,
  866. candidatesEnd);
  867. if (DEBUG) {
  868. Log.i(TAG, "onUpdateSelection: oss=" + oldSelStart + ", ose=" + oldSelEnd + ", nss="
  869. + newSelStart + ", nse=" + newSelEnd + ", cs=" + candidatesStart + ", ce="
  870. + candidatesEnd);
  871. }
  872. if (mAfterVoiceInput) {
  873. mVoiceInput.setCursorPos(newSelEnd);
  874. mVoiceInput.setSelectionSpan(newSelEnd - newSelStart);
  875. }
  876. // If the current selection in the text view changes, we should
  877. // clear whatever candidate text we have.
  878. if ((((mComposing.length() > 0 && mPredicting) || mVoiceInputHighlighted)
  879. && (newSelStart != candidatesEnd || newSelEnd != candidatesEnd) && mLastSelectionStart != newSelStart)) {
  880. mComposing.setLength(0);
  881. mPredicting = false;
  882. postUpdateSuggestions();
  883. TextEntryState.reset();
  884. InputConnection ic = getCurrentInputConnection();
  885. if (ic != null) {
  886. ic.finishComposingText();
  887. }
  888. mVoiceInputHighlighted = false;
  889. } else if (!mPredicting && !mJustAccepted) {
  890. switch (TextEntryState.getState()) {
  891. case ACCEPTED_DEFAULT:
  892. TextEntryState.reset();
  893. //$FALL-THROUGH$
  894. case SPACE_AFTER_PICKED:
  895. mJustAddedAutoSpace = false; // The user moved the cursor.
  896. break;
  897. }
  898. }
  899. mJustAccepted = false;
  900. postUpdateShiftKeyState();
  901. // Make a note of the cursor position
  902. mLastSelectionStart = newSelStart;
  903. mLastSelectionEnd = newSelEnd;
  904. if (mReCorrectionEnabled) {
  905. // Don't look for corrections if the keyboard is not visible
  906. if (mKeyboardSwitcher != null && mKeyboardSwitcher.getInputView() != null
  907. && mKeyboardSwitcher.getInputView().isShown()) {
  908. // Check if we should go in or out of correction mode.
  909. if (isPredictionOn()
  910. && mJustRevertedSeparator == null
  911. && (candidatesStart == candidatesEnd || newSelStart != oldSelStart || TextEntryState
  912. .isCorrecting()) && (newSelStart < newSelEnd - 1 || (!mPredicting))
  913. && !mVoiceInputHighlighted) {
  914. if (isCursorTouchingWord() || mLastSelectionStart < mLastSelectionEnd) {
  915. postUpdateOldSuggestions();
  916. } else {
  917. abortCorrection(false);
  918. // Show the punctuation suggestions list if the current
  919. // one is not
  920. // and if not showing "Touch again to save".
  921. if (mCandidateView != null
  922. && !mSuggestPuncList.equals(mCandidateView.getSuggestions())
  923. && !mCandidateView.isShowingAddToDictionaryHint()) {
  924. setNextSuggestions();
  925. }
  926. }
  927. }
  928. }
  929. }
  930. }
  931. /**
  932. * This is called when the user has clicked on the extracted text view, when running in
  933. * fullscreen mode. The default implementation hides the candidates view when this happens, but
  934. * only if the extracted text editor has a vertical scroll bar because its text doesn't fit.
  935. * Here we override the behavior due to the possibility that a re-correction could cause the
  936. * candidate strip to disappear and re-appear.
  937. */
  938. @Override
  939. public void onExtractedTextClicked() {
  940. if (mReCorrectionEnabled && isPredictionOn())
  941. return;
  942. super.onExtractedTextClicked();
  943. }
  944. /**
  945. * This is called when the user has performed a cursor movement in the extracted text view, when
  946. * it is running in fullscreen mode. The default implementation hides the candidates view when a
  947. * vertical movement happens, but only if the extracted text editor has a vertical scroll bar
  948. * because its text doesn't fit. Here we override the behavior due to the possibility that a
  949. * re-correction could cause the candidate strip to disappear and re-appear.
  950. */
  951. @Override
  952. public void onExtractedCursorMovement(int dx, int dy) {
  953. if (mReCorrectionEnabled && isPredictionOn())
  954. return;
  955. super.onExtractedCursorMovement(dx, dy);
  956. }
  957. @Override
  958. public void hideWindow() {
  959. LatinImeLogger.commit();
  960. onAutoCompletionStateChanged(false);
  961. if (TRACE)
  962. Debug.stopMethodTracing();
  963. if (mOptionsDialog != null && mOptionsDialog.isShowing()) {
  964. mOptionsDialog.dismiss();
  965. mOptionsDialog = null;
  966. }
  967. if (!mConfigurationChanging) {
  968. if (mAfterVoiceInput)
  969. mVoiceInput.logInputEnded();
  970. if (mVoiceWarningDialog != null && mVoiceWarningDialog.isShowing()) {
  971. mVoiceInput.logKeyboardWarningDialogDismissed();
  972. mVoiceWarningDialog.dismiss();
  973. mVoiceWarningDialog = null;
  974. }
  975. if (VOICE_INSTALLED & mRecognizing) {
  976. mVoiceInput.cancel();
  977. }
  978. }
  979. mWordToSuggestions.clear();
  980. mWordHistory.clear();
  981. super.hideWindow();
  982. TextEntryState.endSession();
  983. }
  984. @Override
  985. public void onDisplayCompletions(CompletionInfo[] completions) {
  986. if (DEBUG) {
  987. Log.i("foo", "Received completions:");
  988. for (int i = 0; i < (completions != null ? completions.length : 0); i++) {
  989. Log.i("foo", " #" + i + ": " + completions[i]);
  990. }
  991. }
  992. if (mCompletionOn) {
  993. mCompletions = completions;
  994. if (completions == null) {
  995. clearSuggestions();
  996. return;
  997. }
  998. List<CharSequence> stringList = new ArrayList<CharSequence>();
  999. for (int i = 0; i < (completions != null ? completions.length : 0); i++) {
  1000. CompletionInfo ci = completions[i];
  1001. if (ci != null)
  1002. stringList.add(ci.getText());
  1003. }
  1004. // When in fullscreen mode, show completions generated by the
  1005. // application
  1006. setSuggestions(stringList, true, true, true);
  1007. mBestWord = null;
  1008. setCandidatesViewShown(true);
  1009. }
  1010. }
  1011. private void setCandidatesViewShownInternal(boolean shown, boolean needsInputViewShown) {
  1012. // TODO: Remove this if we support candidates with hard keyboard
  1013. if (onEvaluateInputViewShown()) {
  1014. super.setCandidatesViewShown(shown && mKeyboardSwitcher.getInputView() != null
  1015. && (needsInputViewShown ? mKeyboardSwitcher.getInputView().isShown() : true));
  1016. }
  1017. }
  1018. @Override
  1019. public void setCandidatesViewShown(boolean shown) {
  1020. setCandidatesViewShownInternal(shown, true /* needsInputViewShown */);
  1021. }
  1022. @Override
  1023. public void onComputeInsets(InputMethodService.Insets outInsets) {
  1024. super.onComputeInsets(outInsets);
  1025. if (!isFullscreenMode()) {
  1026. outInsets.contentTopInsets = outInsets.visibleTopInsets;
  1027. }
  1028. }
  1029. @Override
  1030. public boolean onEvaluateFullscreenMode() {
  1031. DisplayMetrics dm = getResources().getDisplayMetrics();
  1032. float displayHeight = dm.heightPixels;
  1033. // If the display is more than X inches high, don't go to fullscreen
  1034. // mode
  1035. float dimen = getResources().getDimension(R.dimen.max_height_for_fullscreen);
  1036. if (displayHeight > dimen) {
  1037. return false;
  1038. } else {
  1039. return super.onEvaluateFullscreenMode();
  1040. }
  1041. }
  1042. @Override
  1043. public boolean onKeyDown(int keyCode, KeyEvent event) {
  1044. switch (keyCode) {
  1045. case KeyEvent.KEYCODE_BACK:
  1046. if (event.getRepeatCount() == 0 && mKeyboardSwitcher.getInputView() != null) {
  1047. if (mKeyboardSwitcher.getInputView().handleBack()) {
  1048. return true;
  1049. }
  1050. }
  1051. break;
  1052. }
  1053. return super.onKeyDown(keyCode, event);
  1054. }
  1055. @Override
  1056. public boolean onKeyUp(int keyCode, KeyEvent event) {
  1057. switch (keyCode) {
  1058. case KeyEvent.KEYCODE_DPAD_DOWN:
  1059. case KeyEvent.KEYCODE_DPAD_UP:
  1060. case KeyEvent.KEYCODE_DPAD_LEFT:
  1061. case KeyEvent.KEYCODE_DPAD_RIGHT:
  1062. LatinKeyboardView view = mKeyboardSwitcher.getInputView();
  1063. if (view != null && view.isShifted()) {
  1064. // Force this key to be shifted.
  1065. event = new KeyEvent(event.getDownTime(), event.getEventTime(),
  1066. event.getAction(), event.getKeyCode(), event.getRepeatCount(),
  1067. event.getMetaState() | KeyEvent.META_SHIFT_ON
  1068. | KeyEvent.META_SHIFT_LEFT_ON, event.getDeviceId(),
  1069. event.getScanCode(), event.getFlags());
  1070. }
  1071. break;
  1072. }
  1073. return super.onKeyUp(keyCode, event);
  1074. }
  1075. @Override
  1076. public boolean onKeyLongPress(int keyCode, KeyEvent event) {
  1077. switch (keyCode) {
  1078. case KeyEvent.KEYCODE_VOLUME_UP:
  1079. if (event.getRepeatCount() == 1) {
  1080. cycleForcedType(1);
  1081. }
  1082. return true;
  1083. case KeyEvent.KEYCODE_VOLUME_DOWN:
  1084. if (event.getRepeatCount() == 1) {
  1085. cycleForcedType(-1);
  1086. }
  1087. return true;
  1088. }
  1089. return super.onKeyLongPress(keyCode, event);
  1090. }
  1091. private void cycleForcedType(int direction) {
  1092. int mode = (mForcedMode + direction);
  1093. if (mode < 0) {
  1094. mode += NUM_FORCE_MODES;
  1095. } else if (mode >= NUM_FORCE_MODES) {
  1096. mode -= NUM_FORCE_MODES;
  1097. }
  1098. requestKeyboardMode(mode);
  1099. }
  1100. private boolean requestKeyboardMode(int mode) {
  1101. if (mode < 0 || mode >= NUM_FORCE_MODES || mForcedMode == mode || mInCallScreen) {
  1102. return false;
  1103. }
  1104. int keyboardMode = mCachedMode;
  1105. boolean predictionOn = mCachedPredictionOn;
  1106. switch (mode) {
  1107. case FORCE_HIDDEN:
  1108. keyboardMode = KeyboardSwitcher.MODE_HIDDEN;
  1109. predictionOn = false;
  1110. mAccessibilityUtils.speakDescription(getString(R.string.spoken_mode_switch_hidden));
  1111. break;
  1112. case FORCE_DPAD:
  1113. keyboardMode = KeyboardSwitcher.MODE_DPAD;
  1114. predictionOn = false;
  1115. mAccessibilityUtils.speakDescription(getString(R.string.spoken_mode_switch_dpad));
  1116. break;
  1117. case FORCE_CACHED:
  1118. keyboardMode = mCachedMode;
  1119. predictionOn = mCachedPredictionOn;
  1120. mAccessibilityUtils.speakDescription(getString(R.string.spoken_mode_switch_keyboard));
  1121. break;
  1122. }
  1123. mPredictionOn = predictionOn;
  1124. mKeyboardSwitcher.setKeyboardMode(keyboardMode);
  1125. mForcedMode = mode;
  1126. mCachedForcedMode = FORCE_NONE;
  1127. mFeedbackUtil.vibrate(VIBRATE_PATTERN_MODE_CHANGE);
  1128. // Broadcast mode switch so that we can receive it in the tutorial.
  1129. sendBroadcast(new Intent(BROADCAST_KEYBOARD_MODE_CHANGE).putExtra(EXTRA_MODE, mode),
  1130. PERMISSION_REQUEST);
  1131. setCandidatesViewShownInternal(isCandidateStripVisible() || mCompletionOn, false /* needsInputViewShown */);
  1132. updateSuggestions();
  1133. updateCorrectionMode();
  1134. mPredictionOn = mPredictionOn && (mCorrectionMode > 0 || mShowSuggestions);
  1135. checkReCorrectionOnStart();
  1136. return true;
  1137. }
  1138. public int getKeyboardMode() {
  1139. return mForcedMode;
  1140. }
  1141. private void revertVoiceInput() {
  1142. InputConnection ic = getCurrentInputConnection();
  1143. if (ic != null)
  1144. ic.commitText("", 1);
  1145. updateSuggestions();
  1146. mVoiceInputHighlighted = false;
  1147. }
  1148. private void commitVoiceInput() {
  1149. InputConnection ic = getCurrentInputConnection();
  1150. if (ic != null)
  1151. ic.finishComposingText();
  1152. updateSuggestions();
  1153. mVoiceInputHighlighted = false;
  1154. }
  1155. private void reloadKeyboards() {
  1156. mKeyboardSwitcher.setLanguageSwitcher(mLanguageSwitcher);
  1157. if (mKeyboardSwitcher.getInputView() != null
  1158. && mKeyboardSwitcher.getKeyboardMode() != KeyboardSwitcher.MODE_NONE) {
  1159. mKeyboardSwitcher.setVoiceMode(mEnableVoice && mEnableVoiceButton, mVoiceOnPrimary);
  1160. }
  1161. mKeyboardSwitcher.makeKeyboards(true);
  1162. }
  1163. private void commitTyped(InputConnection inputConnection) {
  1164. if (mPredicting) {
  1165. mPredicting = false;
  1166. if (mComposing.length() > 0) {
  1167. if (inputConnection != null) {
  1168. inputConnection.commitText(mComposing, 1);
  1169. }
  1170. mCommittedLength = mComposing.length();
  1171. TextEntryState.acceptedTyped(mComposing);
  1172. addToDictionaries(mComposing, AutoDictionary.FREQUENCY_FOR_TYPED);
  1173. }
  1174. updateSuggestions();
  1175. }
  1176. }
  1177. private void postUpdateShiftKeyState() {
  1178. mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
  1179. // TODO: Should remove this 300ms delay?
  1180. mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SHIFT_STATE), 300);
  1181. }
  1182. public void updateShiftKeyState(EditorInfo attr) {
  1183. InputConnection ic = getCurrentInputConnection();
  1184. if (ic != null && attr != null && mKeyboardSwitcher.isAlphabetMode()) {
  1185. mKeyboardSwitcher.setShifted(mShiftKeyState.isMomentary() || mCapsLock
  1186. || getCursorCapsMode(ic, attr) != 0);
  1187. }
  1188. }
  1189. private int getCursorCapsMode(InputConnection ic, EditorInfo attr) {
  1190. int caps = 0;
  1191. EditorInfo ei = getCurrentInputEditorInfo();
  1192. if (mAutoCap && ei != null && ei.inputType != EditorInfo.TYPE_NULL) {
  1193. caps = ic.getCursorCapsMode(attr.inputType);
  1194. }
  1195. return caps;
  1196. }
  1197. private void swapPunctuationAndSpace() {
  1198. final InputConnection ic = getCurrentInputConnection();
  1199. if (ic == null)
  1200. return;
  1201. CharSequence lastTwo = ic.getTextBeforeCursor(2, 0);
  1202. if (lastTwo != null && lastTwo.length() == 2 && lastTwo.charAt(0) == KEYCODE_SPACE
  1203. && isSentenceSeparator(lastTwo.charAt(1))) {
  1204. ic.beginBatchEdit();
  1205. ic.deleteSurroundingText(2, 0);
  1206. ic.commitText(lastTwo.charAt(1) + " ", 1);
  1207. ic.endBatchEdit();
  1208. updateShiftKeyState(getCurrentInputEditorInfo());
  1209. mJustAddedAutoSpace = true;
  1210. }
  1211. }
  1212. private void reswapPeriodAndSpace() {
  1213. final InputConnection ic = getCurrentInputConnection();
  1214. if (ic == null)
  1215. return;
  1216. CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
  1217. if (lastThree != null && lastThree.length() == 3 && lastThree.charAt(0) == KEYCODE_PERIOD
  1218. && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_PERIOD) {
  1219. ic.beginBatchEdit();
  1220. ic.deleteSurroundingText(3, 0);
  1221. ic.commitText(" ..", 1);
  1222. ic.endBatchEdit();
  1223. updateShiftKeyState(getCurrentInputEditorInfo());
  1224. }
  1225. }
  1226. private void doubleSpace() {
  1227. // if (!mAutoPunctuate) return;
  1228. if (mCorrectionMode == Suggest.CORRECTION_NONE)
  1229. return;
  1230. final InputConnection ic = getCurrentInputConnection();
  1231. if (ic == null)
  1232. return;
  1233. CharSequence lastThree = ic.getTextBeforeCursor(3, 0);
  1234. if (lastThree != null && lastThree.length() == 3
  1235. && Character.isLetterOrDigit(lastThree.charAt(0))
  1236. && lastThree.charAt(1) == KEYCODE_SPACE && lastThree.charAt(2) == KEYCODE_SPACE) {
  1237. ic.beginBatchEdit();
  1238. ic.deleteSurroundingText(2, 0);
  1239. ic.commitText(". ", 1);
  1240. ic.endBatchEdit();
  1241. updateShiftKeyState(getCurrentInputEditorInfo());
  1242. mJustAddedAutoSpace = true;
  1243. }
  1244. }
  1245. private void maybeRemovePreviousPeriod(CharSequence text) {
  1246. final InputConnection ic = getCurrentInputConnection();
  1247. if (ic == null)
  1248. return;
  1249. // When the text's first character is '.', remove the previous period
  1250. // if there is one.
  1251. CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
  1252. if (lastOne != null && lastOne.length() == 1 && lastOne.charAt(0) == KEYCODE_PERIOD
  1253. && text.charAt(0) == KEYCODE_PERIOD) {
  1254. ic.deleteSurroundingText(1, 0);
  1255. }
  1256. }
  1257. private void removeTrailingSpace() {
  1258. final InputConnection ic = getCurrentInputConnection();
  1259. if (ic == null)
  1260. return;
  1261. CharSequence lastOne = ic.getTextBeforeCursor(1, 0);
  1262. if (lastOne != null && lastOne.length() == 1 && lastOne.charAt(0) == KEYCODE_SPACE) {
  1263. ic.deleteSurroundingText(1, 0);
  1264. }
  1265. }
  1266. public boolean addWordToDictionary(String word) {
  1267. mUserDictionary.addWord(word, 128);
  1268. // Suggestion strip should be updated after the operation of adding word
  1269. // to the
  1270. // user dictionary
  1271. postUpdateSuggestions();
  1272. return true;
  1273. }
  1274. private boolean isAlphabet(int code) {
  1275. if (Character.isLetter(code)) {
  1276. return true;
  1277. } else {
  1278. return false;
  1279. }
  1280. }
  1281. private void showInputMethodPicker() {
  1282. ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).showInputMethodPicker();
  1283. }
  1284. private void onOptionKeyPressed() {
  1285. if (!isShowingOptionDialog()) {
  1286. if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) {
  1287. showOptionsMenu();
  1288. } else {
  1289. launchSettings();
  1290. }
  1291. }
  1292. }
  1293. private void onOptionKeyLongPressed() {
  1294. if (!isShowingOptionDialog()) {
  1295. if (LatinIMEUtil.hasMultipleEnabledIMEs(this)) {
  1296. showInputMethodPicker();
  1297. } else {
  1298. launchSettings();
  1299. }
  1300. }
  1301. }
  1302. private boolean isShowingOptionDialog() {
  1303. return mOptionsDialog != null && mOptionsDialog.isShowing();
  1304. }
  1305. private void handleBackspace() {
  1306. if (VOICE_INSTALLED && mVoiceInputHighlighted) {
  1307. mVoiceInput.incrementTextModificationDeleteCount(mVoiceResults.candidates.get(0)
  1308. .toString().length());
  1309. revertVoiceInput();
  1310. return;
  1311. }
  1312. boolean deleteChar = false;
  1313. InputConnection ic = getCurrentInputConnection();
  1314. if (ic == null)
  1315. return;
  1316. ic.beginBatchEdit();
  1317. if (mAfterVoiceInput) {
  1318. // Don't log delete if the user is pressing delete at
  1319. // the beginning of the text box (hence not deleting anything)
  1320. if (mVoiceInput.getCursorPos() > 0) {
  1321. // If anything was selected before the delete was pressed,
  1322. // increment the
  1323. // delete count by the length of the selection
  1324. int deleteLen = mVoiceInput.getSelectionSpan() > 0 ? mVoiceInput.getSelectionSpan()
  1325. : 1;
  1326. mVoiceInput.incrementTextModificationDeleteCount(deleteLen);
  1327. }
  1328. }
  1329. if (mPredicting) {
  1330. final int length = mComposing.length();
  1331. if (length > 0) {
  1332. mComposing.delete(length - 1, length);
  1333. mWord.deleteLast();
  1334. ic.setComposingText(mComposing, 1);
  1335. if (mComposing.length() == 0) {
  1336. mPredicting = false;
  1337. }
  1338. postUpdateSuggestions();
  1339. } else {
  1340. ic.deleteSurroundingText(1, 0);
  1341. }
  1342. } else {
  1343. deleteChar = true;
  1344. }
  1345. postUpdateShiftKeyState();
  1346. TextEntryState.backspace();
  1347. if (TextEntryState.getState() == TextEntryState.State.UNDO_COMMIT) {
  1348. revertLastWord(deleteChar);
  1349. ic.endBatchEdit();
  1350. return;
  1351. } else if (mEnteredText != null && sameAsTextBeforeCursor(ic, mEnteredText)) {
  1352. ic.deleteSurroundingText(mEnteredText.length(), 0);
  1353. } else if (deleteChar) {
  1354. if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
  1355. // Go back to the suggestion mode if the user canceled the
  1356. // "Touch again to save".
  1357. // NOTE: In gerenal, we don't revert the word when backspacing
  1358. // from a manual suggestion pick. We deliberately chose a
  1359. // different behavior only in the case of picking the first
  1360. // suggestion (typed word). It's intentional to have made this
  1361. // inconsistent with backspacing after selecting other
  1362. // suggestions.
  1363. revertLastWord(deleteChar);
  1364. } else {
  1365. sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
  1366. if (mDeleteCount > DELETE_ACCELERATE_AT) {
  1367. sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
  1368. }
  1369. }
  1370. }
  1371. mJustRevertedSeparator = null;
  1372. ic.endBatchEdit();
  1373. }
  1374. private void resetShift() {
  1375. handleShiftInternal(true);
  1376. }
  1377. private void handleShift() {
  1378. handleShiftInternal(false);
  1379. }
  1380. private void handleShiftInternal(boolean forceNormal) {
  1381. mHandler.removeMessages(MSG_UPDATE_SHIFT_STATE);
  1382. KeyboardSwitcher switcher = mKeyboardSwitcher;
  1383. LatinKeyboardView inputView = switcher.getInputView();
  1384. if (switcher.isAlphabetMode()) {
  1385. if (mCapsLock || forceNormal) {
  1386. mCapsLock = false;
  1387. switcher.setShifted(false);
  1388. } else if (inputView != null) {
  1389. if (inputView.isShifted()) {
  1390. mCapsLock = true;
  1391. switcher.setShiftLocked(true);
  1392. } else {
  1393. switcher.setShifted(true);
  1394. }
  1395. }
  1396. } else {
  1397. switcher.toggleShift();
  1398. }
  1399. }
  1400. private void abortCorrection(boolean force) {
  1401. if (force || TextEntryState.isCorrecting()) {
  1402. getCurrentInputConnection().finishComposingText();
  1403. clearSuggestions();
  1404. }
  1405. }
  1406. private void handleCharacter(int primaryCode, int[] keyCodes) {
  1407. if (VOICE_INSTALLED && mVoiceInputHighlighted) {
  1408. commitVoiceInput();
  1409. }
  1410. if (mAfterVoiceInput) {
  1411. // Assume input length is 1. This assumption fails for smiley face
  1412. // insertions.
  1413. mVoiceInput.incrementTextModificationInsertCount(1);
  1414. }
  1415. if (mLastSelectionStart == mLastSelectionEnd && TextEntryState.isCorrecting()) {
  1416. abortCorrection(false);
  1417. }
  1418. if (isAlphabet(primaryCode) && isPredictionOn() && !isCursorTouchingWord()) {
  1419. if (!mPredicting) {
  1420. mPredicting = true;
  1421. mComposing.setLength(0);
  1422. saveWordInHistory(mBestWord);
  1423. mWord.reset();
  1424. }
  1425. }
  1426. if (mKeyboardSwitcher.getInputView().isShifted()) {
  1427. if (keyCodes == null || keyCodes[0] < Character.MIN_CODE_POINT
  1428. || keyCodes[0] > Character.MAX_CODE_POINT) {
  1429. return;
  1430. }
  1431. primaryCode = keyCodes[0];
  1432. if (mKeyboardSwitcher.isAlphabetMode() && Character.isLowerCase(primaryCode)) {
  1433. int upperCaseCode = Character.toUpperCase(primaryCode);
  1434. if (upperCaseCode != primaryCode) {
  1435. primaryCode = upperCaseCode;
  1436. } else {
  1437. // Some keys, such as [eszett], have upper case as
  1438. // multi-characters.
  1439. String upperCase = new String(new int[] {
  1440. primaryCode
  1441. }, 0, 1).toUpperCase();
  1442. mKeyboardActionListener.onText(upperCase);
  1443. return;
  1444. }
  1445. }
  1446. }
  1447. if (mPredicting) {
  1448. if (mKeyboardSwitcher.getInputView().isShifted() && mKeyboardSwitcher.isAlphabetMode()
  1449. && mComposing.length() == 0) {
  1450. mWord.setFirstCharCapitalized(true);
  1451. }
  1452. mComposing.append((char) primaryCode);
  1453. mWord.add(primaryCode, keyCodes);
  1454. InputConnection ic = getCurrentInputConnection();
  1455. if (ic != null) {
  1456. // If it's the first letter, make note of auto-caps state
  1457. if (mWord.size() == 1) {
  1458. mWord.setAutoCapitalized(getCursorCapsMode(ic, getCurrentInputEditorInfo()) != 0);
  1459. }
  1460. ic.setComposingText(mComposing, 1);
  1461. }
  1462. postUpdateSuggestions();
  1463. } else {
  1464. sendKeyChar((char) primaryCode);
  1465. }
  1466. updateShiftKeyState(getCurrentInputEditorInfo());
  1467. if (LatinIME.PERF_DEBUG)
  1468. measureCps();
  1469. TextEntryState.typedCharacter((char) primaryCode, isWordSeparator(primaryCode));
  1470. }
  1471. private void handleSeparator(int primaryCode) {
  1472. if (VOICE_INSTALLED && mVoiceInputHighlighted) {
  1473. commitVoiceInput();
  1474. }
  1475. if (mAfterVoiceInput) {
  1476. // Assume input length is 1. This assumption fails for smiley face
  1477. // insertions.
  1478. mVoiceInput.incrementTextModificationInsertPunctuationCount(1);
  1479. }
  1480. // Should dismiss the "Touch again to save" message when handling
  1481. // separator
  1482. if (mCandidateView != null && mCandidateView.dismissAddToDictionaryHint()) {
  1483. postUpdateSuggestions();
  1484. }
  1485. boolean pickedDefault = false;
  1486. // Handle separator
  1487. InputConnection ic = getCurrentInputConnection();
  1488. if (ic != null) {
  1489. ic.beginBatchEdit();
  1490. abortCorrection(false);
  1491. }
  1492. if (mPredicting) {
  1493. // In certain languages where single quote is a separator, it's
  1494. // better not to auto correct, but accept the typed word. For
  1495. // instance, in Italian dov' should not be expanded to dove' because
  1496. // the elision requires the last vowel to be removed.
  1497. if (mAutoCorrectOn
  1498. && primaryCode != '\''
  1499. && (mJustRevertedSeparator == null
  1500. || mJustRevertedSeparator.length() == 0
  1501. || mJustRevertedSeparator.charAt(0) != primaryCode)) {
  1502. pickedDefault = pickDefaultSuggestion();
  1503. // Picked the suggestion by the space key. We consider this
  1504. // as "added an auto space".
  1505. if (primaryCode == KEYCODE_SPACE) {
  1506. mJustAddedAutoSpace = true;
  1507. }
  1508. } else {
  1509. commitTyped(ic);
  1510. }
  1511. }
  1512. if (mJustAddedAutoSpace && primaryCode == KEYCODE_ENTER) {
  1513. removeTrailingSpace();
  1514. mJustAddedAutoSpace = false;
  1515. }
  1516. sendKeyChar((char) primaryCode);
  1517. // Handle the case of ". ." -> " .." with auto-space if necessary
  1518. // before changing the TextEntryState.
  1519. if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
  1520. && primaryCode == KEYCODE_PERIOD) {
  1521. reswapPeriodAndSpace();
  1522. }
  1523. TextEntryState.typedCharacter((char) primaryCode, true);
  1524. if (TextEntryState.getState() == TextEntryState.State.PUNCTUATION_AFTER_ACCEPTED
  1525. && primaryCode != KEYCODE_ENTER) {
  1526. swapPunctuationAndSpace();
  1527. } else if (isPredictionOn() && primaryCode == KEYCODE_SPACE) {
  1528. doubleSpace();
  1529. }
  1530. if (pickedDefault) {
  1531. TextEntryState.backToAcceptedDefault(mWord.getTypedWord());
  1532. }
  1533. updateShiftKeyState(getCurrentInputEditorInfo());
  1534. if (ic != null) {
  1535. ic.endBatchEdit();
  1536. }
  1537. }
  1538. private void handleClose() {
  1539. commitTyped(getCurrentInputConnection());
  1540. if (VOICE_INSTALLED & mRecognizing) {
  1541. mVoiceInput.cancel();
  1542. }
  1543. requestHideSelf(0);
  1544. if (mKeyboardSwitcher != null) {
  1545. LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
  1546. if (inputView != null) {
  1547. inputView.closing();
  1548. }
  1549. }
  1550. TextEntryState.endSession();
  1551. }
  1552. private void saveWordInHistory(CharSequence result) {
  1553. if (mWord.size() <= 1) {
  1554. mWord.reset();
  1555. return;
  1556. }
  1557. // Skip if result is null. It happens in some edge case.
  1558. if (TextUtils.isEmpty(result)) {
  1559. return;
  1560. }
  1561. // Make a copy of the CharSequence, since it is/could be a mutable
  1562. // CharSequence
  1563. final String resultCopy = result.toString();
  1564. TypedWordAlternatives entry = new TypedWordAlternatives(resultCopy, new WordComposer(mWord));
  1565. mWordHistory.add(entry);
  1566. }
  1567. private void postUpdateSuggestions() {
  1568. mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
  1569. mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_SUGGESTIONS), 100);
  1570. }
  1571. private void postUpdateOldSuggestions() {
  1572. mHandler.removeMessages(MSG_UPDATE_OLD_SUGGESTIONS);
  1573. mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UPDATE_OLD_SUGGESTIONS), 300);
  1574. }
  1575. private boolean isPredictionOn() {
  1576. return mPredictionOn;
  1577. }
  1578. private boolean isCandidateStripVisible() {
  1579. return isPredictionOn() && mShowSuggestions;
  1580. }
  1581. @Override
  1582. public void onCancelVoice() {
  1583. if (mRecognizing) {
  1584. switchToKeyboardView();
  1585. }
  1586. }
  1587. private void switchToKeyboardView() {
  1588. mHandler.post(new Runnable() {
  1589. @Override
  1590. public void run() {
  1591. mRecognizing = false;
  1592. if (mKeyboardSwitcher.getInputView() != null) {
  1593. setInputView(mKeyboardSwitcher.getInputView());
  1594. }
  1595. setCandidatesViewShown(true);
  1596. updateInputViewShown();
  1597. postUpdateSuggestions();
  1598. }
  1599. });
  1600. }
  1601. private void switchToRecognitionStatusView() {
  1602. final boolean configChanged = mConfigurationChanging;
  1603. mHandler.post(new Runnable() {
  1604. @Override
  1605. public void run() {
  1606. setCandidatesViewShown(false);
  1607. mRecognizing = true;
  1608. View v = mVoiceInput.getView();
  1609. ViewParent p = v.getParent();
  1610. if (p != null && p instanceof ViewGroup) {
  1611. ((ViewGroup) v.getParent()).removeView(v);
  1612. }
  1613. setInputView(v);
  1614. updateInputViewShown();
  1615. if (configChanged) {
  1616. mVoiceInput.onConfigurationChanged();
  1617. }
  1618. }
  1619. });
  1620. }
  1621. private void startListening(boolean swipe) {
  1622. if (!mHasUsedVoiceInput
  1623. || (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale)) {
  1624. // Calls reallyStartListening if user clicks OK, does nothing if
  1625. // user clicks Cancel.
  1626. showVoiceWarningDialog(swipe);
  1627. } else {
  1628. reallyStartListening(swipe);
  1629. }
  1630. }
  1631. private void reallyStartListening(boolean swipe) {
  1632. if (!mHasUsedVoiceInput) {
  1633. // The user has started a voice input, so remember that in the
  1634. // future (so we don't show the warning dialog after the first run).
  1635. SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this)
  1636. .edit();
  1637. editor.putBoolean(PREF_HAS_USED_VOICE_INPUT, true);
  1638. SharedPreferencesCompat.apply(editor);
  1639. mHasUsedVoiceInput = true;
  1640. }
  1641. if (!mLocaleSupportedForVoiceInput && !mHasUsedVoiceInputUnsupportedLocale) {
  1642. // The user has started a voice input from an unsupported locale, so
  1643. // remember that
  1644. // in the future (so we don't show the warning dialog the next time
  1645. // they do this).
  1646. SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(this)
  1647. .edit();
  1648. editor.putBoolean(PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, true);
  1649. SharedPreferencesCompat.apply(editor);
  1650. mHasUsedVoiceInputUnsupportedLocale = true;
  1651. }
  1652. // Clear N-best suggestions
  1653. clearSuggestions();
  1654. FieldContext context = new FieldContext(getCurrentInputConnection(),
  1655. getCurrentInputEditorInfo(), mLanguageSwitcher.getInputLanguage(),
  1656. mLanguageSwitcher.getEnabledLanguages());
  1657. mVoiceInput.startListening(context, swipe);
  1658. switchToRecognitionStatusView();
  1659. }
  1660. private void showVoiceWarningDialog(final boolean swipe) {
  1661. AlertDialog.Builder builder = new AlertDialog.Builder(this);
  1662. builder.setCancelable(true);
  1663. builder.setIcon(R.drawable.ic_mic_dialog);
  1664. builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
  1665. @Override
  1666. public void onClick(DialogInterface dialog, int whichButton) {
  1667. mVoiceInput.logKeyboardWarningDialogOk();
  1668. reallyStartListening(swipe);
  1669. }
  1670. });
  1671. builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
  1672. @Override
  1673. public void onClick(DialogInterface dialog, int whichButton) {
  1674. mVoiceInput.logKeyboardWarningDialogCancel();
  1675. }
  1676. });
  1677. if (mLocaleSupportedForVoiceInput) {
  1678. String message = getString(R.string.voice_warning_may_not_understand) + "\n\n"
  1679. + getString(R.string.voice_warning_how_to_turn_off);
  1680. builder.setMessage(message);
  1681. } else {
  1682. String message = getString(R.string.voice_warning_locale_not_supported) + "\n\n"
  1683. + getString(R.string.voice_warning_may_not_understand) + "\n\n"
  1684. + getString(R.string.voice_warning_how_to_turn_off);
  1685. builder.setMessage(message);
  1686. }
  1687. builder.setTitle(R.string.voice_warning_title);
  1688. mVoiceWarningDialog = builder.create();
  1689. Window window = mVoiceWarningDialog.getWindow();
  1690. WindowManager.LayoutParams lp = window.getAttributes();
  1691. lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
  1692. lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
  1693. window.setAttributes(lp);
  1694. window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
  1695. mVoiceInput.logKeyboardWarningDialogShown();
  1696. mVoiceWarningDialog.show();
  1697. }
  1698. @Override
  1699. public void onVoiceResults(List<String> candidates, Map<String, List<CharSequence>> alternatives) {
  1700. if (!mRecognizing) {
  1701. return;
  1702. }
  1703. mVoiceResults.candidates = candidates;
  1704. mVoiceResults.alternatives = alternatives;
  1705. mHandler.sendMessage(mHandler.obtainMessage(MSG_VOICE_RESULTS));
  1706. }
  1707. private void handleVoiceResults() {
  1708. mAfterVoiceInput = true;
  1709. mImmediatelyAfterVoiceInput = true;
  1710. InputConnection ic = getCurrentInputConnection();
  1711. if (!isFullscreenMode()) {
  1712. // Start listening for updates to the text from typing, etc.
  1713. if (ic != null) {
  1714. ExtractedTextRequest req = new ExtractedTextRequest();
  1715. ic.getExtractedText(req, InputConnection.GET_EXTRACTED_TEXT_MONITOR);
  1716. }
  1717. }
  1718. vibrate();
  1719. switchToKeyboardView();
  1720. final List<CharSequence> nBest = new ArrayList<CharSequence>();
  1721. boolean capitalizeFirstWord = preferCapitalization()
  1722. || (mKeyboardSwitcher.isAlphabetMode() && mKeyboardSwitcher.getInputView()
  1723. .isShifted());
  1724. for (String c : mVoiceResults.candidates) {
  1725. if (capitalizeFirstWord) {
  1726. c = Character.toUpperCase(c.charAt(0)) + c.substring(1, c.length());
  1727. }
  1728. nBest.add(c);
  1729. }
  1730. if (nBest.size() == 0) {
  1731. return;
  1732. }
  1733. String bestResult = nBest.get(0).toString();
  1734. mVoiceInput.logVoiceInputDelivered(bestResult.length());
  1735. mHints.registerVoiceResult(bestResult);
  1736. if (ic != null)
  1737. ic.beginBatchEdit(); // To avoid extra updates on committing older
  1738. // text
  1739. commitTyped(ic);
  1740. EditingUtil.appendText(ic, bestResult);
  1741. if (ic != null)
  1742. ic.endBatchEdit();
  1743. mVoiceInputHighlighted = true;
  1744. mWordToSuggestions.putAll(mVoiceResults.alternatives);
  1745. }
  1746. private void clearSuggestions() {
  1747. setSuggestions(null, false, false, false);
  1748. }
  1749. private void setSuggestions(List<CharSequence> suggestions, boolean completions,
  1750. boolean typedWordValid, boolean haveMinimalSuggestion) {
  1751. if (mIsShowingHint) {
  1752. setCandidatesView(mCandidateViewContainer);
  1753. mIsShowingHint = false;
  1754. }
  1755. if (mCandidateView != null) {
  1756. mCandidateView.setSuggestions(suggestions, completions, typedWordValid,
  1757. haveMinimalSuggestion);
  1758. }
  1759. }
  1760. private void updateSuggestions() {
  1761. LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
  1762. if (inputView == null || inputView.getKeyboard() == null) {
  1763. return;
  1764. }
  1765. ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null);
  1766. // Check if we have a suggestion engine attached.
  1767. if ((mSuggest == null || !isPredictionOn()) && !mVoiceInputHighlighted) {
  1768. return;
  1769. }
  1770. if (!mPredicting) {
  1771. setNextSuggestions();
  1772. return;
  1773. }
  1774. showSuggestions(mWord);
  1775. }
  1776. private List<CharSequence> getTypedSuggestions(WordComposer word) {
  1777. List<CharSequence> stringList = mSuggest.getSuggestions(mKeyboardSwitcher.getInputView(),
  1778. word, false, null);
  1779. return stringList;
  1780. }
  1781. private void showCorrections(WordAlternatives alternatives) {
  1782. List<CharSequence> stringList = alternatives.getAlternatives();
  1783. ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).setPreferredLetters(null);
  1784. showSuggestions(stringList, alternatives.getOriginalWord(), false, false);
  1785. }
  1786. private void showSuggestions(WordComposer word) {
  1787. // long startTime = System.currentTimeMillis(); // TIME MEASUREMENT!
  1788. // TODO Maybe need better way of retrieving previous word
  1789. CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(),
  1790. mWordSeparators);
  1791. List<CharSequence> stringList = mSuggest.getSuggestions(mKeyboardSwitcher.getInputView(),
  1792. word, false, prevWord);
  1793. // long stopTime = System.currentTimeMillis(); // TIME MEASUREMENT!
  1794. // Log.d("LatinIME","Suggest Total Time - " + (stopTime - startTime));
  1795. int[] nextLettersFrequencies = mSuggest.getNextLettersFrequencies();
  1796. ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard())
  1797. .setPreferredLetters(nextLettersFrequencies);
  1798. boolean correctionAvailable = !mInputTypeNoAutoCorrect && mSuggest.hasMinimalCorrection();
  1799. // || mCorrectionMode == mSuggest.CORRECTION_FULL;
  1800. CharSequence typedWord = word.getTypedWord();
  1801. // If we're in basic correct
  1802. boolean typedWordValid = mSuggest.isValidWord(typedWord)
  1803. || (preferCapitalization() && mSuggest.isValidWord(typedWord.toString()
  1804. .toLowerCase()));
  1805. if (mCorrectionMode == Suggest.CORRECTION_FULL
  1806. || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM) {
  1807. correctionAvailable |= typedWordValid;
  1808. }
  1809. // Don't auto-correct words with multiple capital letter
  1810. correctionAvailable &= !word.isMostlyCaps();
  1811. correctionAvailable &= !TextEntryState.isCorrecting();
  1812. showSuggestions(stringList, typedWord, typedWordValid, correctionAvailable);
  1813. }
  1814. private void showSuggestions(List<CharSequence> stringList, CharSequence typedWord,
  1815. boolean typedWordValid, boolean correctionAvailable) {
  1816. setSuggestions(stringList, false, typedWordValid, correctionAvailable);
  1817. if (stringList.size() > 0) {
  1818. if (correctionAvailable && !typedWordValid && stringList.size() > 1) {
  1819. mBestWord = stringList.get(1);
  1820. } else {
  1821. mBestWord = typedWord;
  1822. }
  1823. } else {
  1824. mBestWord = null;
  1825. }
  1826. setCandidatesViewShown(isCandidateStripVisible() || mCompletionOn);
  1827. }
  1828. private boolean pickDefaultSuggestion() {
  1829. // Complete any pending candidate query first
  1830. if (mHandler.hasMessages(MSG_UPDATE_SUGGESTIONS)) {
  1831. mHandler.removeMessages(MSG_UPDATE_SUGGESTIONS);
  1832. updateSuggestions();
  1833. }
  1834. if (mBestWord != null && mBestWord.length() > 0) {
  1835. TextEntryState.acceptedDefault(mWord.getTypedWord(), mBestWord);
  1836. mJustAccepted = true;
  1837. pickSuggestion(mBestWord, false);
  1838. // Add the word to the auto dictionary if it's not a known word
  1839. addToDictionaries(mBestWord, AutoDictionary.FREQUENCY_FOR_TYPED);
  1840. return true;
  1841. }
  1842. return false;
  1843. }
  1844. public void pickSuggestionManually(int index, CharSequence suggestion) {
  1845. List<CharSequence> suggestions = mCandidateView.getSuggestions();
  1846. if (mAfterVoiceInput && mShowingVoiceSuggestions) {
  1847. mVoiceInput.flushAllTextModificationCounters();
  1848. // send this intent AFTER logging any prior aggregated edits.
  1849. mVoiceInput.logTextModifiedByChooseSuggestion(suggestion.toString(), index,
  1850. mWordSeparators, getCurrentInputConnection());
  1851. }
  1852. final boolean correcting = TextEntryState.isCorrecting();
  1853. InputConnection ic = getCurrentInputConnection();
  1854. if (ic != null) {
  1855. ic.beginBatchEdit();
  1856. }
  1857. if (mCompletionOn && mCompletions != null && index >= 0 && index < mCompletions.length) {
  1858. CompletionInfo ci = mCompletions[index];
  1859. if (ic != null) {
  1860. ic.commitCompletion(ci);
  1861. }
  1862. mCommittedLength = suggestion.length();
  1863. if (mCandidateView != null) {
  1864. mCandidateView.clear();
  1865. }
  1866. updateShiftKeyState(getCurrentInputEditorInfo());
  1867. if (ic != null) {
  1868. ic.endBatchEdit();
  1869. }
  1870. return;
  1871. }
  1872. // If this is a punctuation, apply it through the normal key press
  1873. if (suggestion.length() == 1
  1874. && (isWordSeparator(suggestion.charAt(0)) || isSuggestedPunctuation(suggestion
  1875. .charAt(0)))) {
  1876. // Word separators are suggested before the user inputs something.
  1877. // So, LatinImeLogger logs "" as a user's input.
  1878. LatinImeLogger.logOnManualSuggestion("", suggestion.toString(), index, suggestions);
  1879. final char primaryCode = suggestion.charAt(0);
  1880. mKeyboardActionListener.onKey(primaryCode, new int[] {
  1881. primaryCode
  1882. }, LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE,
  1883. LatinKeyboardBaseView.NOT_A_TOUCH_COORDINATE);
  1884. if (ic != null) {
  1885. ic.endBatchEdit();
  1886. }
  1887. return;
  1888. }
  1889. mJustAccepted = true;
  1890. pickSuggestion(suggestion, correcting);
  1891. // Add the word to the auto dictionary if it's not a known word
  1892. if (index == 0) {
  1893. addToDictionaries(suggestion, AutoDictionary.FREQUENCY_FOR_PICKED);
  1894. } else {
  1895. addToBigramDictionary(suggestion, 1);
  1896. }
  1897. LatinImeLogger.logOnManualSuggestion(mComposing.toString(), suggestion.toString(), index,
  1898. suggestions);
  1899. TextEntryState.acceptedSuggestion(mComposing.toString(), suggestion);
  1900. // Follow it with a space
  1901. if (mAutoSpace && !correcting) {
  1902. sendSpace();
  1903. mJustAddedAutoSpace = true;
  1904. }
  1905. final boolean showingAddToDictionaryHint = index == 0 && mCorrectionMode > 0
  1906. && !mSuggest.isValidWord(suggestion)
  1907. && !mSuggest.isValidWord(suggestion.toString().toLowerCase());
  1908. if (!correcting) {
  1909. // Fool the state watcher so that a subsequent backspace will not do
  1910. // a revert, unless
  1911. // we just did a correction, in which case we need to stay in
  1912. // TextEntryState.State.PICKED_SUGGESTION state.
  1913. TextEntryState.typedCharacter((char) KEYCODE_SPACE, true);
  1914. setNextSuggestions();
  1915. } else if (!showingAddToDictionaryHint) {
  1916. // If we're not showing the "Touch again to save", then show
  1917. // corrections again.
  1918. // In case the cursor position doesn't change, make sure we show the
  1919. // suggestions again.
  1920. clearSuggestions();
  1921. postUpdateOldSuggestions();
  1922. }
  1923. if (showingAddToDictionaryHint) {
  1924. mCandidateView.showAddToDictionaryHint(suggestion);
  1925. }
  1926. if (ic != null) {
  1927. ic.endBatchEdit();
  1928. }
  1929. }
  1930. private void rememberReplacedWord(CharSequence suggestion) {
  1931. if (mShowingVoiceSuggestions) {
  1932. // Retain the replaced word in the alternatives array.
  1933. EditingUtil.Range range = new EditingUtil.Range();
  1934. String wordToBeReplaced = EditingUtil.getWordAtCursor(getCurrentInputConnection(),
  1935. mWordSeparators, range);
  1936. if (!mWordToSuggestions.containsKey(wordToBeReplaced)) {
  1937. wordToBeReplaced = wordToBeReplaced.toLowerCase();
  1938. }
  1939. if (mWordToSuggestions.containsKey(wordToBeReplaced)) {
  1940. List<CharSequence> suggestions = mWordToSuggestions.get(wordToBeReplaced);
  1941. if (suggestions.contains(suggestion)) {
  1942. suggestions.remove(suggestion);
  1943. }
  1944. suggestions.add(wordToBeReplaced);
  1945. mWordToSuggestions.remove(wordToBeReplaced);
  1946. mWordToSuggestions.put(suggestion.toString(), suggestions);
  1947. }
  1948. }
  1949. }
  1950. /**
  1951. * Commits the chosen word to the text field and saves it for later retrieval.
  1952. *
  1953. * @param suggestion the suggestion picked by the user to be committed to the text field
  1954. * @param correcting whether this is due to a correction of an existing word.
  1955. */
  1956. private void pickSuggestion(CharSequence suggestion, boolean correcting) {
  1957. LatinKeyboardView inputView = mKeyboardSwitcher.getInputView();
  1958. if (mCapsLock) {
  1959. suggestion = suggestion.toString().toUpperCase();
  1960. } else if (preferCapitalization()
  1961. || (mKeyboardSwitcher.isAlphabetMode() && inputView.isShifted())) {
  1962. suggestion = suggestion.toString().toUpperCase().charAt(0)
  1963. + suggestion.subSequence(1, suggestion.length()).toString();
  1964. }
  1965. InputConnection ic = getCurrentInputConnection();
  1966. if (ic != null) {
  1967. rememberReplacedWord(suggestion);
  1968. ic.commitText(suggestion, 1);
  1969. }
  1970. saveWordInHistory(suggestion);
  1971. mPredicting = false;
  1972. mCommittedLength = suggestion.length();
  1973. ((LatinKeyboard) inputView.getKeyboard()).setPreferredLetters(null);
  1974. // If we just corrected a word, then don't show punctuations
  1975. if (!correcting) {
  1976. setNextSuggestions();
  1977. }
  1978. updateShiftKeyState(getCurrentInputEditorInfo());
  1979. }
  1980. /**
  1981. * Tries to apply any voice alternatives for the word if this was a spoken word and there are
  1982. * voice alternatives.
  1983. *
  1984. * @param touching The word that the cursor is touching, with position information
  1985. * @return true if an alternative was found, false otherwise.
  1986. */
  1987. private boolean applyVoiceAlternatives(EditingUtil.SelectedWord touching) {
  1988. // Search for result in spoken word alternatives
  1989. String selectedWord = touching.word.toString().trim();
  1990. if (!mWordToSuggestions.containsKey(selectedWord)) {
  1991. selectedWord = selectedWord.toLowerCase();
  1992. }
  1993. if (mWordToSuggestions.containsKey(selectedWord)) {
  1994. mShowingVoiceSuggestions = true;
  1995. List<CharSequence> suggestions = mWordToSuggestions.get(selectedWord);
  1996. // If the first letter of touching is capitalized, make all the
  1997. // suggestions
  1998. // start with a capital letter.
  1999. if (Character.isUpperCase(touching.word.charAt(0))) {
  2000. for (int i = 0; i < suggestions.size(); i++) {
  2001. String origSugg = (String) suggestions.get(i);
  2002. String capsSugg = origSugg.toUpperCase().charAt(0)
  2003. + origSugg.subSequence(1, origSugg.length()).toString();
  2004. suggestions.set(i, capsSugg);
  2005. }
  2006. }
  2007. setSuggestions(suggestions, false, true, true);
  2008. setCandidatesViewShown(true);
  2009. return true;
  2010. }
  2011. return false;
  2012. }
  2013. /**
  2014. * Tries to apply any typed alternatives for the word if we have any cached alternatives,
  2015. * otherwise tries to find new corrections and completions for the word.
  2016. *
  2017. * @param touching The word that the cursor is touching, with position information
  2018. * @return true if an alternative was found, false otherwise.
  2019. */
  2020. private boolean applyTypedAlternatives(EditingUtil.SelectedWord touching) {
  2021. // If we didn't find a match, search for result in typed word history
  2022. WordComposer foundWord = null;
  2023. WordAlternatives alternatives = null;
  2024. for (WordAlternatives entry : mWordHistory) {
  2025. if (TextUtils.equals(entry.getChosenWord(), touching.word)) {
  2026. if (entry instanceof TypedWordAlternatives) {
  2027. foundWord = ((TypedWordAlternatives) entry).word;
  2028. }
  2029. alternatives = entry;
  2030. break;
  2031. }
  2032. }
  2033. // If we didn't find a match, at least suggest completions
  2034. if (foundWord == null
  2035. && (mSuggest.isValidWord(touching.word) || mSuggest.isValidWord(touching.word
  2036. .toString().toLowerCase()))) {
  2037. foundWord = new WordComposer();
  2038. for (int i = 0; i < touching.word.length(); i++) {
  2039. foundWord.add(touching.word.charAt(i), new int[] {
  2040. touching.word.charAt(i)
  2041. });
  2042. }
  2043. foundWord.setFirstCharCapitalized(Character.isUpperCase(touching.word.charAt(0)));
  2044. }
  2045. // Found a match, show suggestions
  2046. if (foundWord != null || alternatives != null) {
  2047. if (alternatives == null) {
  2048. alternatives = new TypedWordAlternatives(touching.word, foundWord);
  2049. }
  2050. showCorrections(alternatives);
  2051. if (foundWord != null) {
  2052. mWord = new WordComposer(foundWord);
  2053. } else {
  2054. mWord.reset();
  2055. }
  2056. return true;
  2057. }
  2058. return false;
  2059. }
  2060. private void setOldSuggestions() {
  2061. mShowingVoiceSuggestions = false;
  2062. if (mCandidateView != null && mCandidateView.isShowingAddToDictionaryHint()) {
  2063. return;
  2064. }
  2065. InputConnection ic = getCurrentInputConnection();
  2066. if (ic == null)
  2067. return;
  2068. if (!mPredicting) {
  2069. // Extract the selected or touching text
  2070. EditingUtil.SelectedWord touching = EditingUtil.getWordAtCursorOrSelection(ic,
  2071. mLastSelectionStart, mLastSelectionEnd, mWordSeparators);
  2072. if (touching != null && touching.word.length() > 1) {
  2073. ic.beginBatchEdit();
  2074. if (!applyVoiceAlternatives(touching) && !applyTypedAlternatives(touching)) {
  2075. abortCorrection(true);
  2076. } else {
  2077. TextEntryState.selectedForCorrection();
  2078. EditingUtil.underlineWord(ic, touching);
  2079. }
  2080. ic.endBatchEdit();
  2081. } else {
  2082. abortCorrection(true);
  2083. setNextSuggestions(); // Show the punctuation suggestions list
  2084. }
  2085. } else {
  2086. abortCorrection(true);
  2087. }
  2088. }
  2089. private void setNextSuggestions() {
  2090. setSuggestions(mSuggestPuncList, false, false, false);
  2091. }
  2092. private void addToDictionaries(CharSequence suggestion, int frequencyDelta) {
  2093. checkAddToDictionary(suggestion, frequencyDelta, false);
  2094. }
  2095. private void addToBigramDictionary(CharSequence suggestion, int frequencyDelta) {
  2096. checkAddToDictionary(suggestion, frequencyDelta, true);
  2097. }
  2098. /**
  2099. * Adds to the UserBigramDictionary and/or AutoDictionary
  2100. *
  2101. * @param addToBigramDictionary true if it should be added to bigram dictionary if possible
  2102. */
  2103. private void checkAddToDictionary(CharSequence suggestion, int frequencyDelta,
  2104. boolean addToBigramDictionary) {
  2105. if (suggestion == null || suggestion.length() < 1)
  2106. return;
  2107. // Only auto-add to dictionary if auto-correct is ON. Otherwise we'll be
  2108. // adding words in situations where the user or application really
  2109. // didn't
  2110. // want corrections enabled or learned.
  2111. if (!(mCorrectionMode == Suggest.CORRECTION_FULL || mCorrectionMode == Suggest.CORRECTION_FULL_BIGRAM)) {
  2112. return;
  2113. }
  2114. if (suggestion != null) {
  2115. if (!addToBigramDictionary
  2116. && mAutoDictionary.isValidWord(suggestion)
  2117. || (!mSuggest.isValidWord(suggestion.toString()) && !mSuggest
  2118. .isValidWord(suggestion.toString().toLowerCase()))) {
  2119. mAutoDictionary.addWord(suggestion.toString(), frequencyDelta);
  2120. }
  2121. if (mUserBigramDictionary != null) {
  2122. CharSequence prevWord = EditingUtil.getPreviousWord(getCurrentInputConnection(),
  2123. mSentenceSeparators);
  2124. if (!TextUtils.isEmpty(prevWord)) {
  2125. mUserBigramDictionary.addBigrams(prevWord.toString(), suggestion.toString());
  2126. }
  2127. }
  2128. }
  2129. }
  2130. private boolean isCursorTouchingWord() {
  2131. InputConnection ic = getCurrentInputConnection();
  2132. if (ic == null)
  2133. return false;
  2134. CharSequence toLeft = ic.getTextBeforeCursor(1, 0);
  2135. CharSequence toRight = ic.getTextAfterCursor(1, 0);
  2136. if (!TextUtils.isEmpty(toLeft) && !isWordSeparator(toLeft.charAt(0))
  2137. && !isSuggestedPunctuation(toLeft.charAt(0))) {
  2138. return true;
  2139. }
  2140. if (!TextUtils.isEmpty(toRight) && !isWordSeparator(toRight.charAt(0))
  2141. && !isSuggestedPunctuation(toRight.charAt(0))) {
  2142. return true;
  2143. }
  2144. return false;
  2145. }
  2146. private boolean sameAsTextBeforeCursor(InputConnection ic, CharSequence text) {
  2147. CharSequence beforeText = ic.getTextBeforeCursor(text.length(), 0);
  2148. return TextUtils.equals(text, beforeText);
  2149. }
  2150. public void revertLastWord(boolean deleteChar) {
  2151. final int length = mComposing.length();
  2152. if (!mPredicting && length > 0) {
  2153. final InputConnection ic = getCurrentInputConnection();
  2154. mPredicting = true;
  2155. mJustRevertedSeparator = ic.getTextBeforeCursor(1, 0);
  2156. if (deleteChar)
  2157. ic.deleteSurroundingText(1, 0);
  2158. int toDelete = mCommittedLength;
  2159. CharSequence toTheLeft = ic.getTextBeforeCursor(mCommittedLength, 0);
  2160. if (toTheLeft != null && toTheLeft.length() > 0 && isWordSeparator(toTheLeft.charAt(0))) {
  2161. toDelete--;
  2162. }
  2163. ic.deleteSurroundingText(toDelete, 0);
  2164. ic.setComposingText(mComposing, 1);
  2165. TextEntryState.backspace();
  2166. postUpdateSuggestions();
  2167. } else {
  2168. sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
  2169. mJustRevertedSeparator = null;
  2170. }
  2171. }
  2172. protected String getWordSeparators() {
  2173. return mWordSeparators;
  2174. }
  2175. public boolean isWordSeparator(int code) {
  2176. String separators = getWordSeparators();
  2177. return separators.contains(String.valueOf((char) code));
  2178. }
  2179. private boolean isSentenceSeparator(int code) {
  2180. return mSentenceSeparators.contains(String.valueOf((char) code));
  2181. }
  2182. private void sendSpace() {
  2183. sendKeyChar((char) KEYCODE_SPACE);
  2184. updateShiftKeyState(getCurrentInputEditorInfo());
  2185. // onKey(KEY_SPACE[0], KEY_SPACE);
  2186. }
  2187. public boolean preferCapitalization() {
  2188. return mWord.isFirstCharCapitalized();
  2189. }
  2190. private void toggleLanguage(boolean reset, boolean next) {
  2191. if (reset) {
  2192. mLanguageSwitcher.reset();
  2193. } else {
  2194. if (next) {
  2195. mLanguageSwitcher.next();
  2196. } else {
  2197. mLanguageSwitcher.prev();
  2198. }
  2199. }
  2200. int currentKeyboardMode = mKeyboardSwitcher.getKeyboardMode();
  2201. reloadKeyboards();
  2202. mKeyboardSwitcher.makeKeyboards(true);
  2203. mKeyboardSwitcher.setKeyboardMode(currentKeyboardMode, 0, mEnableVoiceButton
  2204. && mEnableVoice);
  2205. initSuggest(mLanguageSwitcher.getInputLanguage());
  2206. mLanguageSwitcher.persist();
  2207. updateShiftKeyState(getCurrentInputEditorInfo());
  2208. }
  2209. @Override
  2210. public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
  2211. if (PREF_SELECTED_LANGUAGES.equals(key)) {
  2212. mLanguageSwitcher.loadLocales(sharedPreferences);
  2213. mRefreshKeyboardRequired = true;
  2214. } else if (PREF_RECORRECTION_ENABLED.equals(key)) {
  2215. mReCorrectionEnabled = sharedPreferences.getBoolean(PREF_RECORRECTION_ENABLED,
  2216. getResources().getBoolean(R.bool.default_recorrection_enabled));
  2217. }
  2218. }
  2219. /**
  2220. * @see #sendDownUpKeyEventsPreIme(int, int, long)
  2221. */
  2222. private void sendDownUpKeyEventsPreIme(int keyCode, int metaState) {
  2223. sendDownUpKeyEventsPreIme(keyCode, metaState, 0);
  2224. }
  2225. /**
  2226. * Attempts to send key events to the IME, then to the input connection if they are not handled
  2227. * by the IME. <br>
  2228. * Unlike {@link InputMethodService#sendDownUpKeyEvents(int)}, this method does not set
  2229. * {@link KeyEvent#FLAG_KEEP_TOUCH_MODE} and will therefore exit touch mode and enable focus.
  2230. *
  2231. * @param keyCode The key code to send to the IME (or input connection).
  2232. * @param metaState The meta keys to send along with the key code.
  2233. * @param delay The delay is milliseconds between key events.
  2234. * @see KeyEvent
  2235. */
  2236. private void sendDownUpKeyEventsPreIme(int keyCode, int metaState, long delay) {
  2237. final InputConnection ic = getCurrentInputConnection();
  2238. if (ic == null) {
  2239. Log.e(TAG, "Cannot send key events, no input connection available");
  2240. return;
  2241. }
  2242. final long downTime = SystemClock.uptimeMillis();
  2243. final KeyEvent event1 = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
  2244. 0, metaState, KeyCharacterMap.BUILT_IN_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD);
  2245. if (!onKeyDown(keyCode, event1)) {
  2246. ic.sendKeyEvent(event1);
  2247. }
  2248. mHandler.postDelayed(new KeyEventRunnable(keyCode, metaState, downTime), delay);
  2249. }
  2250. @Override
  2251. public void onGranularityChanged(int granularity) {
  2252. super.onGranularityChanged(granularity);
  2253. sendBroadcast(
  2254. new Intent(BROADCAST_GRANULARITY_CHANGE).putExtra(EXTRA_GRANULARITY, granularity),
  2255. PERMISSION_REQUEST);
  2256. }
  2257. private FieldContext makeFieldContext() {
  2258. return new FieldContext(getCurrentInputConnection(), getCurrentInputEditorInfo(),
  2259. mLanguageSwitcher.getInputLanguage(), mLanguageSwitcher.getEnabledLanguages());
  2260. }
  2261. private boolean fieldCanDoVoice(FieldContext fieldContext) {
  2262. return !mPasswordText && mVoiceInput != null
  2263. && !mVoiceInput.isBlacklistedField(fieldContext);
  2264. }
  2265. private boolean shouldShowVoiceButton(FieldContext fieldContext, EditorInfo attribute) {
  2266. return ENABLE_VOICE_BUTTON
  2267. && fieldCanDoVoice(fieldContext)
  2268. && !(attribute != null && IME_OPTION_NO_MICROPHONE
  2269. .equals(attribute.privateImeOptions))
  2270. && SpeechRecognizer.isRecognitionAvailable(this);
  2271. }
  2272. // update flags for silent mode
  2273. private void updateRingerMode() {
  2274. if (mAudioManager == null) {
  2275. mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
  2276. }
  2277. if (mAudioManager != null) {
  2278. mSilentMode = (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL);
  2279. }
  2280. }
  2281. private void playKeyClick(int primaryCode) {
  2282. // if mAudioManager is null, we don't have the ringer state yet
  2283. // mAudioManager will be set by updateRingerMode
  2284. if (mAudioManager == null) {
  2285. if (mKeyboardSwitcher.getInputView() != null) {
  2286. updateRingerMode();
  2287. }
  2288. }
  2289. if (mSoundOn && !mSilentMode) {
  2290. // FIXME: Volume and enable should come from UI settings
  2291. // FIXME: These should be triggered after auto-repeat logic
  2292. int sound = AudioManager.FX_KEYPRESS_STANDARD;
  2293. switch (primaryCode) {
  2294. case Keyboard.KEYCODE_DELETE:
  2295. sound = AudioManager.FX_KEYPRESS_DELETE;
  2296. break;
  2297. case KEYCODE_ENTER:
  2298. sound = AudioManager.FX_KEYPRESS_RETURN;
  2299. break;
  2300. case KEYCODE_SPACE:
  2301. sound = AudioManager.FX_KEYPRESS_SPACEBAR;
  2302. break;
  2303. }
  2304. mAudioManager.playSoundEffect(sound, FX_VOLUME);
  2305. }
  2306. }
  2307. private void vibrate() {
  2308. if (!mVibrateOn) {
  2309. return;
  2310. }
  2311. if (mKeyboardSwitcher.getInputView() != null) {
  2312. mKeyboardSwitcher.getInputView().performHapticFeedback(
  2313. HapticFeedbackConstants.VIRTUAL_KEY,
  2314. HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
  2315. }
  2316. }
  2317. /* package */void promoteToUserDictionary(String word, int frequency) {
  2318. if (mUserDictionary.isValidWord(word))
  2319. return;
  2320. mUserDictionary.addWord(word, frequency);
  2321. }
  2322. /* package */WordComposer getCurrentWord() {
  2323. return mWord;
  2324. }
  2325. /* package */boolean getPopupOn() {
  2326. return mPopupOn;
  2327. }
  2328. private void updateCorrectionMode() {
  2329. mHasDictionary = mSuggest != null ? mSuggest.hasMainDictionary() : false;
  2330. mAutoCorrectOn = (mAutoCorrectEnabled || mQuickFixes) && !mInputTypeNoAutoCorrect
  2331. && mHasDictionary;
  2332. mCorrectionMode = (mAutoCorrectOn && mAutoCorrectEnabled) ? Suggest.CORRECTION_FULL
  2333. : (mAutoCorrectOn ? Suggest.CORRECTION_BASIC : Suggest.CORRECTION_NONE);
  2334. mCorrectionMode = (mBigramSuggestionEnabled && mAutoCorrectOn && mAutoCorrectEnabled) ? Suggest.CORRECTION_FULL_BIGRAM
  2335. : mCorrectionMode;
  2336. if (mSuggest != null) {
  2337. mSuggest.setCorrectionMode(mCorrectionMode);
  2338. }
  2339. }
  2340. private void updateAutoTextEnabled(Locale systemLocale) {
  2341. if (mSuggest == null)
  2342. return;
  2343. boolean different = !systemLocale.getLanguage().equalsIgnoreCase(
  2344. mInputLocale.substring(0, 2));
  2345. mSuggest.setAutoTextEnabled(!different && mQuickFixes);
  2346. }
  2347. protected void launchSettings() {
  2348. launchSettings(LatinIMESettings.class);
  2349. }
  2350. protected void launchAccessibilitySettings() {
  2351. handleClose();
  2352. Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
  2353. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  2354. startActivity(intent);
  2355. }
  2356. public void launchDebugSettings() {
  2357. launchSettings(LatinIMEDebugSettings.class);
  2358. }
  2359. protected void launchSettings(Class<? extends PreferenceActivity> settingsClass) {
  2360. handleClose();
  2361. Intent intent = new Intent();
  2362. intent.setClass(LatinIME.this, settingsClass);
  2363. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  2364. startActivity(intent);
  2365. }
  2366. private void loadSettings() {
  2367. // Get the settings preferences
  2368. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
  2369. mAutoSwitch = sp.getBoolean(PREF_AUTO_SWITCH,
  2370. mResources.getBoolean(R.bool.default_auto_switch));
  2371. mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, mResources.getBoolean(R.bool.default_vibrate));
  2372. mSoundOn = sp.getBoolean(PREF_SOUND_ON, mResources.getBoolean(R.bool.default_sound));
  2373. mPopupOn = sp
  2374. .getBoolean(PREF_POPUP_ON, mResources.getBoolean(R.bool.default_popup_preview));
  2375. mAutoCap = sp.getBoolean(PREF_AUTO_CAP, mResources.getBoolean(R.bool.default_auto_cap));
  2376. mQuickFixes = sp.getBoolean(PREF_QUICK_FIXES,
  2377. mResources.getBoolean(R.bool.default_quick_fixes));
  2378. mHasUsedVoiceInput = sp.getBoolean(PREF_HAS_USED_VOICE_INPUT, false);
  2379. mHasUsedVoiceInputUnsupportedLocale = sp.getBoolean(
  2380. PREF_HAS_USED_VOICE_INPUT_UNSUPPORTED_LOCALE, false);
  2381. // Apply preferences to feedback utility
  2382. mFeedbackUtil.setVibrationEnabled(mVibrateOn);
  2383. mFeedbackUtil.setSoundEnabled(mSoundOn);
  2384. // Get the current list of supported locales and check the current
  2385. // locale against that
  2386. // list. We cache this value so as not to check it every time the user
  2387. // starts a voice
  2388. // input. Because this method is called by onStartInputView, this should
  2389. // mean that as
  2390. // long as the locale doesn't change while the user is keeping the IME
  2391. // open, the
  2392. // value should never be stale.
  2393. String supportedLocalesString = SettingsUtil.getSettingsString(getContentResolver(),
  2394. SettingsUtil.LATIN_IME_VOICE_INPUT_SUPPORTED_LOCALES,
  2395. DEFAULT_VOICE_INPUT_SUPPORTED_LOCALES);
  2396. ArrayList<String> voiceInputSupportedLocales = newArrayList(supportedLocalesString
  2397. .split("\\s+"));
  2398. mLocaleSupportedForVoiceInput = voiceInputSupportedLocales.contains(mInputLocale);
  2399. mShowSuggestions = sp.getBoolean(PREF_SHOW_SUGGESTIONS,
  2400. mResources.getBoolean(R.bool.default_show_suggestions));
  2401. if (VOICE_INSTALLED) {
  2402. final String voiceMode = sp.getString(PREF_VOICE_MODE,
  2403. getString(R.string.voice_mode_main));
  2404. boolean enableVoice = !voiceMode.equals(getString(R.string.voice_mode_off))
  2405. && mEnableVoiceButton;
  2406. boolean voiceOnPrimary = voiceMode.equals(getString(R.string.voice_mode_main));
  2407. if (mKeyboardSwitcher != null
  2408. && (enableVoice != mEnableVoice || voiceOnPrimary != mVoiceOnPrimary)) {
  2409. mKeyboardSwitcher.setVoiceMode(enableVoice, voiceOnPrimary);
  2410. }
  2411. mEnableVoice = enableVoice;
  2412. mVoiceOnPrimary = voiceOnPrimary;
  2413. }
  2414. mAutoCorrectEnabled = sp.getBoolean(PREF_AUTO_COMPLETE,
  2415. mResources.getBoolean(R.bool.enable_autocorrect))
  2416. & mShowSuggestions;
  2417. mBigramSuggestionEnabled = sp.getBoolean(PREF_BIGRAM_SUGGESTIONS,
  2418. mResources.getBoolean(R.bool.default_bigram_suggestions))
  2419. & mShowSuggestions;
  2420. final boolean linearNavigation =
  2421. sp.getBoolean(PREF_LINEAR_NAVIGATION,
  2422. mResources.getBoolean(R.bool.default_linear_navigation));
  2423. setLinearNavigationEnabled(linearNavigation);
  2424. updateCorrectionMode();
  2425. updateAutoTextEnabled(mResources.getConfiguration().locale);
  2426. mLanguageSwitcher.loadLocales(sp);
  2427. }
  2428. private void initSuggestPuncList() {
  2429. mSuggestPuncList = new ArrayList<CharSequence>();
  2430. mSuggestPuncs = mResources.getString(R.string.suggested_punctuations);
  2431. if (mSuggestPuncs != null) {
  2432. for (int i = 0; i < mSuggestPuncs.length(); i++) {
  2433. mSuggestPuncList.add(mSuggestPuncs.subSequence(i, i + 1));
  2434. }
  2435. }
  2436. }
  2437. private boolean isSuggestedPunctuation(int code) {
  2438. return mSuggestPuncs.contains(String.valueOf((char) code));
  2439. }
  2440. private void showOptionsMenu() {
  2441. AlertDialog.Builder builder = new AlertDialog.Builder(this);
  2442. builder.setCancelable(true);
  2443. builder.setIcon(R.drawable.ic_dialog_keyboard);
  2444. builder.setNegativeButton(android.R.string.cancel, null);
  2445. CharSequence itemSettings = getString(R.string.english_ime_settings);
  2446. CharSequence itemAccessibilitySettings = getString(R.string.accessibility_settings);
  2447. CharSequence itemInputMethod = getString(R.string.selectInputMethod);
  2448. builder.setItems(new CharSequence[] {
  2449. itemInputMethod, itemSettings, itemAccessibilitySettings
  2450. }, new DialogInterface.OnClickListener() {
  2451. @Override
  2452. public void onClick(DialogInterface di, int position) {
  2453. di.dismiss();
  2454. switch (position) {
  2455. case POS_SETTINGS:
  2456. launchSettings();
  2457. break;
  2458. case POS_ACCESSIBILITY_SETTINGS:
  2459. launchAccessibilitySettings();
  2460. break;
  2461. case POS_METHOD:
  2462. ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE))
  2463. .showInputMethodPicker();
  2464. break;
  2465. }
  2466. }
  2467. });
  2468. builder.setTitle(mResources.getString(R.string.english_ime_input_options));
  2469. mOptionsDialog = builder.create();
  2470. Window window = mOptionsDialog.getWindow();
  2471. WindowManager.LayoutParams lp = window.getAttributes();
  2472. lp.token = mKeyboardSwitcher.getInputView().getWindowToken();
  2473. lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
  2474. window.setAttributes(lp);
  2475. window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
  2476. mOptionsDialog.show();
  2477. }
  2478. private void changeKeyboardMode() {
  2479. mKeyboardSwitcher.toggleSymbols();
  2480. if (mCapsLock && mKeyboardSwitcher.isAlphabetMode()) {
  2481. mKeyboardSwitcher.setShiftLocked(mCapsLock);
  2482. }
  2483. updateShiftKeyState(getCurrentInputEditorInfo());
  2484. }
  2485. public static <E> ArrayList<E> newArrayList(E... elements) {
  2486. int capacity = (elements.length * 110) / 100 + 5;
  2487. ArrayList<E> list = new ArrayList<E>(capacity);
  2488. Collections.addAll(list, elements);
  2489. return list;
  2490. }
  2491. @Override
  2492. protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
  2493. super.dump(fd, fout, args);
  2494. final Printer p = new PrintWriterPrinter(fout);
  2495. p.println("LatinIME state :");
  2496. p.println(" Keyboard mode = " + mKeyboardSwitcher.getKeyboardMode());
  2497. p.println(" mCapsLock=" + mCapsLock);
  2498. p.println(" mComposing=" + mComposing.toString());
  2499. p.println(" mPredictionOn=" + mPredictionOn);
  2500. p.println(" mCorrectionMode=" + mCorrectionMode);
  2501. p.println(" mPredicting=" + mPredicting);
  2502. p.println(" mAutoCorrectOn=" + mAutoCorrectOn);
  2503. p.println(" mAutoSpace=" + mAutoSpace);
  2504. p.println(" mCompletionOn=" + mCompletionOn);
  2505. p.println(" TextEntryState.state=" + TextEntryState.getState());
  2506. p.println(" mSoundOn=" + mSoundOn);
  2507. p.println(" mVibrateOn=" + mVibrateOn);
  2508. p.println(" mPopupOn=" + mPopupOn);
  2509. }
  2510. // Characters per second measurement
  2511. private long mLastCpsTime;
  2512. private static final int CPS_BUFFER_SIZE = 16;
  2513. private long[] mCpsIntervals = new long[CPS_BUFFER_SIZE];
  2514. private int mCpsIndex;
  2515. private void measureCps() {
  2516. long now = System.currentTimeMillis();
  2517. if (mLastCpsTime == 0)
  2518. mLastCpsTime = now - 100; // Initial
  2519. mCpsIntervals[mCpsIndex] = now - mLastCpsTime;
  2520. mLastCpsTime = now;
  2521. mCpsIndex = (mCpsIndex + 1) % CPS_BUFFER_SIZE;
  2522. long total = 0;
  2523. for (int i = 0; i < CPS_BUFFER_SIZE; i++)
  2524. total += mCpsIntervals[i];
  2525. System.out.println("CPS = " + ((CPS_BUFFER_SIZE * 1000f) / total));
  2526. }
  2527. public void onAutoCompletionStateChanged(boolean isAutoCompletion) {
  2528. mKeyboardSwitcher.onAutoCompletionStateChanged(isAutoCompletion);
  2529. }
  2530. public final LatinKeyboardBaseView.OnKeyboardActionListener mKeyboardActionListener = new LatinKeyboardBaseView.OnKeyboardActionListener() {
  2531. @Override
  2532. public boolean swipeRight(int pointerCount) {
  2533. return sendSwipeEvent(pointerCount, KeyEvent.KEYCODE_DPAD_RIGHT);
  2534. }
  2535. @Override
  2536. public boolean swipeLeft(int pointerCount) {
  2537. return sendSwipeEvent(pointerCount, KeyEvent.KEYCODE_DPAD_LEFT);
  2538. }
  2539. @Override
  2540. public boolean swipeDown(int pointerCount) {
  2541. return sendSwipeEvent(pointerCount, KeyEvent.KEYCODE_DPAD_DOWN);
  2542. }
  2543. @Override
  2544. public boolean swipeUp(int pointerCount) {
  2545. return sendSwipeEvent(pointerCount, KeyEvent.KEYCODE_DPAD_UP);
  2546. }
  2547. private boolean sendSwipeEvent(int pointerCount, int keyCode) {
  2548. mFeedbackUtil.playSound(SOUND_RESOURCE_SWIPE);
  2549. mFeedbackUtil.vibrate(VIBRATE_PATTERN_SWIPE);
  2550. final boolean isAltOn = !mKeyboardSwitcher.isAlphabetMode()
  2551. && mKeyboardSwitcher.isShiftedOrShiftLocked();
  2552. final int metaState = (isAltOn | pointerCount >= 2) ? KeyEvent.META_ALT_ON : 0;
  2553. sendDownUpKeyEventsPreIme(keyCode, metaState);
  2554. return true;
  2555. }
  2556. @Override
  2557. public boolean singleTap(int pointerCount) {
  2558. if (pointerCount == 1
  2559. && mKeyboardSwitcher.getKeyboardMode() == KeyboardSwitcher.MODE_DPAD
  2560. && !mKeyboardSwitcher.isDpadKeysEnabled()) {
  2561. mFeedbackUtil.playSound(SOUND_RESOURCE_TAP);
  2562. mFeedbackUtil.vibrate(VIBRATE_PATTERN_TAP);
  2563. sendDownUpKeyEventsPreIme(KeyEvent.KEYCODE_DPAD_CENTER, 0);
  2564. return true;
  2565. } else if (pointerCount == 2) {
  2566. mFeedbackUtil.playSound(SOUND_RESOURCE_SWIPE);
  2567. mFeedbackUtil.vibrate(VIBRATE_PATTERN_SWIPE);
  2568. sendDownUpKeyEventsPreIme(KeyEvent.KEYCODE_DPAD_UP, KeyEvent.META_ALT_ON);
  2569. return true;
  2570. } else {
  2571. return false;
  2572. }
  2573. }
  2574. @Override
  2575. public boolean doubleTap(int pointerCount) {
  2576. if (pointerCount == 1
  2577. && mKeyboardSwitcher.getKeyboardMode() == KeyboardSwitcher.MODE_DPAD) {
  2578. mFeedbackUtil.playSound(SOUND_RESOURCE_DOUBLE_TAP);
  2579. mFeedbackUtil.vibrate(VIBRATE_PATTERN_DOUBLE_TAP);
  2580. sendDownUpKeyEventsPreIme(KeyEvent.KEYCODE_DPAD_CENTER, 0);
  2581. sendDownUpKeyEventsPreIme(KeyEvent.KEYCODE_DPAD_CENTER, 0);
  2582. return true;
  2583. } else if (pointerCount == 2) {
  2584. mFeedbackUtil.playSound(SOUND_RESOURCE_SWIPE);
  2585. mFeedbackUtil.vibrate(VIBRATE_PATTERN_SWIPE);
  2586. sendDownUpKeyEventsPreIme(KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.META_ALT_ON);
  2587. return true;
  2588. } else {
  2589. return false;
  2590. }
  2591. }
  2592. @Override
  2593. public boolean longPress(int pointerCount) {
  2594. if (mKeyboardSwitcher.getKeyboardMode() == KeyboardSwitcher.MODE_DPAD) {
  2595. // TODO Add support for long-pressing system keys like Search.
  2596. if (mPressedKey != LatinKeyboardView.KEYCODE_UNKNOWN) {
  2597. return false;
  2598. }
  2599. mFeedbackUtil.playSound(SOUND_RESOURCE_LONG_PRESS);
  2600. mFeedbackUtil.vibrate(VIBRATE_PATTERN_LONG_PRESS);
  2601. final long timeout = ViewConfiguration.getLongPressTimeout()
  2602. + ViewConfiguration.getTapTimeout();
  2603. sendDownUpKeyEventsPreIme(KeyEvent.KEYCODE_DPAD_CENTER, 0, timeout);
  2604. return true;
  2605. } else {
  2606. return false;
  2607. }
  2608. }
  2609. @Override
  2610. public void exploreKeyboardArea() {
  2611. // TODO We used to vibrate here, but it was annoying. Maybe add some
  2612. // sort of feedback.
  2613. }
  2614. @Override
  2615. public void enteredSegment(int segment) {
  2616. if (segment < 0) {
  2617. speakCurrentText();
  2618. return;
  2619. }
  2620. if (!isCandidateStripVisible())
  2621. return;
  2622. final List<CharSequence> suggestions = mCandidateView.getSuggestions();
  2623. final int index = Math.min(segment, suggestions.size() - 1);
  2624. final CharSequence text = suggestions.get(index);
  2625. mFeedbackUtil.vibrate(VIBRATE_PATTERN_TAP);
  2626. mFeedbackUtil.playSound(SOUND_RESOURCE_TAP);
  2627. mAccessibilityUtils.speakDescription(text);
  2628. }
  2629. @Override
  2630. public void selectedSegment(int segment) {
  2631. final AccessibleInputConnection aic = getCurrentInputConnection();
  2632. if (!aic.hasExtractedText() || !isCandidateStripVisible() || segment < 0)
  2633. return;
  2634. mFeedbackUtil.vibrate(VIBRATE_PATTERN_SELECTED);
  2635. mFeedbackUtil.playSound(SOUND_RESOURCE_SELECTED);
  2636. final List<CharSequence> suggestions = mCandidateView.getSuggestions();
  2637. final int index = Math.min(segment, suggestions.size());
  2638. final CharSequence suggestion = suggestions.get(segment);
  2639. pickSuggestionManually(index, suggestion);
  2640. }
  2641. @Override
  2642. public void leftKeyboardArea() {
  2643. sendBroadcast(new Intent(BROADCAST_LEFT_KEYBOARD_AREA), PERMISSION_REQUEST);
  2644. mFeedbackUtil.vibrate(VIBRATE_PATTERN_LEFT);
  2645. mFeedbackUtil.playSound(R.raw.type3);
  2646. if (isCandidateStripVisible())
  2647. return;
  2648. speakCurrentText();
  2649. }
  2650. private void speakCurrentText() {
  2651. final AccessibleInputConnection aic = getCurrentInputConnection();
  2652. final ExtractedText extracted =
  2653. aic.getExtractedText(new ExtractedTextRequest(), 0);
  2654. if (extracted != null && extracted.text != null) {
  2655. final CharSequence text;
  2656. if (extracted != null && !TextUtils.isEmpty(extracted.text)) {
  2657. text = getString(R.string.spoken_current_text_is, extracted.text);
  2658. } else {
  2659. text = getString(R.string.spoken_no_text_entered);
  2660. }
  2661. mAccessibilityUtils.speakDescription(text);
  2662. }
  2663. }
  2664. @Override
  2665. public void enteredKeyboardArea() {
  2666. sendBroadcast(new Intent(BROADCAST_ENTERED_KEYBOARD_AREA), PERMISSION_REQUEST);
  2667. mFeedbackUtil.vibrate(VIBRATE_PATTERN_ENTERED);
  2668. mFeedbackUtil.playSound(R.raw.type3);
  2669. }
  2670. @Override
  2671. public void upOutsideKeyboardArea() {
  2672. sendBroadcast(new Intent(BROADCAST_UP_OUTSIDE_KEYBOARD), PERMISSION_REQUEST);
  2673. }
  2674. @Override
  2675. public void onPress(Key key) {
  2676. if (key == null) {
  2677. return;
  2678. }
  2679. final int primaryCode = key.codes[0];
  2680. mPressedKey = primaryCode;
  2681. mFeedbackUtil.vibrate(VIBRATE_PATTERN_SELECTED);
  2682. mFeedbackUtil.playSound(SOUND_RESOURCE_SELECTED);
  2683. final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch();
  2684. final boolean accessibilityEnabled = mKeyboardSwitcher.isAccessibilityEnabled();
  2685. if (!accessibilityEnabled && distinctMultiTouch
  2686. && primaryCode == Keyboard.KEYCODE_SHIFT) {
  2687. mShiftKeyState.onPress();
  2688. } else if (!accessibilityEnabled && distinctMultiTouch
  2689. && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
  2690. mSymbolKeyState.onPress();
  2691. } else {
  2692. mShiftKeyState.onOtherKeyPressed();
  2693. mSymbolKeyState.onOtherKeyPressed();
  2694. }
  2695. mAccessibilityUtils.onPress(getResources(), mKeyboardSwitcher, key);
  2696. }
  2697. @Override
  2698. public void onLeaving(Key key) {
  2699. if (key == null) {
  2700. return;
  2701. }
  2702. final int primaryCode = key.codes[0];
  2703. mPressedKey = LatinKeyboardView.KEYCODE_UNKNOWN;
  2704. // Reset any drag flags in the keyboard
  2705. ((LatinKeyboard) mKeyboardSwitcher.getInputView().getKeyboard()).keyReleased();
  2706. // Handle multitouch keys
  2707. final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch();
  2708. final boolean accessibilityEnabled = mKeyboardSwitcher.isAccessibilityEnabled();
  2709. if (!accessibilityEnabled && distinctMultiTouch
  2710. && primaryCode == Keyboard.KEYCODE_SHIFT) {
  2711. if (mShiftKeyState.isMomentary())
  2712. resetShift();
  2713. mShiftKeyState.onRelease();
  2714. } else if (!accessibilityEnabled && distinctMultiTouch
  2715. && primaryCode == Keyboard.KEYCODE_MODE_CHANGE) {
  2716. if (mSymbolKeyState.isMomentary())
  2717. changeKeyboardMode();
  2718. mSymbolKeyState.onRelease();
  2719. }
  2720. }
  2721. @Override
  2722. public void onRelease(Key key) {
  2723. if (key == null) {
  2724. return;
  2725. }
  2726. final int primaryCode = key.codes[0];
  2727. // Releasing implies leaving
  2728. onLeaving(key);
  2729. // Provide feedback
  2730. vibrate();
  2731. playKeyClick(primaryCode);
  2732. mAccessibilityUtils.onRelease(key, mKeyboardSwitcher);
  2733. }
  2734. // Implementation of KeyboardViewListener
  2735. @Override
  2736. public void onKey(int primaryCode, int[] keyCodes, int x, int y) {
  2737. long when = SystemClock.uptimeMillis();
  2738. if (primaryCode != Keyboard.KEYCODE_DELETE || when > mLastKeyTime + QUICK_PRESS) {
  2739. mDeleteCount = 0;
  2740. }
  2741. mLastKeyTime = when;
  2742. final boolean accessibilityEnabled = mKeyboardSwitcher.isAccessibilityEnabled();
  2743. final boolean distinctMultiTouch = mKeyboardSwitcher.hasDistinctMultitouch();
  2744. switch (primaryCode) {
  2745. case Keyboard.KEYCODE_DELETE:
  2746. handleBackspace();
  2747. mDeleteCount++;
  2748. LatinImeLogger.logOnDelete();
  2749. break;
  2750. case Keyboard.KEYCODE_SHIFT:
  2751. handleShift();
  2752. break;
  2753. case Keyboard.KEYCODE_MODE_CHANGE:
  2754. changeKeyboardMode();
  2755. break;
  2756. case Keyboard.KEYCODE_CANCEL:
  2757. if (!isShowingOptionDialog()) {
  2758. handleClose();
  2759. }
  2760. break;
  2761. case LatinKeyboardView.KEYCODE_OPTIONS:
  2762. onOptionKeyPressed();
  2763. break;
  2764. case LatinKeyboardView.KEYCODE_OPTIONS_LONGPRESS:
  2765. onOptionKeyLongPressed();
  2766. break;
  2767. case LatinKeyboardView.KEYCODE_NEXT_LANGUAGE:
  2768. toggleLanguage(false, true);
  2769. break;
  2770. case LatinKeyboardView.KEYCODE_PREV_LANGUAGE:
  2771. toggleLanguage(false, false);
  2772. break;
  2773. case LatinKeyboardView.KEYCODE_VOICE:
  2774. if (VOICE_INSTALLED) {
  2775. startListening(false /*
  2776. * was a button press, was not a swipe
  2777. */);
  2778. }
  2779. break;
  2780. case LatinKeyboardView.KEYCODE_HOME: {
  2781. Intent launcher = new Intent(Intent.ACTION_MAIN, null);
  2782. launcher.addCategory(Intent.CATEGORY_HOME);
  2783. launcher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
  2784. | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
  2785. startActivity(launcher);
  2786. break;
  2787. }
  2788. case LatinKeyboardView.KEYCODE_BACK:
  2789. case LatinKeyboardView.KEYCODE_SEARCH:
  2790. case LatinKeyboardView.KEYCODE_MENU:
  2791. case LatinKeyboardView.KEYCODE_CALL:
  2792. case LatinKeyboardView.KEYCODE_ENDCALL:
  2793. case LatinKeyboardView.KEYCODE_DPAD_UP:
  2794. case LatinKeyboardView.KEYCODE_DPAD_LEFT:
  2795. case LatinKeyboardView.KEYCODE_DPAD_CENTER:
  2796. case LatinKeyboardView.KEYCODE_DPAD_RIGHT:
  2797. case LatinKeyboardView.KEYCODE_DPAD_DOWN:
  2798. final int keyCode = LatinKeyboardView.getKeyCode(primaryCode);
  2799. sendDownUpKeyEventsPreIme(keyCode, 0);
  2800. break;
  2801. case 9 /* Tab */:
  2802. sendDownUpKeyEvents(KeyEvent.KEYCODE_TAB);
  2803. break;
  2804. default:
  2805. if (primaryCode != KEYCODE_ENTER) {
  2806. mJustAddedAutoSpace = false;
  2807. }
  2808. RingCharBuffer.getInstance().push((char) primaryCode, x, y);
  2809. LatinImeLogger.logOnInputChar();
  2810. if (isWordSeparator(primaryCode)) {
  2811. handleSeparator(primaryCode);
  2812. } else {
  2813. handleCharacter(primaryCode, keyCodes);
  2814. }
  2815. // Cancel the just reverted state
  2816. mJustRevertedSeparator = null;
  2817. }
  2818. if (mKeyboardSwitcher.onKey(primaryCode)) {
  2819. changeKeyboardMode();
  2820. }
  2821. // Reset after any single keystroke
  2822. mEnteredText = null;
  2823. }
  2824. @Override
  2825. public void onText(CharSequence text) {
  2826. if (VOICE_INSTALLED && mVoiceInputHighlighted) {
  2827. commitVoiceInput();
  2828. }
  2829. InputConnection ic = getCurrentInputConnection();
  2830. if (ic == null)
  2831. return;
  2832. abortCorrection(false);
  2833. ic.beginBatchEdit();
  2834. if (mPredicting) {
  2835. commitTyped(ic);
  2836. }
  2837. maybeRemovePreviousPeriod(text);
  2838. ic.commitText(text, 1);
  2839. ic.endBatchEdit();
  2840. updateShiftKeyState(getCurrentInputEditorInfo());
  2841. mJustRevertedSeparator = null;
  2842. mJustAddedAutoSpace = false;
  2843. mEnteredText = text;
  2844. }
  2845. @Override
  2846. public void onCancel() {
  2847. // User released a finger outside any key
  2848. }
  2849. };
  2850. }