blob: 7f494aa61c8401b870f50fdf0610842086247e3c [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
2 * Copyright (C) 2013 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
17package com.android.incallui;
18
19import android.content.Context;
20import android.os.Bundle;
21import android.text.Editable;
22import android.text.method.DialerKeyListener;
23import android.util.ArrayMap;
24import android.util.AttributeSet;
25import android.view.KeyEvent;
26import android.view.LayoutInflater;
27import android.view.View;
28import android.view.View.OnClickListener;
29import android.view.View.OnKeyListener;
30import android.view.ViewGroup;
31import android.widget.EditText;
32import android.widget.LinearLayout;
33import android.widget.TextView;
34import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
35import com.android.dialer.dialpadview.DialpadKeyButton;
36import com.android.dialer.dialpadview.DialpadKeyButton.OnPressedListener;
37import com.android.dialer.dialpadview.DialpadView;
38import com.android.incallui.DialpadPresenter.DialpadUi;
39import com.android.incallui.baseui.BaseFragment;
40import java.util.Map;
41
42/** Fragment for call control buttons */
43public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadUi>
44 implements DialpadUi, OnKeyListener, OnClickListener, OnPressedListener {
45
46 /** Hash Map to map a view id to a character */
47 private static final Map<Integer, Character> mDisplayMap = new ArrayMap<>();
48
49 /** Set up the static maps */
50 static {
51 // Map the buttons to the display characters
52 mDisplayMap.put(R.id.one, '1');
53 mDisplayMap.put(R.id.two, '2');
54 mDisplayMap.put(R.id.three, '3');
55 mDisplayMap.put(R.id.four, '4');
56 mDisplayMap.put(R.id.five, '5');
57 mDisplayMap.put(R.id.six, '6');
58 mDisplayMap.put(R.id.seven, '7');
59 mDisplayMap.put(R.id.eight, '8');
60 mDisplayMap.put(R.id.nine, '9');
61 mDisplayMap.put(R.id.zero, '0');
62 mDisplayMap.put(R.id.pound, '#');
63 mDisplayMap.put(R.id.star, '*');
64 }
65
66 private final int[] mButtonIds =
67 new int[] {
68 R.id.zero,
69 R.id.one,
70 R.id.two,
71 R.id.three,
72 R.id.four,
73 R.id.five,
74 R.id.six,
75 R.id.seven,
76 R.id.eight,
77 R.id.nine,
78 R.id.star,
79 R.id.pound
80 };
81 private EditText mDtmfDialerField;
82 // KeyListener used with the "dialpad digits" EditText widget.
83 private DTMFKeyListener mDialerKeyListener;
84 private DialpadView mDialpadView;
85 private int mCurrentTextColor;
86
87 @Override
88 public void onClick(View v) {
89 if (v.getId() == R.id.dialpad_back) {
90 getActivity().onBackPressed();
91 }
92 }
93
94 @Override
95 public boolean onKey(View v, int keyCode, KeyEvent event) {
96 Log.d(this, "onKey: keyCode " + keyCode + ", view " + v);
97
98 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) {
99 int viewId = v.getId();
100 if (mDisplayMap.containsKey(viewId)) {
101 switch (event.getAction()) {
102 case KeyEvent.ACTION_DOWN:
103 if (event.getRepeatCount() == 0) {
104 getPresenter().processDtmf(mDisplayMap.get(viewId));
105 }
106 break;
107 case KeyEvent.ACTION_UP:
108 getPresenter().stopDtmf();
109 break;
110 }
111 // do not return true [handled] here, since we want the
112 // press / click animation to be handled by the framework.
113 }
114 }
115 return false;
116 }
117
118 @Override
119 public DialpadPresenter createPresenter() {
120 return new DialpadPresenter();
121 }
122
123 @Override
124 public DialpadPresenter.DialpadUi getUi() {
125 return this;
126 }
127
128 // TODO Adds hardware keyboard listener
129
130 @Override
131 public View onCreateView(
132 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
133 final View parent = inflater.inflate(R.layout.incall_dialpad_fragment, container, false);
134 mDialpadView = (DialpadView) parent.findViewById(R.id.dialpad_view);
135 mDialpadView.setCanDigitsBeEdited(false);
136 mDialpadView.setBackgroundResource(R.color.incall_dialpad_background);
137 mDtmfDialerField = (EditText) parent.findViewById(R.id.digits);
138 if (mDtmfDialerField != null) {
139 mDialerKeyListener = new DTMFKeyListener();
140 mDtmfDialerField.setKeyListener(mDialerKeyListener);
141 // remove the long-press context menus that support
142 // the edit (copy / paste / select) functions.
143 mDtmfDialerField.setLongClickable(false);
144 mDtmfDialerField.setElegantTextHeight(false);
145 configureKeypadListeners();
146 }
147 View backButton = mDialpadView.findViewById(R.id.dialpad_back);
148 backButton.setVisibility(View.VISIBLE);
149 backButton.setOnClickListener(this);
150
151 return parent;
152 }
153
154 @Override
155 public void onResume() {
156 super.onResume();
157 updateColors();
158 }
159
160 public void updateColors() {
161 int textColor = InCallPresenter.getInstance().getThemeColorManager().getPrimaryColor();
162
163 if (mCurrentTextColor == textColor) {
164 return;
165 }
166
167 DialpadKeyButton dialpadKey;
168 for (int i = 0; i < mButtonIds.length; i++) {
169 dialpadKey = (DialpadKeyButton) mDialpadView.findViewById(mButtonIds[i]);
170 ((TextView) dialpadKey.findViewById(R.id.dialpad_key_number)).setTextColor(textColor);
171 }
172
173 mCurrentTextColor = textColor;
174 }
175
176 @Override
177 public void onDestroyView() {
178 mDialerKeyListener = null;
179 super.onDestroyView();
180 }
181
182 /**
183 * Getter for Dialpad text.
184 *
185 * @return String containing current Dialpad EditText text.
186 */
187 public String getDtmfText() {
188 return mDtmfDialerField.getText().toString();
189 }
190
191 /**
192 * Sets the Dialpad text field with some text.
193 *
194 * @param text Text to set Dialpad EditText to.
195 */
196 public void setDtmfText(String text) {
197 mDtmfDialerField.setText(PhoneNumberUtilsCompat.createTtsSpannable(text));
198 }
199
200 @Override
201 public void setVisible(boolean on) {
202 if (on) {
203 getView().setVisibility(View.VISIBLE);
204 } else {
205 getView().setVisibility(View.INVISIBLE);
206 }
207 }
208
209 /** Starts the slide up animation for the Dialpad keys when the Dialpad is revealed. */
210 public void animateShowDialpad() {
211 final DialpadView dialpadView = (DialpadView) getView().findViewById(R.id.dialpad_view);
212 dialpadView.animateShow();
213 }
214
215 @Override
216 public void appendDigitsToField(char digit) {
217 if (mDtmfDialerField != null) {
218 // TODO: maybe *don't* manually append this digit if
219 // mDialpadDigits is focused and this key came from the HW
220 // keyboard, since in that case the EditText field will
221 // get the key event directly and automatically appends
222 // whetever the user types.
223 // (Or, a cleaner fix would be to just make mDialpadDigits
224 // *not* handle HW key presses. That seems to be more
225 // complicated than just setting focusable="false" on it,
226 // though.)
227 mDtmfDialerField.getText().append(digit);
228 }
229 }
230
231 /** Called externally (from InCallScreen) to play a DTMF Tone. */
232 /* package */ boolean onDialerKeyDown(KeyEvent event) {
233 Log.d(this, "Notifying dtmf key down.");
234 if (mDialerKeyListener != null) {
235 return mDialerKeyListener.onKeyDown(event);
236 } else {
237 return false;
238 }
239 }
240
241 /** Called externally (from InCallScreen) to cancel the last DTMF Tone played. */
242 public boolean onDialerKeyUp(KeyEvent event) {
243 Log.d(this, "Notifying dtmf key up.");
244 if (mDialerKeyListener != null) {
245 return mDialerKeyListener.onKeyUp(event);
246 } else {
247 return false;
248 }
249 }
250
251 private void configureKeypadListeners() {
252 DialpadKeyButton dialpadKey;
253 for (int i = 0; i < mButtonIds.length; i++) {
254 dialpadKey = (DialpadKeyButton) mDialpadView.findViewById(mButtonIds[i]);
255 dialpadKey.setOnKeyListener(this);
256 dialpadKey.setOnClickListener(this);
257 dialpadKey.setOnPressedListener(this);
258 }
259 }
260
261 @Override
262 public void onPressed(View view, boolean pressed) {
263 if (pressed && mDisplayMap.containsKey(view.getId())) {
264 Log.d(this, "onPressed: " + pressed + " " + mDisplayMap.get(view.getId()));
265 getPresenter().processDtmf(mDisplayMap.get(view.getId()));
266 }
267 if (!pressed) {
268 Log.d(this, "onPressed: " + pressed);
269 getPresenter().stopDtmf();
270 }
271 }
272
273 /**
274 * LinearLayout with getter and setter methods for the translationY property using floats, for
275 * animation purposes.
276 */
277 public static class DialpadSlidingLinearLayout extends LinearLayout {
278
279 public DialpadSlidingLinearLayout(Context context) {
280 super(context);
281 }
282
283 public DialpadSlidingLinearLayout(Context context, AttributeSet attrs) {
284 super(context, attrs);
285 }
286
287 public DialpadSlidingLinearLayout(Context context, AttributeSet attrs, int defStyle) {
288 super(context, attrs, defStyle);
289 }
290
291 public float getYFraction() {
292 final int height = getHeight();
293 if (height == 0) {
294 return 0;
295 }
296 return getTranslationY() / height;
297 }
298
299 public void setYFraction(float yFraction) {
300 setTranslationY(yFraction * getHeight());
301 }
302 }
303
304 /**
305 * Our own key listener, specialized for dealing with DTMF codes. 1. Ignore the backspace since it
306 * is irrelevant. 2. Allow ONLY valid DTMF characters to generate a tone and be sent as a DTMF
307 * code. 3. All other remaining characters are handled by the superclass.
308 *
309 * <p>This code is purely here to handle events from the hardware keyboard while the DTMF dialpad
310 * is up.
311 */
312 private class DTMFKeyListener extends DialerKeyListener {
313
314 /**
315 * Overrides the characters used in {@link DialerKeyListener#CHARACTERS} These are the valid
316 * dtmf characters.
317 */
318 public final char[] DTMF_CHARACTERS =
319 new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'};
320
321 private DTMFKeyListener() {
322 super();
323 }
324
325 /** Overriden to return correct DTMF-dialable characters. */
326 @Override
327 protected char[] getAcceptedChars() {
328 return DTMF_CHARACTERS;
329 }
330
331 /** special key listener ignores backspace. */
332 @Override
333 public boolean backspace(View view, Editable content, int keyCode, KeyEvent event) {
334 return false;
335 }
336
337 /**
338 * Overriden so that with each valid button press, we start sending a dtmf code and play a local
339 * dtmf tone.
340 */
341 @Override
342 public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) {
343 // if (DBG) log("DTMFKeyListener.onKeyDown, keyCode " + keyCode + ", view " + view);
344
345 // find the character
346 char c = (char) lookup(event, content);
347
348 // if not a long press, and parent onKeyDown accepts the input
349 if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) {
350
351 boolean keyOK = ok(getAcceptedChars(), c);
352
353 // if the character is a valid dtmf code, start playing the tone and send the
354 // code.
355 if (keyOK) {
356 Log.d(this, "DTMFKeyListener reading '" + c + "' from input.");
357 getPresenter().processDtmf(c);
358 } else {
359 Log.d(this, "DTMFKeyListener rejecting '" + c + "' from input.");
360 }
361 return true;
362 }
363 return false;
364 }
365
366 /**
367 * Overriden so that with each valid button up, we stop sending a dtmf code and the dtmf tone.
368 */
369 @Override
370 public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) {
371 // if (DBG) log("DTMFKeyListener.onKeyUp, keyCode " + keyCode + ", view " + view);
372
373 super.onKeyUp(view, content, keyCode, event);
374
375 // find the character
376 char c = (char) lookup(event, content);
377
378 boolean keyOK = ok(getAcceptedChars(), c);
379
380 if (keyOK) {
381 Log.d(this, "Stopping the tone for '" + c + "'");
382 getPresenter().stopDtmf();
383 return true;
384 }
385
386 return false;
387 }
388
389 /** Handle individual keydown events when we DO NOT have an Editable handy. */
390 public boolean onKeyDown(KeyEvent event) {
391 char c = lookup(event);
392 Log.d(this, "DTMFKeyListener.onKeyDown: event '" + c + "'");
393
394 // if not a long press, and parent onKeyDown accepts the input
395 if (event.getRepeatCount() == 0 && c != 0) {
396 // if the character is a valid dtmf code, start playing the tone and send the
397 // code.
398 if (ok(getAcceptedChars(), c)) {
399 Log.d(this, "DTMFKeyListener reading '" + c + "' from input.");
400 getPresenter().processDtmf(c);
401 return true;
402 } else {
403 Log.d(this, "DTMFKeyListener rejecting '" + c + "' from input.");
404 }
405 }
406 return false;
407 }
408
409 /**
410 * Handle individual keyup events.
411 *
412 * @param event is the event we are trying to stop. If this is null, then we just force-stop the
413 * last tone without checking if the event is an acceptable dialer event.
414 */
415 public boolean onKeyUp(KeyEvent event) {
416 if (event == null) {
417 //the below piece of code sends stopDTMF event unnecessarily even when a null event
418 //is received, hence commenting it.
419 /*if (DBG) log("Stopping the last played tone.");
420 stopTone();*/
421 return true;
422 }
423
424 char c = lookup(event);
425 Log.d(this, "DTMFKeyListener.onKeyUp: event '" + c + "'");
426
427 // TODO: stopTone does not take in character input, we may want to
428 // consider checking for this ourselves.
429 if (ok(getAcceptedChars(), c)) {
430 Log.d(this, "Stopping the tone for '" + c + "'");
431 getPresenter().stopDtmf();
432 return true;
433 }
434
435 return false;
436 }
437
438 /**
439 * Find the Dialer Key mapped to this event.
440 *
441 * @return The char value of the input event, otherwise 0 if no matching character was found.
442 */
443 private char lookup(KeyEvent event) {
444 // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup}
445 int meta = event.getMetaState();
446 int number = event.getNumber();
447
448 if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) {
449 int match = event.getMatch(getAcceptedChars(), meta);
450 number = (match != 0) ? match : number;
451 }
452
453 return (char) number;
454 }
455
456 /** Check to see if the keyEvent is dialable. */
457 boolean isKeyEventAcceptable(KeyEvent event) {
458 return (ok(getAcceptedChars(), lookup(event)));
459 }
460 }
461}