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