/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

Large files are truncated click here to view the full file

  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 di