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 /**
26  * Abstract base class for key listeners.
27  *
28  * Provides a basic foundation for entering and editing text.
29  * Subclasses should override {@link #onKeyDown} and {@link #onKeyUp} to insert
30  * characters as keys are pressed.
31  * <p></p>
32  * As for all implementations of {@link KeyListener}, this class is only concerned
33  * with hardware keyboards.  Software input methods have no obligation to trigger
34  * the methods in this class.
35  */
36 public abstract class BaseKeyListener extends MetaKeyKeyListener
37         implements KeyListener {
38     /* package */ static final Object OLD_SEL_START = new NoCopySpan.Concrete();
39 
40     /**
41      * Performs the action that happens when you press the {@link KeyEvent#KEYCODE_DEL} key in
42      * a {@link TextView}.  If there is a selection, deletes the selection; otherwise,
43      * deletes the character before the cursor, if any; ALT+DEL deletes everything on
44      * the line the cursor is on.
45      *
46      * @return true if anything was deleted; false otherwise.
47      */
backspace(View view, Editable content, int keyCode, KeyEvent event)48     public boolean backspace(View view, Editable content, int keyCode, KeyEvent event) {
49         return backspaceOrForwardDelete(view, content, keyCode, event, false);
50     }
51 
52     /**
53      * Performs the action that happens when you press the {@link KeyEvent#KEYCODE_FORWARD_DEL}
54      * key in a {@link TextView}.  If there is a selection, deletes the selection; otherwise,
55      * deletes the character before the cursor, if any; ALT+FORWARD_DEL deletes everything on
56      * the line the cursor is on.
57      *
58      * @return true if anything was deleted; false otherwise.
59      */
forwardDelete(View view, Editable content, int keyCode, KeyEvent event)60     public boolean forwardDelete(View view, Editable content, int keyCode, KeyEvent event) {
61         return backspaceOrForwardDelete(view, content, keyCode, event, true);
62     }
63 
backspaceOrForwardDelete(View view, Editable content, int keyCode, KeyEvent event, boolean isForwardDelete)64     private boolean backspaceOrForwardDelete(View view, Editable content, int keyCode,
65             KeyEvent event, boolean isForwardDelete) {
66         // Ensure the key event does not have modifiers except ALT or SHIFT.
67         if (!KeyEvent.metaStateHasNoModifiers(event.getMetaState()
68                 & ~(KeyEvent.META_SHIFT_MASK | KeyEvent.META_ALT_MASK))) {
69             return false;
70         }
71 
72         // If there is a current selection, delete it.
73         if (deleteSelection(view, content)) {
74             return true;
75         }
76 
77         // Alt+Backspace or Alt+ForwardDelete deletes the current line, if possible.
78         if (getMetaState(content, META_ALT_ON, event) == 1) {
79             if (deleteLine(view, content)) {
80                 return true;
81             }
82         }
83 
84         // Delete a character.
85         final int start = Selection.getSelectionEnd(content);
86         final int end;
87         if (isForwardDelete || event.isShiftPressed()
88                 || getMetaState(content, META_SHIFT_ON) == 1) {
89             end = TextUtils.getOffsetAfter(content, start);
90         } else {
91             end = TextUtils.getOffsetBefore(content, start);
92         }
93         if (start != end) {
94             content.delete(Math.min(start, end), Math.max(start, end));
95             return true;
96         }
97         return false;
98     }
99 
deleteSelection(View view, Editable content)100     private boolean deleteSelection(View view, Editable content) {
101         int selectionStart = Selection.getSelectionStart(content);
102         int selectionEnd = Selection.getSelectionEnd(content);
103         if (selectionEnd < selectionStart) {
104             int temp = selectionEnd;
105             selectionEnd = selectionStart;
106             selectionStart = temp;
107         }
108         if (selectionStart != selectionEnd) {
109             content.delete(selectionStart, selectionEnd);
110             return true;
111         }
112         return false;
113     }
114 
deleteLine(View view, Editable content)115     private boolean deleteLine(View view, Editable content) {
116         if (view instanceof TextView) {
117             final Layout layout = ((TextView) view).getLayout();
118             if (layout != null) {
119                 final int line = layout.getLineForOffset(Selection.getSelectionStart(content));
120                 final int start = layout.getLineStart(line);
121                 final int end = layout.getLineEnd(line);
122                 if (end != start) {
123                     content.delete(start, end);
124                     return true;
125                 }
126             }
127         }
128         return false;
129     }
130 
makeTextContentType(Capitalize caps, boolean autoText)131     static int makeTextContentType(Capitalize caps, boolean autoText) {
132         int contentType = InputType.TYPE_CLASS_TEXT;
133         switch (caps) {
134             case CHARACTERS:
135                 contentType |= InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
136                 break;
137             case WORDS:
138                 contentType |= InputType.TYPE_TEXT_FLAG_CAP_WORDS;
139                 break;
140             case SENTENCES:
141                 contentType |= InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
142                 break;
143         }
144         if (autoText) {
145             contentType |= InputType.TYPE_TEXT_FLAG_AUTO_CORRECT;
146         }
147         return contentType;
148     }
149 
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)150     public boolean onKeyDown(View view, Editable content,
151                              int keyCode, KeyEvent event) {
152         boolean handled;
153         switch (keyCode) {
154             case KeyEvent.KEYCODE_DEL:
155                 handled = backspace(view, content, keyCode, event);
156                 break;
157             case KeyEvent.KEYCODE_FORWARD_DEL:
158                 handled = forwardDelete(view, content, keyCode, event);
159                 break;
160             default:
161                 handled = false;
162                 break;
163         }
164 
165         if (handled) {
166             adjustMetaAfterKeypress(content);
167         }
168 
169         return super.onKeyDown(view, content, keyCode, event);
170     }
171 
172     /**
173      * Base implementation handles ACTION_MULTIPLE KEYCODE_UNKNOWN by inserting
174      * the event's text into the content.
175      */
onKeyOther(View view, Editable content, KeyEvent event)176     public boolean onKeyOther(View view, Editable content, KeyEvent event) {
177         if (event.getAction() != KeyEvent.ACTION_MULTIPLE
178                 || event.getKeyCode() != KeyEvent.KEYCODE_UNKNOWN) {
179             // Not something we are interested in.
180             return false;
181         }
182 
183         int selectionStart = Selection.getSelectionStart(content);
184         int selectionEnd = Selection.getSelectionEnd(content);
185         if (selectionEnd < selectionStart) {
186             int temp = selectionEnd;
187             selectionEnd = selectionStart;
188             selectionStart = temp;
189         }
190 
191         CharSequence text = event.getCharacters();
192         if (text == null) {
193             return false;
194         }
195 
196         content.replace(selectionStart, selectionEnd, text);
197         return true;
198     }
199 }
200 
201