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