diff options
Diffstat (limited to 'java/com/android/incallui/DialpadFragment.java')
-rw-r--r-- | java/com/android/incallui/DialpadFragment.java | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/java/com/android/incallui/DialpadFragment.java b/java/com/android/incallui/DialpadFragment.java new file mode 100644 index 000000000..7f494aa61 --- /dev/null +++ b/java/com/android/incallui/DialpadFragment.java @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.incallui; + +import android.content.Context; +import android.os.Bundle; +import android.text.Editable; +import android.text.method.DialerKeyListener; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnKeyListener; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.android.contacts.common.compat.PhoneNumberUtilsCompat; +import com.android.dialer.dialpadview.DialpadKeyButton; +import com.android.dialer.dialpadview.DialpadKeyButton.OnPressedListener; +import com.android.dialer.dialpadview.DialpadView; +import com.android.incallui.DialpadPresenter.DialpadUi; +import com.android.incallui.baseui.BaseFragment; +import java.util.Map; + +/** Fragment for call control buttons */ +public class DialpadFragment extends BaseFragment<DialpadPresenter, DialpadUi> + implements DialpadUi, OnKeyListener, OnClickListener, OnPressedListener { + + /** Hash Map to map a view id to a character */ + private static final Map<Integer, Character> mDisplayMap = new ArrayMap<>(); + + /** Set up the static maps */ + static { + // Map the buttons to the display characters + mDisplayMap.put(R.id.one, '1'); + mDisplayMap.put(R.id.two, '2'); + mDisplayMap.put(R.id.three, '3'); + mDisplayMap.put(R.id.four, '4'); + mDisplayMap.put(R.id.five, '5'); + mDisplayMap.put(R.id.six, '6'); + mDisplayMap.put(R.id.seven, '7'); + mDisplayMap.put(R.id.eight, '8'); + mDisplayMap.put(R.id.nine, '9'); + mDisplayMap.put(R.id.zero, '0'); + mDisplayMap.put(R.id.pound, '#'); + mDisplayMap.put(R.id.star, '*'); + } + + private final int[] mButtonIds = + new int[] { + R.id.zero, + R.id.one, + R.id.two, + R.id.three, + R.id.four, + R.id.five, + R.id.six, + R.id.seven, + R.id.eight, + R.id.nine, + R.id.star, + R.id.pound + }; + private EditText mDtmfDialerField; + // KeyListener used with the "dialpad digits" EditText widget. + private DTMFKeyListener mDialerKeyListener; + private DialpadView mDialpadView; + private int mCurrentTextColor; + + @Override + public void onClick(View v) { + if (v.getId() == R.id.dialpad_back) { + getActivity().onBackPressed(); + } + } + + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + Log.d(this, "onKey: keyCode " + keyCode + ", view " + v); + + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) { + int viewId = v.getId(); + if (mDisplayMap.containsKey(viewId)) { + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + if (event.getRepeatCount() == 0) { + getPresenter().processDtmf(mDisplayMap.get(viewId)); + } + break; + case KeyEvent.ACTION_UP: + getPresenter().stopDtmf(); + break; + } + // do not return true [handled] here, since we want the + // press / click animation to be handled by the framework. + } + } + return false; + } + + @Override + public DialpadPresenter createPresenter() { + return new DialpadPresenter(); + } + + @Override + public DialpadPresenter.DialpadUi getUi() { + return this; + } + + // TODO Adds hardware keyboard listener + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View parent = inflater.inflate(R.layout.incall_dialpad_fragment, container, false); + mDialpadView = (DialpadView) parent.findViewById(R.id.dialpad_view); + mDialpadView.setCanDigitsBeEdited(false); + mDialpadView.setBackgroundResource(R.color.incall_dialpad_background); + mDtmfDialerField = (EditText) parent.findViewById(R.id.digits); + if (mDtmfDialerField != null) { + mDialerKeyListener = new DTMFKeyListener(); + mDtmfDialerField.setKeyListener(mDialerKeyListener); + // remove the long-press context menus that support + // the edit (copy / paste / select) functions. + mDtmfDialerField.setLongClickable(false); + mDtmfDialerField.setElegantTextHeight(false); + configureKeypadListeners(); + } + View backButton = mDialpadView.findViewById(R.id.dialpad_back); + backButton.setVisibility(View.VISIBLE); + backButton.setOnClickListener(this); + + return parent; + } + + @Override + public void onResume() { + super.onResume(); + updateColors(); + } + + public void updateColors() { + int textColor = InCallPresenter.getInstance().getThemeColorManager().getPrimaryColor(); + + if (mCurrentTextColor == textColor) { + return; + } + + DialpadKeyButton dialpadKey; + for (int i = 0; i < mButtonIds.length; i++) { + dialpadKey = (DialpadKeyButton) mDialpadView.findViewById(mButtonIds[i]); + ((TextView) dialpadKey.findViewById(R.id.dialpad_key_number)).setTextColor(textColor); + } + + mCurrentTextColor = textColor; + } + + @Override + public void onDestroyView() { + mDialerKeyListener = null; + super.onDestroyView(); + } + + /** + * Getter for Dialpad text. + * + * @return String containing current Dialpad EditText text. + */ + public String getDtmfText() { + return mDtmfDialerField.getText().toString(); + } + + /** + * Sets the Dialpad text field with some text. + * + * @param text Text to set Dialpad EditText to. + */ + public void setDtmfText(String text) { + mDtmfDialerField.setText(PhoneNumberUtilsCompat.createTtsSpannable(text)); + } + + @Override + public void setVisible(boolean on) { + if (on) { + getView().setVisibility(View.VISIBLE); + } else { + getView().setVisibility(View.INVISIBLE); + } + } + + /** Starts the slide up animation for the Dialpad keys when the Dialpad is revealed. */ + public void animateShowDialpad() { + final DialpadView dialpadView = (DialpadView) getView().findViewById(R.id.dialpad_view); + dialpadView.animateShow(); + } + + @Override + public void appendDigitsToField(char digit) { + if (mDtmfDialerField != null) { + // TODO: maybe *don't* manually append this digit if + // mDialpadDigits is focused and this key came from the HW + // keyboard, since in that case the EditText field will + // get the key event directly and automatically appends + // whetever the user types. + // (Or, a cleaner fix would be to just make mDialpadDigits + // *not* handle HW key presses. That seems to be more + // complicated than just setting focusable="false" on it, + // though.) + mDtmfDialerField.getText().append(digit); + } + } + + /** Called externally (from InCallScreen) to play a DTMF Tone. */ + /* package */ boolean onDialerKeyDown(KeyEvent event) { + Log.d(this, "Notifying dtmf key down."); + if (mDialerKeyListener != null) { + return mDialerKeyListener.onKeyDown(event); + } else { + return false; + } + } + + /** Called externally (from InCallScreen) to cancel the last DTMF Tone played. */ + public boolean onDialerKeyUp(KeyEvent event) { + Log.d(this, "Notifying dtmf key up."); + if (mDialerKeyListener != null) { + return mDialerKeyListener.onKeyUp(event); + } else { + return false; + } + } + + private void configureKeypadListeners() { + DialpadKeyButton dialpadKey; + for (int i = 0; i < mButtonIds.length; i++) { + dialpadKey = (DialpadKeyButton) mDialpadView.findViewById(mButtonIds[i]); + dialpadKey.setOnKeyListener(this); + dialpadKey.setOnClickListener(this); + dialpadKey.setOnPressedListener(this); + } + } + + @Override + public void onPressed(View view, boolean pressed) { + if (pressed && mDisplayMap.containsKey(view.getId())) { + Log.d(this, "onPressed: " + pressed + " " + mDisplayMap.get(view.getId())); + getPresenter().processDtmf(mDisplayMap.get(view.getId())); + } + if (!pressed) { + Log.d(this, "onPressed: " + pressed); + getPresenter().stopDtmf(); + } + } + + /** + * LinearLayout with getter and setter methods for the translationY property using floats, for + * animation purposes. + */ + public static class DialpadSlidingLinearLayout extends LinearLayout { + + public DialpadSlidingLinearLayout(Context context) { + super(context); + } + + public DialpadSlidingLinearLayout(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public DialpadSlidingLinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public float getYFraction() { + final int height = getHeight(); + if (height == 0) { + return 0; + } + return getTranslationY() / height; + } + + public void setYFraction(float yFraction) { + setTranslationY(yFraction * getHeight()); + } + } + + /** + * Our own key listener, specialized for dealing with DTMF codes. 1. Ignore the backspace since it + * is irrelevant. 2. Allow ONLY valid DTMF characters to generate a tone and be sent as a DTMF + * code. 3. All other remaining characters are handled by the superclass. + * + * <p>This code is purely here to handle events from the hardware keyboard while the DTMF dialpad + * is up. + */ + private class DTMFKeyListener extends DialerKeyListener { + + /** + * Overrides the characters used in {@link DialerKeyListener#CHARACTERS} These are the valid + * dtmf characters. + */ + public final char[] DTMF_CHARACTERS = + new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'}; + + private DTMFKeyListener() { + super(); + } + + /** Overriden to return correct DTMF-dialable characters. */ + @Override + protected char[] getAcceptedChars() { + return DTMF_CHARACTERS; + } + + /** special key listener ignores backspace. */ + @Override + public boolean backspace(View view, Editable content, int keyCode, KeyEvent event) { + return false; + } + + /** + * Overriden so that with each valid button press, we start sending a dtmf code and play a local + * dtmf tone. + */ + @Override + public boolean onKeyDown(View view, Editable content, int keyCode, KeyEvent event) { + // if (DBG) log("DTMFKeyListener.onKeyDown, keyCode " + keyCode + ", view " + view); + + // find the character + char c = (char) lookup(event, content); + + // if not a long press, and parent onKeyDown accepts the input + if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) { + + boolean keyOK = ok(getAcceptedChars(), c); + + // if the character is a valid dtmf code, start playing the tone and send the + // code. + if (keyOK) { + Log.d(this, "DTMFKeyListener reading '" + c + "' from input."); + getPresenter().processDtmf(c); + } else { + Log.d(this, "DTMFKeyListener rejecting '" + c + "' from input."); + } + return true; + } + return false; + } + + /** + * Overriden so that with each valid button up, we stop sending a dtmf code and the dtmf tone. + */ + @Override + public boolean onKeyUp(View view, Editable content, int keyCode, KeyEvent event) { + // if (DBG) log("DTMFKeyListener.onKeyUp, keyCode " + keyCode + ", view " + view); + + super.onKeyUp(view, content, keyCode, event); + + // find the character + char c = (char) lookup(event, content); + + boolean keyOK = ok(getAcceptedChars(), c); + + if (keyOK) { + Log.d(this, "Stopping the tone for '" + c + "'"); + getPresenter().stopDtmf(); + return true; + } + + return false; + } + + /** Handle individual keydown events when we DO NOT have an Editable handy. */ + public boolean onKeyDown(KeyEvent event) { + char c = lookup(event); + Log.d(this, "DTMFKeyListener.onKeyDown: event '" + c + "'"); + + // if not a long press, and parent onKeyDown accepts the input + if (event.getRepeatCount() == 0 && c != 0) { + // if the character is a valid dtmf code, start playing the tone and send the + // code. + if (ok(getAcceptedChars(), c)) { + Log.d(this, "DTMFKeyListener reading '" + c + "' from input."); + getPresenter().processDtmf(c); + return true; + } else { + Log.d(this, "DTMFKeyListener rejecting '" + c + "' from input."); + } + } + return false; + } + + /** + * Handle individual keyup events. + * + * @param event is the event we are trying to stop. If this is null, then we just force-stop the + * last tone without checking if the event is an acceptable dialer event. + */ + public boolean onKeyUp(KeyEvent event) { + if (event == null) { + //the below piece of code sends stopDTMF event unnecessarily even when a null event + //is received, hence commenting it. + /*if (DBG) log("Stopping the last played tone."); + stopTone();*/ + return true; + } + + char c = lookup(event); + Log.d(this, "DTMFKeyListener.onKeyUp: event '" + c + "'"); + + // TODO: stopTone does not take in character input, we may want to + // consider checking for this ourselves. + if (ok(getAcceptedChars(), c)) { + Log.d(this, "Stopping the tone for '" + c + "'"); + getPresenter().stopDtmf(); + return true; + } + + return false; + } + + /** + * Find the Dialer Key mapped to this event. + * + * @return The char value of the input event, otherwise 0 if no matching character was found. + */ + private char lookup(KeyEvent event) { + // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup} + int meta = event.getMetaState(); + int number = event.getNumber(); + + if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) { + int match = event.getMatch(getAcceptedChars(), meta); + number = (match != 0) ? match : number; + } + + return (char) number; + } + + /** Check to see if the keyEvent is dialable. */ + boolean isKeyEventAcceptable(KeyEvent event) { + return (ok(getAcceptedChars(), lookup(event))); + } + } +} |