1 /*
2  * Copyright (C) 2015 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 android.support.car.input;
17 
18 import android.content.Context;
19 import android.text.InputType;
20 import android.util.AttributeSet;
21 import android.view.ActionMode;
22 import android.view.KeyEvent;
23 import android.view.inputmethod.EditorInfo;
24 import android.view.inputmethod.InputConnection;
25 import android.view.inputmethod.InputConnectionWrapper;
26 import android.widget.EditText;
27 import android.widget.TextView;
28 
29 /**
30  * A special EditText for use in-car. This EditText:
31  * <ul>
32  *     <li>Disables selection</li>
33  *     <li>Disables Cut/Copy/Paste</li>
34  *     <li>Force-disables suggestions</li>
35  * </ul>
36  */
37 public class CarRestrictedEditText extends EditText implements CarEditable {
38 
39     private static final boolean SELECTION_CLAMPING_ENABLED = false;
40 
41     private int mLastSelEnd = 0;
42     private int mLastSelStart = 0;
43     private boolean mCursorClamped;
44 
45     private CarEditableListener mCarEditableListener;
46     private KeyListener mListener;
47 
48     public interface KeyListener {
onKeyDown(int keyCode)49         void onKeyDown(int keyCode);
onKeyUp(int keyCode)50         void onKeyUp(int keyCode);
onCommitText(String input)51         void onCommitText(String input);
onCloseKeyboard()52         void onCloseKeyboard();
onDelete()53         void onDelete();
54     }
55 
CarRestrictedEditText(Context context, AttributeSet attrs)56     public CarRestrictedEditText(Context context, AttributeSet attrs) {
57         super(context, attrs);
58         setInputType(getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
59         setTextIsSelectable(false);
60         setSelection(getText().length());
61         mCursorClamped = true;
62         setOnEditorActionListener(new OnEditorActionListener() {
63             @Override
64             public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
65                 if (mListener != null && actionId == EditorInfo.IME_ACTION_DONE) {
66                     mListener.onCloseKeyboard();
67                 }
68                 // Return false because we don't want to hijack the default behavior.
69                 return false;
70             }
71         });
72     }
73 
setKeyListener(KeyListener listener)74     public void setKeyListener(KeyListener listener) {
75         mListener = listener;
76     }
77 
78     @SuppressWarnings("unused")
79     @Override
onSelectionChanged(int selStart, int selEnd)80     protected void onSelectionChanged(int selStart, int selEnd) {
81         if (mCursorClamped && SELECTION_CLAMPING_ENABLED) {
82             setSelection(mLastSelStart, mLastSelEnd);
83             return;
84         }
85         if (mCarEditableListener != null) {
86             mCarEditableListener.onUpdateSelection(mLastSelStart, mLastSelEnd, selStart, selEnd);
87         }
88         mLastSelStart = selStart;
89         mLastSelEnd = selEnd;
90     }
91 
92     @Override
startActionMode(ActionMode.Callback callback)93     public ActionMode startActionMode(ActionMode.Callback callback) {
94         return null;
95     }
96 
97     @Override
setCarEditableListener(CarEditableListener listener)98     public void setCarEditableListener(CarEditableListener listener) {
99         mCarEditableListener = listener;
100     }
101 
102     @Override
setInputEnabled(boolean enabled)103     public void setInputEnabled(boolean enabled) {
104         mCursorClamped = !enabled;
105     }
106 
107     @Override
onCreateInputConnection(EditorInfo outAttrs)108     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
109         InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
110         return new InputConnectionWrapper(inputConnection, false) {
111             @Override
112             public boolean sendKeyEvent(android.view.KeyEvent event) {
113                 if (mListener != null) {
114                     if (event.getAction() == KeyEvent.ACTION_DOWN) {
115                         mListener.onKeyDown(event.getKeyCode());
116                     } else if (event.getAction() == KeyEvent.ACTION_UP) {
117                         mListener.onKeyUp(event.getKeyCode());
118 
119                         // InputMethodService#sendKeyChar doesn't call
120                         // InputConnection#commitText for digit chars.
121                         // TODO: fix projected IME to be in coherence with system IME.
122                         char unicodeChar = (char) event.getUnicodeChar();
123                         if (Character.isDigit(unicodeChar)) {
124                             commitText(String.valueOf(unicodeChar), 1);
125                         }
126                     }
127                     return true;
128                 } else {
129                     return super.sendKeyEvent(event);
130                 }
131             }
132 
133             @Override
134             public boolean commitText(java.lang.CharSequence charSequence, int i) {
135                 if (mListener != null) {
136                     mListener.onCommitText(charSequence.toString());
137                     return true;
138                 }
139                 return super.commitText(charSequence, i);
140             }
141 
142             @Override
143             public boolean deleteSurroundingText(int i, int i1) {
144                 if (mListener != null) {
145                     mListener.onDelete();
146                     return true;
147                 }
148                 return super.deleteSurroundingText(i, i1);
149             }
150         };
151     }
152 }
153