1/* 2 * Copyright (C) 2009 Google Inc. 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 17package com.android.inputmethod.voice; 18 19import android.view.inputmethod.ExtractedText; 20import android.view.inputmethod.ExtractedTextRequest; 21import android.view.inputmethod.InputConnection; 22 23/** 24 * Utility methods to deal with editing text through an InputConnection. 25 */ 26public class EditingUtil { 27 private EditingUtil() {}; 28 29 /** 30 * Append newText to the text field represented by connection. 31 * The new text becomes selected. 32 */ 33 public static void appendText(InputConnection connection, String newText) { 34 if (connection == null) { 35 return; 36 } 37 38 // Commit the composing text 39 connection.finishComposingText(); 40 41 // Add a space if the field already has text. 42 CharSequence charBeforeCursor = connection.getTextBeforeCursor(1, 0); 43 if (charBeforeCursor != null 44 && !charBeforeCursor.equals(" ") 45 && (charBeforeCursor.length() > 0)) { 46 newText = " " + newText; 47 } 48 49 connection.setComposingText(newText, 1); 50 } 51 52 private static int getCursorPosition(InputConnection connection) { 53 ExtractedText extracted = connection.getExtractedText( 54 new ExtractedTextRequest(), 0); 55 if (extracted == null) { 56 return -1; 57 } 58 return extracted.startOffset + extracted.selectionStart; 59 } 60 61 private static int getSelectionEnd(InputConnection connection) { 62 ExtractedText extracted = connection.getExtractedText( 63 new ExtractedTextRequest(), 0); 64 if (extracted == null) { 65 return -1; 66 } 67 return extracted.startOffset + extracted.selectionEnd; 68 } 69 70 /** 71 * @param connection connection to the current text field. 72 * @param sep characters which may separate words 73 * @return the word that surrounds the cursor, including up to one trailing 74 * separator. For example, if the field contains "he|llo world", where | 75 * represents the cursor, then "hello " will be returned. 76 */ 77 public static String getWordAtCursor( 78 InputConnection connection, String separators) { 79 Range range = getWordRangeAtCursor(connection, separators); 80 return (range == null) ? null : range.word; 81 } 82 83 /** 84 * Removes the word surrounding the cursor. Parameters are identical to 85 * getWordAtCursor. 86 */ 87 public static void deleteWordAtCursor( 88 InputConnection connection, String separators) { 89 90 Range range = getWordRangeAtCursor(connection, separators); 91 if (range == null) return; 92 93 connection.finishComposingText(); 94 // Move cursor to beginning of word, to avoid crash when cursor is outside 95 // of valid range after deleting text. 96 int newCursor = getCursorPosition(connection) - range.charsBefore; 97 connection.setSelection(newCursor, newCursor); 98 connection.deleteSurroundingText(0, range.charsBefore + range.charsAfter); 99 } 100 101 /** 102 * Represents a range of text, relative to the current cursor position. 103 */ 104 private static class Range { 105 /** Characters before selection start */ 106 int charsBefore; 107 108 /** 109 * Characters after selection start, including one trailing word 110 * separator. 111 */ 112 int charsAfter; 113 114 /** The actual characters that make up a word */ 115 String word; 116 117 public Range(int charsBefore, int charsAfter, String word) { 118 if (charsBefore < 0 || charsAfter < 0) { 119 throw new IndexOutOfBoundsException(); 120 } 121 this.charsBefore = charsBefore; 122 this.charsAfter = charsAfter; 123 this.word = word; 124 } 125 } 126 127 private static Range getWordRangeAtCursor( 128 InputConnection connection, String sep) { 129 if (connection == null || sep == null) { 130 return null; 131 } 132 CharSequence before = connection.getTextBeforeCursor(1000, 0); 133 CharSequence after = connection.getTextAfterCursor(1000, 0); 134 if (before == null || after == null) { 135 return null; 136 } 137 138 // Find first word separator before the cursor 139 int start = before.length(); 140 while (--start > 0 && !isWhitespace(before.charAt(start - 1), sep)); 141 142 // Find last word separator after the cursor 143 int end = -1; 144 while (++end < after.length() && !isWhitespace(after.charAt(end), sep)); 145 if (end < after.length() - 1) { 146 end++; // Include trailing space, if it exists, in word 147 } 148 149 int cursor = getCursorPosition(connection); 150 if (start >= 0 && cursor + end <= after.length() + before.length()) { 151 String word = before.toString().substring(start, before.length()) 152 + after.toString().substring(0, end); 153 return new Range(before.length() - start, end, word); 154 } 155 156 return null; 157 } 158 159 private static boolean isWhitespace(int code, String whitespace) { 160 return whitespace.contains(String.valueOf((char) code)); 161 } 162} 163