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.os.Handler;
23 import android.support.car.ui.FabDrawable;
24 import android.support.v4.app.Fragment;
25 import android.text.TextUtils;
26 import android.util.ArrayMap;
27 import android.util.Log;
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 import com.android.car.dialer.telecom.TelecomUtils;
34 import com.android.car.dialer.telecom.UiCallManager;
35 import com.android.car.dialer.telecom.UiCallManager.CallListener;
36 
37 /**
38  * Fragment that controls the dialpad.
39  */
40 public class DialerFragment extends Fragment {
41     private static final String TAG = "Em.DialerFragment";
42     private static final String INPUT_ACTIVE_KEY = "INPUT_ACTIVE_KEY";
43     private static final int TONE_RELATIVE_VOLUME = 80;
44     private static final int ABANDON_AUDIO_FOCUS_DELAY_MS = 250;
45     private static final int MAX_DIAL_NUMBER = 20;
46     private static final int NO_TONE = -1;
47 
48     private static final ArrayMap<Integer, Integer> mToneMap = new ArrayMap<>();
49 
50     static {
mToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1)51         mToneMap.put(KeyEvent.KEYCODE_1, ToneGenerator.TONE_DTMF_1);
mToneMap.put(KeyEvent.KEYCODE_2, ToneGenerator.TONE_DTMF_2)52         mToneMap.put(KeyEvent.KEYCODE_2, ToneGenerator.TONE_DTMF_2);
mToneMap.put(KeyEvent.KEYCODE_3, ToneGenerator.TONE_DTMF_3)53         mToneMap.put(KeyEvent.KEYCODE_3, ToneGenerator.TONE_DTMF_3);
mToneMap.put(KeyEvent.KEYCODE_4, ToneGenerator.TONE_DTMF_4)54         mToneMap.put(KeyEvent.KEYCODE_4, ToneGenerator.TONE_DTMF_4);
mToneMap.put(KeyEvent.KEYCODE_5, ToneGenerator.TONE_DTMF_5)55         mToneMap.put(KeyEvent.KEYCODE_5, ToneGenerator.TONE_DTMF_5);
mToneMap.put(KeyEvent.KEYCODE_6, ToneGenerator.TONE_DTMF_6)56         mToneMap.put(KeyEvent.KEYCODE_6, ToneGenerator.TONE_DTMF_6);
mToneMap.put(KeyEvent.KEYCODE_7, ToneGenerator.TONE_DTMF_7)57         mToneMap.put(KeyEvent.KEYCODE_7, ToneGenerator.TONE_DTMF_7);
mToneMap.put(KeyEvent.KEYCODE_8, ToneGenerator.TONE_DTMF_8)58         mToneMap.put(KeyEvent.KEYCODE_8, ToneGenerator.TONE_DTMF_8);
mToneMap.put(KeyEvent.KEYCODE_9, ToneGenerator.TONE_DTMF_9)59         mToneMap.put(KeyEvent.KEYCODE_9, ToneGenerator.TONE_DTMF_9);
mToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0)60         mToneMap.put(KeyEvent.KEYCODE_0, ToneGenerator.TONE_DTMF_0);
mToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S)61         mToneMap.put(KeyEvent.KEYCODE_STAR, ToneGenerator.TONE_DTMF_S);
mToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P)62         mToneMap.put(KeyEvent.KEYCODE_POUND, ToneGenerator.TONE_DTMF_P);
63     }
64 
65     private Context mContext;
66     private final StringBuffer mNumber = new StringBuffer(MAX_DIAL_NUMBER);
67     private AudioManager mAudioManager;
68     private ToneGenerator mToneGenerator;
69     private final Handler mHandler = new Handler();
70     private final Object mToneGeneratorLock = new Object();
71     private TextView mNumberView;
72     private boolean mShowInput = true;
73     private Runnable mPendingRunnable;
74 
75     private DialerBackButtonListener mBackListener;
76 
77     /**
78      * Interface for a class that will be notified when the back button of the dialer has been
79      * clicked.
80      */
81     public interface DialerBackButtonListener {
82         /**
83          * Called when the back button has been clicked on the dialer. This action should dismiss
84          * the dialer fragment.
85          */
onDialerBackClick()86         void onDialerBackClick();
87     }
88 
89     /**
90      * Sets the given {@link DialerBackButtonListener} to be notified whenever the back button
91      * on the dialer has been clicked. Passing {@code null} to this method will clear all listeners.
92      */
setDialerBackButtonListener(DialerBackButtonListener listener)93     public void setDialerBackButtonListener(DialerBackButtonListener listener) {
94         mBackListener = listener;
95     }
96 
97     @Override
onCreate(Bundle savedInstanceState)98     public void onCreate(Bundle savedInstanceState) {
99         super.onCreate(savedInstanceState);
100         mAudioManager =
101                 (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
102         if (savedInstanceState != null && savedInstanceState.containsKey(INPUT_ACTIVE_KEY)) {
103             mShowInput = savedInstanceState.getBoolean(INPUT_ACTIVE_KEY);
104         }
105     }
106 
107     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)108     public View onCreateView(LayoutInflater inflater, ViewGroup container,
109             Bundle savedInstanceState) {
110         if (Log.isLoggable(TAG, Log.DEBUG)) {
111             Log.d(TAG, "onCreateView");
112         }
113 
114         mContext = getContext();
115         View view = inflater.inflate(R.layout.dialer_fragment, container, false);
116 
117         if (Log.isLoggable(TAG, Log.VERBOSE)) {
118             Log.v(TAG, "onCreateView: inflated successfully");
119         }
120 
121         view.findViewById(R.id.exit_dialer_button).setOnClickListener((unusedView) -> {
122             if (mBackListener != null) {
123                 mBackListener.onDialerBackClick();
124             }
125         });
126 
127         mNumberView = (TextView) view.findViewById(R.id.number);
128         final boolean hasTouch = getResources().getBoolean(R.bool.has_touch);
129 
130         if (Log.isLoggable(TAG, Log.VERBOSE)) {
131             Log.v(TAG, "hasTouch: " + hasTouch + ", mShowInput: " + mShowInput);
132         }
133 
134         if (hasTouch) {
135             // Only show the delete and call buttons in touch mode.
136             // Buttons are in rotary input itself.
137             View callButton = view.findViewById(R.id.call);
138             FabDrawable answerCallDrawable = new FabDrawable(mContext);
139             answerCallDrawable.setFabAndStrokeColor(getResources().getColor(R.color.phone_call));
140             callButton.setBackground(answerCallDrawable);
141             callButton.setVisibility(View.VISIBLE);
142             callButton.setOnClickListener((unusedView) -> {
143                 if (Log.isLoggable(TAG, Log.DEBUG)) {
144                     Log.d(TAG, "Call button clicked, placing a call: " + mNumber.toString());
145                 }
146 
147                 if (!TextUtils.isEmpty(mNumber.toString())) {
148                     getUiCallManager().safePlaceCall(mNumber.toString(), false);
149                 }
150             });
151             View deleteButton = view.findViewById(R.id.delete);
152             deleteButton.setVisibility(View.VISIBLE);
153             deleteButton.setOnClickListener((unusedView) -> {
154                 if (mNumber.length() != 0) {
155                     mNumber.deleteCharAt(mNumber.length() - 1);
156                     mNumberView.setText(getFormattedNumber(mNumber.toString()));
157                 }
158             });
159         }
160 
161         setupKeypad(view);
162 
163         return view;
164     }
165 
166     /**
167      * The default click listener for all dialpad buttons. This click listener will append its
168      * associated value to {@link #mNumber}.
169      */
170     private class DialpadClickListener implements View.OnClickListener {
171         private String mValue;
172 
DialpadClickListener(String value)173         public DialpadClickListener(String value) {
174             mValue = value;
175         }
176 
177         @Override
onClick(View v)178         public void onClick(View v) {
179             mNumber.append(mValue);
180             mNumberView.setText(getFormattedNumber(mNumber.toString()));
181         }
182     };
183 
184     /**
185      * Sets up the click listeners for all the dialpad buttons.
186      */
setupKeypad(View parent)187     private void setupKeypad(View parent) {
188         parent.findViewById(R.id.zero).setOnClickListener(new DialpadClickListener("0"));
189         parent.findViewById(R.id.one).setOnClickListener(new DialpadClickListener("1"));
190         parent.findViewById(R.id.two).setOnClickListener(new DialpadClickListener("2"));
191         parent.findViewById(R.id.three).setOnClickListener(new DialpadClickListener("3"));
192         parent.findViewById(R.id.four).setOnClickListener(new DialpadClickListener("4"));
193         parent.findViewById(R.id.five).setOnClickListener(new DialpadClickListener("5"));
194         parent.findViewById(R.id.six).setOnClickListener(new DialpadClickListener("6"));
195         parent.findViewById(R.id.seven).setOnClickListener(new DialpadClickListener("7"));
196         parent.findViewById(R.id.eight).setOnClickListener(new DialpadClickListener("8"));
197         parent.findViewById(R.id.nine).setOnClickListener(new DialpadClickListener("9"));
198         parent.findViewById(R.id.star).setOnClickListener(new DialpadClickListener("*"));
199         parent.findViewById(R.id.pound).setOnClickListener(new DialpadClickListener("#"));
200     }
201 
202     @Override
onResume()203     public void onResume() {
204         super.onResume();
205         synchronized (mToneGeneratorLock) {
206             if (mToneGenerator == null) {
207                 mToneGenerator = new ToneGenerator(AudioManager.STREAM_MUSIC, TONE_RELATIVE_VOLUME);
208             }
209         }
210         UiCallManager.getInstance(mContext).addListener(mCallListener);
211 
212         if (mPendingRunnable != null) {
213             mPendingRunnable.run();
214             mPendingRunnable = null;
215         }
216     }
217 
218     @Override
onPause()219     public void onPause() {
220         super.onPause();
221         UiCallManager.getInstance(mContext).removeListener(mCallListener);
222         stopTone();
223         synchronized (mToneGeneratorLock) {
224             if (mToneGenerator != null) {
225                 mToneGenerator.release();
226                 mToneGenerator = null;
227             }
228         }
229     }
230 
231     @Override
onDestroyView()232     public void onDestroyView() {
233         super.onDestroyView();
234         mContext = null;
235         mNumberView = null;
236     }
237 
setDialNumber(final String number)238     public void setDialNumber(final String number) {
239         if (TextUtils.isEmpty(number)) {
240             return;
241         }
242 
243         if (mContext != null && mNumberView != null) {
244             setDialNumberInternal(number);
245         } else {
246             mPendingRunnable = () -> setDialNumberInternal(number);
247         }
248     }
249 
setDialNumberInternal(final String number)250     private void setDialNumberInternal(final String number) {
251         // Clear existing content in mNumber.
252         mNumber.setLength(0);
253         mNumber.append(number);
254         mNumberView.setText(getFormattedNumber(mNumber.toString()));
255     }
256 
stopTone()257     private void stopTone() {
258         synchronized (mToneGeneratorLock) {
259             if (mToneGenerator == null) {
260                 Log.w(TAG, "stopTone: mToneGenerator == null");
261                 return;
262             }
263             mToneGenerator.stopTone();
264             mHandler.postDelayed(mDelayedAbandonAudioFocusRunnable, ABANDON_AUDIO_FOCUS_DELAY_MS);
265         }
266     }
267 
268     private final Runnable mDelayedAbandonAudioFocusRunnable = new Runnable() {
269         @Override
270         public void run() {
271             mAudioManager.abandonAudioFocus(null);
272         }
273     };
274 
getUiCallManager()275     private UiCallManager getUiCallManager() {
276         return UiCallManager.getInstance(mContext);
277     }
278 
getFormattedNumber(String number)279     private String getFormattedNumber(String number) {
280         return TelecomUtils.getFormattedNumber(mContext, number);
281     }
282 
283     private final CallListener mCallListener = new CallListener() {
284         @Override
285         public void dispatchPhoneKeyEvent(KeyEvent event) {
286             if (event.getKeyCode() == KeyEvent.KEYCODE_CALL &&
287                     event.getAction() == KeyEvent.ACTION_UP &&
288                     !TextUtils.isEmpty(mNumber.toString())) {
289                 getUiCallManager().safePlaceCall(mNumber.toString(), false);
290             }
291         }
292     };
293 }
294