• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.incallui;
18 
19 import android.content.Context;
20 import android.os.Bundle;
21 import android.text.Editable;
22 import android.text.method.DialerKeyListener;
23 import android.util.ArrayMap;
24 import android.util.AttributeSet;
25 import android.view.KeyEvent;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.View.OnClickListener;
29 import android.view.View.OnKeyListener;
30 import android.view.ViewGroup;
31 import android.widget.EditText;
32 import android.widget.LinearLayout;
33 import android.widget.TextView;
34 import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
35 import com.android.dialer.dialpadview.DialpadKeyButton;
36 import com.android.dialer.dialpadview.DialpadKeyButton.OnPressedListener;
37 import com.android.dialer.dialpadview.DialpadView;
38 import com.android.incallui.DialpadPresenter.DialpadUi;
39 import com.android.incallui.baseui.BaseFragment;
40 import java.util.Map;
41 
42 /** Fragment for call control buttons */
43 public 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
mDisplayMap.put(R.id.one, '1')52     mDisplayMap.put(R.id.one, '1');
mDisplayMap.put(R.id.two, '2')53     mDisplayMap.put(R.id.two, '2');
mDisplayMap.put(R.id.three, '3')54     mDisplayMap.put(R.id.three, '3');
mDisplayMap.put(R.id.four, '4')55     mDisplayMap.put(R.id.four, '4');
mDisplayMap.put(R.id.five, '5')56     mDisplayMap.put(R.id.five, '5');
mDisplayMap.put(R.id.six, '6')57     mDisplayMap.put(R.id.six, '6');
mDisplayMap.put(R.id.seven, '7')58     mDisplayMap.put(R.id.seven, '7');
mDisplayMap.put(R.id.eight, '8')59     mDisplayMap.put(R.id.eight, '8');
mDisplayMap.put(R.id.nine, '9')60     mDisplayMap.put(R.id.nine, '9');
mDisplayMap.put(R.id.zero, '0')61     mDisplayMap.put(R.id.zero, '0');
mDisplayMap.put(R.id.pound, '#')62     mDisplayMap.put(R.id.pound, '#');
mDisplayMap.put(R.id.star, '*')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
onClick(View v)88   public void onClick(View v) {
89     if (v.getId() == R.id.dialpad_back) {
90       getActivity().onBackPressed();
91     }
92   }
93 
94   @Override
onKey(View v, int keyCode, KeyEvent event)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
createPresenter()119   public DialpadPresenter createPresenter() {
120     return new DialpadPresenter();
121   }
122 
123   @Override
getUi()124   public DialpadPresenter.DialpadUi getUi() {
125     return this;
126   }
127 
128   // TODO Adds hardware keyboard listener
129 
130   @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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
onResume()155   public void onResume() {
156     super.onResume();
157     updateColors();
158   }
159 
updateColors()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
onDestroyView()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    */
getDtmfText()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    */
setDtmfText(String text)196   public void setDtmfText(String text) {
197     mDtmfDialerField.setText(PhoneNumberUtilsCompat.createTtsSpannable(text));
198   }
199 
200   @Override
setVisible(boolean on)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. */
animateShowDialpad()210   public void animateShowDialpad() {
211     final DialpadView dialpadView = (DialpadView) getView().findViewById(R.id.dialpad_view);
212     dialpadView.animateShow();
213   }
214 
215   @Override
appendDigitsToField(char digit)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. */
onDialerKeyDown(KeyEvent event)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. */
onDialerKeyUp(KeyEvent event)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 
configureKeypadListeners()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
onPressed(View view, boolean pressed)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 
DialpadSlidingLinearLayout(Context context)279     public DialpadSlidingLinearLayout(Context context) {
280       super(context);
281     }
282 
DialpadSlidingLinearLayout(Context context, AttributeSet attrs)283     public DialpadSlidingLinearLayout(Context context, AttributeSet attrs) {
284       super(context, attrs);
285     }
286 
DialpadSlidingLinearLayout(Context context, AttributeSet attrs, int defStyle)287     public DialpadSlidingLinearLayout(Context context, AttributeSet attrs, int defStyle) {
288       super(context, attrs, defStyle);
289     }
290 
getYFraction()291     public float getYFraction() {
292       final int height = getHeight();
293       if (height == 0) {
294         return 0;
295       }
296       return getTranslationY() / height;
297     }
298 
setYFraction(float yFraction)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 
DTMFKeyListener()321     private DTMFKeyListener() {
322       super();
323     }
324 
325     /** Overriden to return correct DTMF-dialable characters. */
326     @Override
getAcceptedChars()327     protected char[] getAcceptedChars() {
328       return DTMF_CHARACTERS;
329     }
330 
331     /** special key listener ignores backspace. */
332     @Override
backspace(View view, Editable content, int keyCode, KeyEvent event)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
onKeyDown(View view, Editable content, int keyCode, KeyEvent event)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
onKeyUp(View view, Editable content, int keyCode, KeyEvent event)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. */
onKeyDown(KeyEvent event)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      */
onKeyUp(KeyEvent event)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      */
lookup(KeyEvent event)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. */
isKeyEventAcceptable(KeyEvent event)457     boolean isKeyEventAcceptable(KeyEvent event) {
458       return (ok(getAcceptedChars(), lookup(event)));
459     }
460   }
461 }
462