Home | History | Annotate | Download | only in dialer
      1 /*
      2  * Copyright (C) 2015 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 package com.android.car.dialer;
     17 
     18 import android.content.Context;
     19 import android.media.AudioManager;
     20 import android.media.ToneGenerator;
     21 import android.os.Bundle;
     22 import android.support.annotation.Nullable;
     23 import android.support.v4.app.Fragment;
     24 import android.text.TextUtils;
     25 import android.util.Log;
     26 import android.util.SparseArray;
     27 import android.util.SparseIntArray;
     28 import android.view.KeyEvent;
     29 import android.view.LayoutInflater;
     30 import android.view.View;
     31 import android.view.ViewGroup;
     32 import android.widget.TextView;
     33 
     34 import com.android.car.apps.common.FabDrawable;
     35 import com.android.car.dialer.telecom.TelecomUtils;
     36 import com.android.car.dialer.telecom.UiCallManager;
     37 import com.android.car.dialer.telecom.UiCallManager.CallListener;
     38 
     39 /**
     40  * Fragment that controls the dialpad.
     41  */
     42 public class DialerFragment extends Fragment {
     43     private static final String TAG = "Em.DialerFragment";
     44     private static final String INPUT_ACTIVE_KEY = "INPUT_ACTIVE_KEY";
     45     private static final String DIAL_NUMBER_KEY = "DIAL_NUMBER_KEY";
     46 
     47     private static final int TONE_LENGTH_MS = 150;
     48     private static final int TONE_RELATIVE_VOLUME = 80;
     49     private static final int MAX_DIAL_NUMBER = 20;
     50 
     51     private static final SparseIntArray mToneMap = new SparseIntArray();
     52     private static final SparseArray<String> mDialValueMap = new SparseArray<>();
     53 
     54     static {
     55         mToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1);
     56         mToneMap.put(KeyEvent.KEYCODE_2, ToneGenerator.TONE_DTMF_2);
     57         mToneMap.put(KeyEvent.KEYCODE_3, ToneGenerator.TONE_DTMF_3);
     58         mToneMap.put(KeyEvent.KEYCODE_4, ToneGenerator.TONE_DTMF_4);
     59         mToneMap.put(KeyEvent.KEYCODE_5, ToneGenerator.TONE_DTMF_5);
     60         mToneMap.put(KeyEvent.KEYCODE_6, ToneGenerator.TONE_DTMF_6);
     61         mToneMap.put(KeyEvent.KEYCODE_7, ToneGenerator.TONE_DTMF_7);
     62         mToneMap.put(KeyEvent.KEYCODE_8, ToneGenerator.TONE_DTMF_8);
     63         mToneMap.put(KeyEvent.KEYCODE_9, ToneGenerator.TONE_DTMF_9);
     64         mToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0);
     65         mToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S);
     66         mToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P);
     67 
     68         mDialValueMap.put(KeyEvent.KEYCODE_1, "1");
     69         mDialValueMap.put(KeyEvent.KEYCODE_2, "2");
     70         mDialValueMap.put(KeyEvent.KEYCODE_3, "3");
     71         mDialValueMap.put(KeyEvent.KEYCODE_4, "4");
     72         mDialValueMap.put(KeyEvent.KEYCODE_5, "5");
     73         mDialValueMap.put(KeyEvent.KEYCODE_6, "6");
     74         mDialValueMap.put(KeyEvent.KEYCODE_7, "7");
     75         mDialValueMap.put(KeyEvent.KEYCODE_8, "8");
     76         mDialValueMap.put(KeyEvent.KEYCODE_9, "9");
     77         mDialValueMap.put(KeyEvent.KEYCODE_0, "0");
     78         mDialValueMap.put(KeyEvent.KEYCODE_STAR, "*");
     79         mDialValueMap.put(KeyEvent.KEYCODE_POUND, "#");
     80     }
     81 
     82     private Context mContext;
     83     private UiCallManager mUiCallManager;
     84     private final StringBuffer mNumber = new StringBuffer(MAX_DIAL_NUMBER);
     85     private ToneGenerator mToneGenerator;
     86     private final Object mToneGeneratorLock = new Object();
     87     private TextView mNumberView;
     88     private boolean mShowInput = true;
     89     private Runnable mPendingRunnable;
     90 
     91     private DialerBackButtonListener mBackListener;
     92 
     93     /**
     94      * Interface for a class that will be notified when the back button of the dialer has been
     95      * clicked.
     96      */
     97     public interface DialerBackButtonListener {
     98         /**
     99          * Called when the back button has been clicked on the dialer. This action should dismiss
    100          * the dialer fragment.
    101          */
    102         void onDialerBackClick();
    103     }
    104 
    105     /**
    106      * Creates a new instance of the {@link DialerFragment} and display the given number as the one
    107      * to dial.
    108      */
    109     static DialerFragment newInstance(UiCallManager callManager,
    110             DialerBackButtonListener listener, @Nullable String dialNumber) {
    111         DialerFragment fragment = new DialerFragment();
    112         fragment.mUiCallManager = callManager;
    113         fragment.mBackListener = listener;
    114 
    115         if (!TextUtils.isEmpty(dialNumber)) {
    116             Bundle args = new Bundle();
    117             args.putString(DIAL_NUMBER_KEY, dialNumber);
    118             fragment.setArguments(args);
    119         }
    120 
    121         return fragment;
    122     }
    123 
    124     @Override
    125     public void onCreate(Bundle savedInstanceState) {
    126         super.onCreate(savedInstanceState);
    127         if (savedInstanceState != null && savedInstanceState.containsKey(INPUT_ACTIVE_KEY)) {
    128             mShowInput = savedInstanceState.getBoolean(INPUT_ACTIVE_KEY);
    129         }
    130 
    131         Bundle args = getArguments();
    132         if (args != null) {
    133             setDialNumber(args.getString(DIAL_NUMBER_KEY));
    134         }
    135     }
    136 
    137     @Override
    138     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    139             Bundle savedInstanceState) {
    140         if (Log.isLoggable(TAG, Log.DEBUG)) {
    141             Log.d(TAG, "onCreateView");
    142         }
    143 
    144         mContext = getContext();
    145         View view = inflater.inflate(R.layout.dialer_fragment, container, false);
    146 
    147         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    148             Log.v(TAG, "onCreateView: inflated successfully");
    149         }
    150 
    151         view.findViewById(R.id.exit_dialer_button).setOnClickListener(v -> {
    152             if (mBackListener != null) {
    153                 mBackListener.onDialerBackClick();
    154             }
    155         });
    156 
    157         mNumberView = (TextView) view.findViewById(R.id.number);
    158 
    159         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    160             Log.v(TAG, "mShowInput: " + mShowInput);
    161         }
    162 
    163         FabDrawable answerCallDrawable = new FabDrawable(mContext);
    164         answerCallDrawable.setFabAndStrokeColor(getContext().getColor(R.color.phone_call));
    165 
    166         View callButton = view.findViewById(R.id.call);
    167         callButton.setBackground(answerCallDrawable);
    168         callButton.setVisibility(View.VISIBLE);
    169         callButton.setOnClickListener((unusedView) -> {
    170             if (Log.isLoggable(TAG, Log.DEBUG)) {
    171                 Log.d(TAG, "Call button clicked, placing a call: " + mNumber.toString());
    172             }
    173 
    174             if (!TextUtils.isEmpty(mNumber.toString())) {
    175                 mUiCallManager.safePlaceCall(mNumber.toString(), false);
    176             }
    177         });
    178 
    179         View deleteButton = view.findViewById(R.id.delete);
    180         deleteButton.setVisibility(View.VISIBLE);
    181         deleteButton.setOnClickListener(v -> {
    182             if (mNumber.length() != 0) {
    183                 mNumber.deleteCharAt(mNumber.length() - 1);
    184                 mNumberView.setText(getFormattedNumber(mNumber.toString()));
    185             }
    186         });
    187 
    188         setupKeypadClickListeners(view);
    189 
    190         return view;
    191     }
    192 
    193     /**
    194      * The default click listener for all dialpad buttons. This click listener will append its
    195      * associated value to {@link #mNumber}.
    196      */
    197     private class DialpadClickListener implements View.OnClickListener {
    198         private final int mTone;
    199         private final String mValue;
    200 
    201         DialpadClickListener(int keyCode) {
    202             mTone = mToneMap.get(keyCode);
    203             mValue = mDialValueMap.get(keyCode);
    204         }
    205 
    206         @Override
    207         public void onClick(View v) {
    208             mNumber.append(mValue);
    209             mNumberView.setText(getFormattedNumber(mNumber.toString()));
    210             playTone(mTone);
    211         }
    212     }
    213 
    214     private void setupKeypadClickListeners(View parent) {
    215         parent.findViewById(R.id.zero).setOnClickListener(
    216                 new DialpadClickListener(KeyEvent.KEYCODE_0));
    217         parent.findViewById(R.id.one).setOnClickListener(
    218                 new DialpadClickListener(KeyEvent.KEYCODE_1));
    219         parent.findViewById(R.id.two).setOnClickListener(
    220                 new DialpadClickListener(KeyEvent.KEYCODE_2));
    221         parent.findViewById(R.id.three).setOnClickListener(
    222                 new DialpadClickListener(KeyEvent.KEYCODE_3));
    223         parent.findViewById(R.id.four).setOnClickListener(
    224                 new DialpadClickListener(KeyEvent.KEYCODE_4));
    225         parent.findViewById(R.id.five).setOnClickListener(
    226                 new DialpadClickListener(KeyEvent.KEYCODE_5));
    227         parent.findViewById(R.id.six).setOnClickListener(
    228                 new DialpadClickListener(KeyEvent.KEYCODE_6));
    229         parent.findViewById(R.id.seven).setOnClickListener(
    230                 new DialpadClickListener(KeyEvent.KEYCODE_7));
    231         parent.findViewById(R.id.eight).setOnClickListener(
    232                 new DialpadClickListener(KeyEvent.KEYCODE_8));
    233         parent.findViewById(R.id.nine).setOnClickListener(
    234                 new DialpadClickListener(KeyEvent.KEYCODE_9));
    235         parent.findViewById(R.id.star).setOnClickListener(
    236                 new DialpadClickListener(KeyEvent.KEYCODE_STAR));
    237         parent.findViewById(R.id.pound).setOnClickListener(
    238                 new DialpadClickListener(KeyEvent.KEYCODE_POUND));
    239     }
    240 
    241     @Override
    242     public void onResume() {
    243         super.onResume();
    244         synchronized (mToneGeneratorLock) {
    245             if (mToneGenerator == null) {
    246                 mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, TONE_RELATIVE_VOLUME);
    247             }
    248         }
    249         mUiCallManager.addListener(mCallListener);
    250 
    251         if (mPendingRunnable != null) {
    252             mPendingRunnable.run();
    253             mPendingRunnable = null;
    254         }
    255     }
    256 
    257     @Override
    258     public void onPause() {
    259         super.onPause();
    260         mUiCallManager.removeListener(mCallListener);
    261         stopTone();
    262         synchronized (mToneGeneratorLock) {
    263             if (mToneGenerator != null) {
    264                 mToneGenerator.release();
    265                 mToneGenerator = null;
    266             }
    267         }
    268     }
    269 
    270     @Override
    271     public void onDestroyView() {
    272         super.onDestroyView();
    273         mContext = null;
    274         mNumberView = null;
    275     }
    276 
    277     private void setDialNumber(final String number) {
    278         if (TextUtils.isEmpty(number)) {
    279             return;
    280         }
    281 
    282         if (mContext != null && mNumberView != null) {
    283             setDialNumberInternal(number);
    284         } else {
    285             mPendingRunnable = () -> setDialNumberInternal(number);
    286         }
    287     }
    288 
    289     private void setDialNumberInternal(final String number) {
    290         // Clear existing content in mNumber.
    291         mNumber.setLength(0);
    292         mNumber.append(number);
    293         mNumberView.setText(getFormattedNumber(mNumber.toString()));
    294     }
    295 
    296     private void playTone(int tone) {
    297         synchronized (mToneGeneratorLock) {
    298             if (mToneGenerator == null) {
    299                 Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone);
    300                 return;
    301             }
    302 
    303             // Start the new tone (will stop any playing tone)
    304             mToneGenerator.startTone(tone, TONE_LENGTH_MS);
    305         }
    306     }
    307 
    308     private void stopTone() {
    309         synchronized (mToneGeneratorLock) {
    310             if (mToneGenerator == null) {
    311                 Log.w(TAG, "stopTone: mToneGenerator == null");
    312                 return;
    313             }
    314             mToneGenerator.stopTone();
    315         }
    316     }
    317 
    318     private String getFormattedNumber(String number) {
    319         return TelecomUtils.getFormattedNumber(mContext, number);
    320     }
    321 
    322     private final CallListener mCallListener = new CallListener() {
    323         @Override
    324         public void dispatchPhoneKeyEvent(KeyEvent event) {
    325             if (event.getKeyCode() == KeyEvent.KEYCODE_CALL &&
    326                     event.getAction() == KeyEvent.ACTION_UP &&
    327                     !TextUtils.isEmpty(mNumber.toString())) {
    328                 mUiCallManager.safePlaceCall(mNumber.toString(), false);
    329             }
    330         }
    331     };
    332 }
    333