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