1 /*
2  * Copyright (C) 2006 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 
17 package android.text.method;
18 
19 import android.view.KeyEvent;
20 import android.view.View;
21 import android.text.*;
22 import android.text.method.TextKeyListener.Capitalize;
23 import android.widget.TextView;
24 
25 import java.text.BreakIterator;
26 
27 /**
28  * Abstract base class for key listeners.
29  *
30  * Provides a basic foundation for entering and editing text.
31  * Subclasses should override {@link #onKeyDown} and {@link #onKeyUp} to insert
32  * characters as keys are pressed.
33  * <p></p>
34  * As for all implementations of {@link KeyListener}, this class is only concerned
35  * with hardware keyboards.  Software input methods have no obligation to trigger
36  * the methods in this class.
37  */
38 public abstract class BaseKeyListener extends MetaKeyKeyListener
39         implements KeyListener {
40     /* package */ static final Object OLD_SEL_START = new NoCopySpan.Concrete();
41 
42     /**
43      * Performs the action that happens when you press the {@link KeyEvent#KEYCODE_DEL} key in
44      * a {@link TextView}.  If there is a selection, deletes the selection; otherwise,
45      * deletes the character before the cursor, if any; ALT+DEL deletes everything on
46      * the line the cursor is on.
47      *
48      * @return true if anything was deleted; false otherwise.
49      */
backspace(View view, Editable content, int keyCode, KeyEvent event)50     public boolean backspace(View view, Editable content, int keyCode, KeyEvent event) {
51         return backspaceOrForwardDelete(view, content, keyCode, event, false);
52     }
53 
54     /**
55      * Performs the action that happens when you press the {@link KeyEvent#KEYCODE_FORWARD_DEL}
56      * key in a {@link TextView}.  If there is a selection, deletes the selection; otherwise,
57      * deletes the character before the cursor, if any; ALT+FORWARD_DEL deletes everything on
58      * the line the cursor is on.
59      *
60      * @return true if anything was deleted; false otherwise.
61      */
forwardDelete(View view, Editable content, int keyCode, KeyEvent event)62     public boolean forwardDelete(View view, Editable content, int keyCode, KeyEvent event) {
63         return backspaceOrForwardDelete(view, content, keyCode, event, true);
64     }
65 
backspaceOrForwardDelete(View view, Editable content, int keyCode, KeyEvent event, boolean isForwardDelete)66     private boolean backspaceOrForwardDelete(View view, Editable content, int keyCode,
67             KeyEvent event, boolean isForwardDelete) {
68         // Ensure the key event does not have modifiers except ALT or SHIFT or CTRL.
69         if (!KeyEvent.metaStateHasNoModifiers(event.getMetaState()
70                 & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK | KeyEvent.META_CTRL_MASK))) {
71             return false;
72         }
73 
74         // If there is a current selection, delete it.
75         if (deleteSelection(view, content)) {
76             return true;
77         }
78 
79         // MetaKeyKeyListener doesn't track control key state. Need to check the KeyEvent instead.
80         boolean isCtrlActive = ((event.getMetaState() & KeyEvent.META_CTRL_ON) != 0);
81         boolean isShiftActive = (getMetaState(content, META_SHIFT_ON, event) == 1);
82         boolean isAltActive = (getMetaState(content, META_ALT_ON, event) == 1);
83 
84         if (isCtrlActive) {
85             if (isAltActive || isShiftActive) {
86                 // Ctrl+Alt, Ctrl+Shift, Ctrl+Alt+Shift should not delete any characters.
87                 return false;
88             }
89             return deleteUntilWordBoundary(view, content, isForwardDelete);
90         }
91 
92         // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
93         if (isAltActive && deleteLine(view, content)) {
94             return true;
95         }
96 
97         // Delete a character.
98         final int start = Selection.getSelectionEnd(content);
99         final int end;
100         if (isForwardDelete) {
101             end = TextUtils.getOffsetAfter(content, start);
102         } else {
103             end = TextUtils.getOffsetBefore(content, start);
104         }
105         if (start != end) {
106             content.delete(Math.min(start, end), Math.max(start, end));
107             return true;
108         }
109         return false;
110     }
111 
deleteUntilWordBoundary(View view, Editable content, boolean isForwardDelete)112     private boolean deleteUntilWordBoundary(View view, Editable content, boolean isForwardDelete) {
113         int currentCursorOffset = Selection.getSelectionStart(content);
114 
115         // If there is a selection, do nothing.
116         if (currentCursorOffset != Selection.getSelectionEnd(content)) {
117             return false;
118         }
119 
120         // Early exit if there is no contents to delete.
121         if ((!isForwardDelete && currentCursorOffset == 0) ||
122             (isForwardDelete && currentCursorOffset == content.length())) {
123             return false;
124         }
125 
126         WordIterator wordIterator = null;
127         if (view instanceof TextView) {
128             wordIterator = ((TextView)view).getWordIterator();
129         }
130 
131         if (wordIterator == null) {
132             // Default locale is used for WordIterator since the appropriate locale is not clear
133             // here.
134             // TODO: Use appropriate locale for WordIterator.
135             wordIterator = new WordIterator();
136         }
137 
138         int deleteFrom;
139         int deleteTo;
140 
141         if (isForwardDelete) {
142             deleteFrom = currentCursorOffset;
143             wordIterator.setCharSequence(content, deleteFrom, content.length());
144             deleteTo = wordIterator.following(currentCursorOffset);
145             if (deleteTo == BreakIterator.DONE) {
146                 deleteTo = content.length();
147             }
148         } else {
149             deleteTo = currentCursorOffset;
150             wordIterator.setCharSequence(content, 0, deleteTo);
151             deleteFrom = wordIterator.preceding(currentCursorOffset);
152             if (deleteFrom == BreakIterator.DONE) {
153                 deleteFrom = 0;
154             }
155         }
156         content.delete(deleteFrom, deleteTo);
157         return true;
158     }
159 
deleteSelection(View view, Editable content)160     private boolean deleteSelection(View view, Editable content) {
161         int selectionStart = Selection.getSelectionStart(content);
162         int selectionEnd = Selection.getSelectionEnd(content);
163         if (selectionEnd < selectionStart) {
164             int temp = selectionEnd;
165             selectionEnd = selectionStart;
166             selectionStart = temp;
167         }
168         if (selectionStart != selectionEnd) {
169             content.delete(selectionStart, selectionEnd);
170             return true;
171         }
172         return false;
173     }
174 
deleteLine(View view, Editable content)175     private boolean deleteLine(View view, Editable content) {
176         if (view instanceof TextView) {
177             final Layout layout = ((TextView) view).getLayout();
178             if (layout != null) {
179                 final int line = layout.getLineForOffset(Selection.getSelectionStart(content));
180                 final int start = layout.getLineStart(line);
181                 final int end = layout.getLineEnd(line);
182                 if (end != start) {
183                     content.delete(start, end);
184                     return true;
185                 }
186             }
187         }
188         return false;
189     }
190 
makeTextContentType(Capitalize caps, boolean autoText)191     static int makeTextContentType(Capitalize caps, boolean autoText) {
192         int contentType = InputType.TYPE_CLASS_TEXT;
193         switch (caps) {
194             case CHARACTERS:
195                 contentType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
196                 break;
197             case WORDS:
198                 contentType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
199                 break;
200             case SENTENCES:
201                 contentType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
202                 break;
203         }
204         if (autoText) {
205             contentType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
206         }
207         return contentType;
208     }
209 
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)210     public boolean onKeyDown(View view, Editable content,
211                              int keyCode, KeyEvent event) {
212         boolean handled;
213         switch (keyCode) {
214             case KeyEvent.KEYCODE_DEL:
215                 handled = backspace(view, content, keyCode, event);
216                 break;
217             case KeyEvent.KEYCODE_FORWARD_DEL:
218                 handled = forwardDelete(view, content, keyCode, event);
219                 break;
220             default:
221                 handled = false;
222                 break;
223         }
224 
225         if (handled) {
226             adjustMetaAfterKeypress(content);
227         }
228 
229         return super.onKeyDown(view, content, keyCode, event);
230     }
231 
232     /**
233      * Base implementation handles ACTION_MULTIPLE KEYCODE_UNKNOWN by inserting
234      * the event's text into the content.
235      */
onKeyOther(View view, Editable content, KeyEvent event)236     public boolean onKeyOther(View view, Editable content, KeyEvent event) {
237         if (event.getAction() != KeyEvent.ACTION_MULTIPLE
238                 || event.getKeyCode() != KeyEvent.KEYCODE_UNKNOWN) {
239             // Not something we are interested in.
240             return false;
241         }
242 
243         int selectionStart = Selection.getSelectionStart(content);
244         int selectionEnd = Selection.getSelectionEnd(content);
245         if (selectionEnd < selectionStart) {
246             int temp = selectionEnd;
247             selectionEnd = selectionStart;
248             selectionStart = temp;
249         }
250 
251         CharSequence text = event.getCharacters();
252         if (text == null) {
253             return false;
254         }
255 
256         content.replace(selectionStart, selectionEnd, text);
257         return true;
258     }
259 }
260 
261