Home | History | Annotate | Download | only in incallui
      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
     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 }
    462