InputConnection.java 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. /*
  2. * Copyright (C) 2021 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package textinput;
  17. import static android.view.inputmethod.EditorInfo.IME_ACTION_UNSPECIFIED;
  18. import android.app.Activity;
  19. import android.content.Context;
  20. import android.os.Bundle;
  21. import android.text.Editable;
  22. import android.text.InputFilter;
  23. import android.text.Selection;
  24. import android.text.SpannableString;
  25. import android.text.SpannableStringBuilder;
  26. import android.text.Spanned;
  27. import android.text.TextUtils;
  28. import android.util.Log;
  29. import android.view.KeyEvent;
  30. import android.view.View;
  31. import android.view.inputmethod.BaseInputConnection;
  32. import android.view.inputmethod.CompletionInfo;
  33. import android.view.inputmethod.CorrectionInfo;
  34. import android.view.inputmethod.EditorInfo;
  35. import android.view.inputmethod.ExtractedText;
  36. import android.view.inputmethod.ExtractedTextRequest;
  37. import android.view.inputmethod.InputMethodManager;
  38. import android.graphics.Insets;
  39. import android.view.WindowInsets;
  40. import textinput.GameTextInput.Pair;
  41. public class InputConnection extends BaseInputConnection implements View.OnKeyListener {
  42. private final InputMethodManager imm;
  43. private final View targetView;
  44. private final Settings settings;
  45. private final Editable mEditable;
  46. private Listener listener;
  47. private boolean mSoftKeyboardActive;
  48. private void log(String text) {
  49. //Log.d("darkfi", text);
  50. }
  51. private void logEditable(String context) {
  52. try {
  53. int len = mEditable.length();
  54. int selStart = Selection.getSelectionStart(mEditable);
  55. int selEnd = Selection.getSelectionEnd(mEditable);
  56. int cmpStart = getComposingSpanStart(mEditable);
  57. int cmpEnd = getComposingSpanEnd(mEditable);
  58. StringBuilder sb = new StringBuilder(160);
  59. sb.append("EDITABLE [").append(context).append("] ");
  60. sb.append("len=").append(len);
  61. sb.append(" text='").append(mEditable).append("'");
  62. sb.append(" sel=(").append(selStart).append(",").append(selEnd).append(")");
  63. sb.append(" compose=(").append(cmpStart).append(",").append(cmpEnd).append(")");
  64. Object[] spans = mEditable.getSpans(0, len, Object.class);
  65. sb.append(" nspans=").append(spans.length).append(" [");
  66. for (int i = 0; i < spans.length; i++) {
  67. if (i > 0) sb.append(",");
  68. Object s = spans[i];
  69. sb.append(s.getClass().getSimpleName());
  70. sb.append("@[").append(mEditable.getSpanStart(s));
  71. sb.append(",").append(mEditable.getSpanEnd(s)).append("]");
  72. sb.append(":").append(mEditable.getSpanFlags(s));
  73. }
  74. sb.append("]");
  75. Log.i("darkfi", sb.toString());
  76. } catch (Throwable t) {
  77. Log.e("darkfi", "EDITABLE [" + context + "] CORRUPT: "
  78. + t.getClass().getSimpleName() + ": " + t.getMessage());
  79. }
  80. }
  81. /*
  82. * This class filters EOL characters from the input. For details of how InputFilter.filter
  83. * function works, refer to its documentation. If the suggested change is accepted without
  84. * modifications, filter() should return null.
  85. */
  86. private class SingeLineFilter implements InputFilter {
  87. public CharSequence filter(
  88. CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
  89. boolean keepOriginal = true;
  90. StringBuilder builder = new StringBuilder(end - start);
  91. for (int i = start; i < end; i++) {
  92. char c = source.charAt(i);
  93. if (c == '\n') {
  94. keepOriginal = false;
  95. } else {
  96. builder.append(c);
  97. }
  98. }
  99. if (keepOriginal) {
  100. return null;
  101. }
  102. if (source instanceof Spanned) {
  103. SpannableString s = new SpannableString(builder);
  104. TextUtils.copySpansFrom((Spanned) source, start, builder.length(), null, s, 0);
  105. return s;
  106. } else {
  107. return builder;
  108. }
  109. }
  110. }
  111. private static final int MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT = 5000;
  112. /**
  113. * Constructor
  114. *
  115. * @param ctx The app's context
  116. * @param targetView The view created this input connection
  117. * @param settings EditorInfo and other settings needed by this class
  118. * InputConnection.
  119. */
  120. public InputConnection(Context ctx, View targetView, Settings settings) {
  121. super(targetView, settings.mEditorInfo.inputType != 0);
  122. log("InputConnection created");
  123. this.targetView = targetView;
  124. this.settings = settings;
  125. Object imm = ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
  126. if (imm == null) {
  127. throw new java.lang.RuntimeException("Can't get IMM");
  128. } else {
  129. this.imm = (InputMethodManager) imm;
  130. this.mEditable = (Editable) (new SpannableStringBuilder());
  131. }
  132. // Listen for insets changes
  133. targetView.setOnKeyListener(this);
  134. // Apply EditorInfo settings
  135. this.setEditorInfo(settings.mEditorInfo);
  136. }
  137. /**
  138. * Restart the input method manager. This is useful to apply changes to the keyboard
  139. * after calling setEditorInfo.
  140. */
  141. public void restartInput() {
  142. logEditable("restartInput.enter");
  143. imm.restartInput(targetView);
  144. }
  145. /**
  146. * Get whether the soft keyboard is visible.
  147. *
  148. * @return true if the soft keyboard is visible, false otherwise
  149. */
  150. public final boolean getSoftKeyboardActive() {
  151. return this.mSoftKeyboardActive;
  152. }
  153. /**
  154. * Request the soft keyboard to become visible or invisible.
  155. *
  156. * @param active True if the soft keyboard should be made visible, otherwise false.
  157. * @param flags See
  158. * https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#showSoftInput(android.view.View,%20int)
  159. */
  160. public final void setSoftKeyboardActive(boolean active, int flags) {
  161. log("setSoftKeyboardActive, active: " + active);
  162. logEditable("setSoftKeyboardActive.enter");
  163. this.mSoftKeyboardActive = active;
  164. if (active) {
  165. this.targetView.setFocusableInTouchMode(true);
  166. this.targetView.requestFocus();
  167. this.imm.showSoftInput(this.targetView, flags);
  168. } else {
  169. this.imm.hideSoftInputFromWindow(this.targetView.getWindowToken(), flags);
  170. }
  171. logEditable("setSoftKeyboardActive.before_restart");
  172. restartInput();
  173. logEditable("setSoftKeyboardActive.exit");
  174. }
  175. /**
  176. * Get the current EditorInfo used to configure the InputConnection's behaviour.
  177. *
  178. * @return The current EditorInfo.
  179. */
  180. public final EditorInfo getEditorInfo() {
  181. return this.settings.mEditorInfo;
  182. }
  183. /**
  184. * Set the current EditorInfo used to configure the InputConnection's behaviour.
  185. *
  186. * @param editorInfo The EditorInfo to use
  187. */
  188. public final void setEditorInfo(EditorInfo editorInfo) {
  189. log("setEditorInfo");
  190. settings.mEditorInfo = editorInfo;
  191. // Depending on the multiline state, we might need a different set of filters.
  192. // Filters are being used to filter specific characters for hardware keyboards
  193. // (software input methods already support TYPE_TEXT_FLAG_MULTI_LINE).
  194. if ((settings.mEditorInfo.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0) {
  195. mEditable.setFilters(
  196. new InputFilter[] {new InputFilter.LengthFilter(MAX_LENGTH_FOR_SINGLE_LINE_EDIT_TEXT),
  197. new SingeLineFilter()});
  198. } else {
  199. mEditable.setFilters(new InputFilter[] {});
  200. }
  201. }
  202. /**
  203. * Set the input type for the IME.
  204. *
  205. * @param inputType The input type to use.
  206. */
  207. public final void setInputType(int inputType) {
  208. log("setInputType: " + inputType);
  209. logEditable("setInputType.enter");
  210. settings.mEditorInfo.inputType = inputType;
  211. logEditable("setInputType.before_restart");
  212. restartInput();
  213. logEditable("setInputType.exit");
  214. }
  215. /**
  216. * Set the text, selection and composing region state.
  217. *
  218. * @param state The state to be used by the IME.
  219. * This replaces any text, selections and composing regions currently active.
  220. */
  221. public final void setState(State state) {
  222. if (state == null)
  223. return;
  224. log(
  225. "setState: '" + state.text + "', selection=(" + state.selectionStart + ","
  226. + state.selectionEnd + "), composing region=(" + state.composingRegionStart + ","
  227. + state.composingRegionEnd + ")");
  228. logEditable("setState.enter");
  229. mEditable.clear();
  230. logEditable("setState.after_clear");
  231. mEditable.clearSpans();
  232. logEditable("setState.after_clearSpans");
  233. mEditable.insert(0, (CharSequence) state.text);
  234. logEditable("setState.after_insert");
  235. setSelection(state.selectionStart, state.selectionEnd);
  236. logEditable("setState.after_setSelection");
  237. if (state.composingRegionStart != state.composingRegionEnd) {
  238. setComposingRegion(state.composingRegionStart, state.composingRegionEnd);
  239. logEditable("setState.after_setComposingRegion");
  240. }
  241. restartInput();
  242. logEditable("setState.exit");
  243. }
  244. /**
  245. * Get the current listener for state changes.
  246. *
  247. * @return The current Listener
  248. */
  249. public final Listener getListener() {
  250. return listener;
  251. }
  252. /**
  253. * Set a listener for state changes.
  254. *
  255. * @param listener
  256. * @return This InputConnection, for setter chaining.
  257. */
  258. public final InputConnection setListener(Listener listener) {
  259. this.listener = listener;
  260. return this;
  261. }
  262. // From View.OnKeyListener
  263. @Override
  264. public boolean onKey(View view, int i, KeyEvent keyEvent) {
  265. log("onKey: " + keyEvent);
  266. if (!getSoftKeyboardActive()) {
  267. return false;
  268. }
  269. // Don't call sendKeyEvent as it might produce an infinite loop.
  270. if (processKeyEvent(keyEvent)) {
  271. // IMM seems to cache the content of Editable, so we update it with restartInput
  272. // Also it caches selection and composing region, so let's notify it about updates.
  273. logEditable("onKey.after_processKeyEvent");
  274. stateUpdated();
  275. logEditable("onKey.after_stateUpdated");
  276. immUpdateSelection();
  277. logEditable("onKey.after_immUpdateSelection");
  278. restartInput();
  279. logEditable("onKey.after_restart");
  280. return true;
  281. }
  282. return false;
  283. }
  284. // From BaseInputConnection
  285. @Override
  286. public Editable getEditable() {
  287. log("getEditable");
  288. return mEditable;
  289. }
  290. // From BaseInputConnection
  291. @Override
  292. public boolean setSelection(int start, int end) {
  293. log("setSelection: " + start + ":" + end);
  294. logEditable("setSelection.enter");
  295. boolean r = super.setSelection(start, end);
  296. logEditable("setSelection.exit");
  297. return r;
  298. }
  299. // From BaseInputConnection
  300. @Override
  301. public boolean setComposingText(CharSequence text, int newCursorPosition) {
  302. log(
  303. "setComposingText='" + text + "' newCursorPosition=" + newCursorPosition);
  304. if (text == null) {
  305. return false;
  306. }
  307. logEditable("setComposingText.enter");
  308. boolean r = super.setComposingText(text, newCursorPosition);
  309. logEditable("setComposingText.exit");
  310. return r;
  311. }
  312. @Override
  313. public boolean setComposingRegion(int start, int end) {
  314. log("setComposingRegion: " + start + ":" + end);
  315. logEditable("setComposingRegion.enter");
  316. boolean r = super.setComposingRegion(start, end);
  317. logEditable("setComposingRegion.exit");
  318. return r;
  319. }
  320. // From BaseInputConnection
  321. @Override
  322. public boolean finishComposingText() {
  323. log("finishComposingText");
  324. logEditable("finishComposingText.enter");
  325. return super.finishComposingText();
  326. }
  327. @Override
  328. public boolean endBatchEdit() {
  329. log("endBatchEdit");
  330. logEditable("endBatchEdit.enter");
  331. stateUpdated();
  332. boolean r = super.endBatchEdit();
  333. logEditable("endBatchEdit.exit");
  334. return r;
  335. }
  336. @Override
  337. public boolean commitCompletion(CompletionInfo text) {
  338. log("commitCompletion");
  339. return super.commitCompletion(text);
  340. }
  341. @Override
  342. public boolean commitCorrection(CorrectionInfo text) {
  343. log("commitCompletion");
  344. return super.commitCorrection(text);
  345. }
  346. // From BaseInputConnection
  347. @Override
  348. public boolean commitText(CharSequence text, int newCursorPosition) {
  349. log(
  350. (new StringBuilder())
  351. .append("commitText: ")
  352. .append(text)
  353. .append(", new pos = ")
  354. .append(newCursorPosition)
  355. .toString());
  356. logEditable("commitText.enter");
  357. boolean r = super.commitText(text, newCursorPosition);
  358. logEditable("commitText.exit");
  359. return r;
  360. }
  361. // From BaseInputConnection
  362. @Override
  363. public boolean deleteSurroundingText(int beforeLength, int afterLength) {
  364. log("deleteSurroundingText: " + beforeLength + ":" + afterLength);
  365. logEditable("deleteSurroundingText.enter");
  366. boolean r = super.deleteSurroundingText(beforeLength, afterLength);
  367. logEditable("deleteSurroundingText.exit");
  368. return r;
  369. }
  370. // From BaseInputConnection
  371. @Override
  372. public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
  373. log("deleteSurroundingTextInCodePoints: " + beforeLength + ":" + afterLength);
  374. logEditable("deleteSurroundingTextInCodePoints.enter");
  375. boolean r = super.deleteSurroundingTextInCodePoints(beforeLength, afterLength);
  376. logEditable("deleteSurroundingTextInCodePoints.exit");
  377. return r;
  378. }
  379. // From BaseInputConnection
  380. @Override
  381. public boolean sendKeyEvent(KeyEvent event) {
  382. log("sendKeyEvent: " + event);
  383. logEditable("sendKeyEvent.enter");
  384. boolean r = super.sendKeyEvent(event);
  385. logEditable("sendKeyEvent.exit");
  386. return r;
  387. }
  388. // From BaseInputConnection
  389. @Override
  390. public CharSequence getSelectedText(int flags) {
  391. CharSequence result = super.getSelectedText(flags);
  392. if (result == null) {
  393. result = "";
  394. }
  395. log("getSelectedText: " + flags + ", result: " + result);
  396. return result;
  397. }
  398. // From BaseInputConnection
  399. @Override
  400. public CharSequence getTextAfterCursor(int length, int flags) {
  401. log("getTextAfterCursor: " + length + ":" + flags);
  402. if (length < 0) {
  403. Log.i("darkfi", "getTextAfterCursor: returning null to due to an invalid length=" + length);
  404. return null;
  405. }
  406. return super.getTextAfterCursor(length, flags);
  407. }
  408. // From BaseInputConnection
  409. @Override
  410. public CharSequence getTextBeforeCursor(int length, int flags) {
  411. log("getTextBeforeCursor: " + length + ", flags=" + flags);
  412. if (length < 0) {
  413. Log.i("darkfi", "getTextBeforeCursor: returning null to due to an invalid length=" + length);
  414. return null;
  415. }
  416. return super.getTextBeforeCursor(length, flags);
  417. }
  418. // From BaseInputConnection
  419. @Override
  420. public boolean requestCursorUpdates(int cursorUpdateMode) {
  421. log("Request cursor updates: " + cursorUpdateMode);
  422. return super.requestCursorUpdates(cursorUpdateMode);
  423. }
  424. // From BaseInputConnection
  425. @Override
  426. public void closeConnection() {
  427. log("closeConnection");
  428. logEditable("closeConnection.enter");
  429. super.closeConnection();
  430. }
  431. @Override
  432. public boolean setImeConsumesInput(boolean imeConsumesInput) {
  433. log("setImeConsumesInput: " + imeConsumesInput);
  434. return super.setImeConsumesInput(imeConsumesInput);
  435. }
  436. @Override
  437. public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
  438. log("getExtractedText");
  439. return super.getExtractedText(request, flags);
  440. }
  441. @Override
  442. public boolean performPrivateCommand(String action, Bundle data) {
  443. log("performPrivateCommand");
  444. return super.performPrivateCommand(action, data);
  445. }
  446. private void immUpdateSelection() {
  447. Pair selection = this.getSelection();
  448. Pair cr = this.getComposingRegion();
  449. log(
  450. "immUpdateSelection: " + selection.first + "," + selection.second + ". " + cr.first + ","
  451. + cr.second);
  452. settings.mEditorInfo.initialSelStart = selection.first;
  453. settings.mEditorInfo.initialSelEnd = selection.second;
  454. imm.updateSelection(targetView, selection.first, selection.second, cr.first, cr.second);
  455. }
  456. private Pair getSelection() {
  457. return new Pair(Selection.getSelectionStart(mEditable), Selection.getSelectionEnd(mEditable));
  458. }
  459. private Pair getComposingRegion() {
  460. return new Pair(getComposingSpanStart(mEditable), getComposingSpanEnd(mEditable));
  461. }
  462. private boolean processKeyEvent(KeyEvent event) {
  463. if (event == null) {
  464. return false;
  465. }
  466. int keyCode = event.getKeyCode();
  467. log(
  468. "processKeyEvent(key=" + keyCode + ") text=" + this.mEditable.toString());
  469. // Filter out Enter keys if multi-line mode is disabled.
  470. if ((settings.mEditorInfo.inputType & EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) == 0
  471. && (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_NUMPAD_ENTER)
  472. && event.hasNoModifiers()) {
  473. sendEditorAction(settings.mEditorInfo.actionId);
  474. return true;
  475. }
  476. if (event.getAction() != KeyEvent.ACTION_DOWN) {
  477. return false;
  478. }
  479. // If no selection is set, move the selection to the end.
  480. // This is the case when first typing on keys when the selection is not set.
  481. // Note that for InputType.TYPE_CLASS_TEXT, this is not be needed because the
  482. // selection is set in setComposingText.
  483. Pair selection = this.getSelection();
  484. if (selection.first == -1) {
  485. selection.first = this.mEditable.length();
  486. selection.second = this.mEditable.length();
  487. }
  488. if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
  489. if (selection.first == selection.second) {
  490. int newIndex = findIndexBackward(mEditable, selection.first, 1);
  491. setSelection(newIndex, newIndex);
  492. } else {
  493. setSelection(selection.first, selection.first);
  494. }
  495. return true;
  496. }
  497. if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
  498. if (selection.first == selection.second) {
  499. int newIndex = findIndexForward(mEditable, selection.second, 1);
  500. setSelection(newIndex, newIndex);
  501. } else {
  502. setSelection(selection.second, selection.second);
  503. }
  504. return true;
  505. }
  506. if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) {
  507. setSelection(0, 0);
  508. return true;
  509. }
  510. if (keyCode == KeyEvent.KEYCODE_MOVE_END) {
  511. setSelection(this.mEditable.length(), this.mEditable.length());
  512. return true;
  513. }
  514. if (keyCode == KeyEvent.KEYCODE_DEL || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
  515. if (selection.first != selection.second) {
  516. this.mEditable.delete(selection.first, selection.second);
  517. return true;
  518. }
  519. if (keyCode == KeyEvent.KEYCODE_DEL) {
  520. if (selection.first > 0) {
  521. finishComposingText();
  522. deleteSurroundingTextInCodePoints(1, 0);
  523. return true;
  524. }
  525. }
  526. if (keyCode == KeyEvent.KEYCODE_FORWARD_DEL) {
  527. if (selection.first < this.mEditable.length()) {
  528. finishComposingText();
  529. deleteSurroundingTextInCodePoints(0, 1);
  530. return true;
  531. }
  532. }
  533. return false;
  534. }
  535. if (event.getUnicodeChar() == 0) {
  536. return false;
  537. }
  538. if (selection.first != selection.second) {
  539. log("processKeyEvent: deleting selection");
  540. this.mEditable.delete(selection.first, selection.second);
  541. }
  542. String charsToInsert = Character.toString((char) event.getUnicodeChar());
  543. this.mEditable.insert(selection.first, (CharSequence) charsToInsert);
  544. int length = this.mEditable.length();
  545. // Same logic as in setComposingText(): we must update composing region,
  546. // so make sure it points to a valid range.
  547. Pair composingRegion = this.getComposingRegion();
  548. if (composingRegion.first == -1) {
  549. composingRegion = this.getSelection();
  550. if (composingRegion.first == -1) {
  551. composingRegion = new Pair(0, 0);
  552. }
  553. }
  554. composingRegion.second = composingRegion.first + length;
  555. this.setComposingRegion(composingRegion.first, composingRegion.second);
  556. int new_cursor = selection.first + charsToInsert.length();
  557. setSelection(new_cursor, new_cursor);
  558. log("processKeyEvent: exit, text=" + this.mEditable.toString());
  559. return true;
  560. }
  561. private final void stateUpdated() {
  562. Pair selection = this.getSelection();
  563. Pair cr = this.getComposingRegion();
  564. State state = new State(
  565. this.mEditable.toString(), selection.first, selection.second, cr.first, cr.second);
  566. settings.mEditorInfo.initialSelStart = selection.first;
  567. settings.mEditorInfo.initialSelEnd = selection.second;
  568. // Keep a reference to the listener to avoid a race condition when setting the listener.
  569. Listener listener = this.listener;
  570. // We always propagate state change events because unfortunately keyboard visibility functions
  571. // are unreliable, and text editor logic should not depend on them.
  572. if (listener != null) {
  573. listener.stateChanged(state, /*dismissed=*/false);
  574. }
  575. }
  576. /**
  577. * Get the current IME insets.
  578. *
  579. * @return The current IME insets
  580. */
  581. public Insets getImeInsets() {
  582. if (this.targetView == null) {
  583. return Insets.NONE;
  584. }
  585. WindowInsets insets = this.targetView.getRootWindowInsets();
  586. if (insets == null) {
  587. return Insets.NONE;
  588. }
  589. return insets.getInsets(WindowInsets.Type.ime());
  590. }
  591. /**
  592. * Returns true if software keyboard is visible, false otherwise.
  593. *
  594. * @return whether software IME is visible or not.
  595. */
  596. public boolean isSoftwareKeyboardVisible() {
  597. if (this.targetView == null) {
  598. return false;
  599. }
  600. WindowInsets insets = this.targetView.getRootWindowInsets();
  601. if (insets == null) {
  602. return false;
  603. }
  604. return insets.isVisible(WindowInsets.Type.ime());
  605. }
  606. /**
  607. * This is an event handler from InputConnection interface.
  608. * It's called when action button is triggered (typically this means Enter was pressed).
  609. *
  610. * @param action Action code, either one from EditorInfo.imeOptions or a custom one.
  611. * @return Returns true on success, false if the input connection is no longer valid.
  612. */
  613. @Override
  614. public boolean performEditorAction(int action) {
  615. log("performEditorAction, action=" + action);
  616. if (action == IME_ACTION_UNSPECIFIED) {
  617. // Super emulates Enter key press/release
  618. return super.performEditorAction(action);
  619. }
  620. return sendEditorAction(action);
  621. }
  622. /**
  623. * Delivers editor action to listener
  624. *
  625. * @param action Action code, either one from EditorInfo.imeOptions or a custom one.
  626. * @return Returns true on success, false if the input connection is no longer valid.
  627. */
  628. private boolean sendEditorAction(int action) {
  629. Listener listener = this.listener;
  630. if (listener != null) {
  631. listener.onEditorAction(action);
  632. return true;
  633. }
  634. return false;
  635. }
  636. private static int INVALID_INDEX = -1;
  637. // Implementation copy from BaseInputConnection
  638. private static int findIndexBackward(
  639. final CharSequence cs, final int from, final int numCodePoints) {
  640. int currentIndex = from;
  641. boolean waitingHighSurrogate = false;
  642. final int N = cs.length();
  643. if (currentIndex < 0 || N < currentIndex) {
  644. return INVALID_INDEX; // The starting point is out of range.
  645. }
  646. if (numCodePoints < 0) {
  647. return INVALID_INDEX; // Basically this should not happen.
  648. }
  649. int remainingCodePoints = numCodePoints;
  650. while (true) {
  651. if (remainingCodePoints == 0) {
  652. return currentIndex; // Reached to the requested length in code points.
  653. }
  654. --currentIndex;
  655. if (currentIndex < 0) {
  656. if (waitingHighSurrogate) {
  657. return INVALID_INDEX; // An invalid surrogate pair is found.
  658. }
  659. return 0; // Reached to the beginning of the text w/o any invalid surrogate pair.
  660. }
  661. final char c = cs.charAt(currentIndex);
  662. if (waitingHighSurrogate) {
  663. if (!java.lang.Character.isHighSurrogate(c)) {
  664. return INVALID_INDEX; // An invalid surrogate pair is found.
  665. }
  666. waitingHighSurrogate = false;
  667. --remainingCodePoints;
  668. continue;
  669. }
  670. if (!java.lang.Character.isSurrogate(c)) {
  671. --remainingCodePoints;
  672. continue;
  673. }
  674. if (java.lang.Character.isHighSurrogate(c)) {
  675. return INVALID_INDEX; // A invalid surrogate pair is found.
  676. }
  677. waitingHighSurrogate = true;
  678. }
  679. }
  680. // Implementation copy from BaseInputConnection
  681. private static int findIndexForward(
  682. final CharSequence cs, final int from, final int numCodePoints) {
  683. int currentIndex = from;
  684. boolean waitingLowSurrogate = false;
  685. final int N = cs.length();
  686. if (currentIndex < 0 || N < currentIndex) {
  687. return INVALID_INDEX; // The starting point is out of range.
  688. }
  689. if (numCodePoints < 0) {
  690. return INVALID_INDEX; // Basically this should not happen.
  691. }
  692. int remainingCodePoints = numCodePoints;
  693. while (true) {
  694. if (remainingCodePoints == 0) {
  695. return currentIndex; // Reached to the requested length in code points.
  696. }
  697. if (currentIndex >= N) {
  698. if (waitingLowSurrogate) {
  699. return INVALID_INDEX; // An invalid surrogate pair is found.
  700. }
  701. return N; // Reached to the end of the text w/o any invalid surrogate pair.
  702. }
  703. final char c = cs.charAt(currentIndex);
  704. if (waitingLowSurrogate) {
  705. if (!java.lang.Character.isLowSurrogate(c)) {
  706. return INVALID_INDEX; // An invalid surrogate pair is found.
  707. }
  708. --remainingCodePoints;
  709. waitingLowSurrogate = false;
  710. ++currentIndex;
  711. continue;
  712. }
  713. if (!java.lang.Character.isSurrogate(c)) {
  714. --remainingCodePoints;
  715. ++currentIndex;
  716. continue;
  717. }
  718. if (java.lang.Character.isLowSurrogate(c)) {
  719. return INVALID_INDEX; // A invalid surrogate pair is found.
  720. }
  721. waitingLowSurrogate = true;
  722. ++currentIndex;
  723. }
  724. }
  725. }