blob: 3e43eaf9040f94df8ebab0fff18934cf6c043b61 [file] [log] [blame]
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08001/*
2 * Copyright (C) 2007 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
17package com.android.contacts;
18
19import android.app.Activity;
20import android.content.ActivityNotFoundException;
21import android.content.Context;
22import android.content.Intent;
23import android.content.res.Resources;
24import android.database.Cursor;
25import android.graphics.Bitmap;
26import android.graphics.BitmapFactory;
27import android.graphics.drawable.Drawable;
28import android.media.AudioManager;
29import android.media.ToneGenerator;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.Message;
34import android.os.RemoteException;
35import android.os.ServiceManager;
36import android.os.SystemClock;
David Brownc29c7ab2009-07-07 16:00:18 -070037import android.os.Vibrator;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080038import android.provider.Contacts.Intents.Insert;
39import android.provider.Contacts.People;
40import android.provider.Contacts.Phones;
41import android.provider.Contacts.PhonesColumns;
42import android.provider.Settings;
43import android.telephony.PhoneNumberFormattingTextWatcher;
44import android.telephony.PhoneNumberUtils;
45import android.telephony.PhoneStateListener;
46import android.telephony.TelephonyManager;
47import android.text.Editable;
Reli Talc2a2a512009-06-10 16:48:00 -040048import android.text.SpannableStringBuilder;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080049import android.text.TextUtils;
50import android.text.TextWatcher;
51import android.text.method.DialerKeyListener;
52import android.util.Log;
53import android.view.KeyEvent;
54import android.view.LayoutInflater;
55import android.view.Menu;
56import android.view.MenuItem;
57import android.view.View;
58import android.view.ViewConfiguration;
59import android.view.ViewGroup;
Karl Rosaenf46bc312009-03-24 18:20:48 -070060import android.view.inputmethod.InputMethodManager;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080061import android.widget.AdapterView;
62import android.widget.BaseAdapter;
63import android.widget.EditText;
64import android.widget.ImageView;
65import android.widget.ListView;
66import android.widget.TextView;
67
68import com.android.internal.telephony.ITelephony;
69
70/**
71 * Dialer activity that displays the typical twelve key interface.
72 */
73public class TwelveKeyDialer extends Activity implements View.OnClickListener,
74 View.OnLongClickListener, View.OnKeyListener,
75 AdapterView.OnItemClickListener, TextWatcher {
76
77 private static final String TAG = "TwelveKeyDialer";
Eric Laurentd9efc872009-07-17 11:52:06 -070078
David Browne766c772009-08-14 13:46:16 -070079 // Handler message codes
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080080 private static final int STOP_TONE = 1;
81
82 /** The length of DTMF tones in milliseconds */
83 private static final int TONE_LENGTH_MS = 150;
Eric Laurentd9efc872009-07-17 11:52:06 -070084
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080085 /** The DTMF tone volume relative to other sounds in the stream */
86 private static final int TONE_RELATIVE_VOLUME = 50;
87
88 private EditText mDigits;
89 private View mDelete;
90 private MenuItem mAddToContactMenuItem;
91 private ToneGenerator mToneGenerator;
92 private Object mToneGeneratorLock = new Object();
93 private Drawable mDigitsBackground;
94 private Drawable mDigitsEmptyBackground;
95 private Drawable mDeleteBackground;
96 private Drawable mDeleteEmptyBackground;
97 private View mDigitsAndBackspace;
98 private View mDialpad;
David Brown3d07e6d2009-08-04 20:30:09 -070099 private View mDialButton;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800100 private ListView mDialpadChooser;
101 private DialpadChooserAdapter mDialpadChooserAdapter;
Reli Talc2a2a512009-06-10 16:48:00 -0400102 //Member variables for dialpad options
103 private MenuItem m2SecPauseMenuItem;
104 private MenuItem mWaitMenuItem;
105 private static final int MENU_ADD_CONTACTS = 1;
106 private static final int MENU_2S_PAUSE = 2;
107 private static final int MENU_WAIT = 3;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800108
109 // determines if we want to playback local DTMF tones.
110 private boolean mDTMFToneEnabled;
David Brownc29c7ab2009-07-07 16:00:18 -0700111
112 // Vibration (haptic feedback) for dialer key presses.
113 private Vibrator mVibrator;
114 private boolean mVibrateOn;
115 private long mVibrateDuration;
116
Eric Laurentd9efc872009-07-17 11:52:06 -0700117
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800118 /** Identifier for the "Add Call" intent extra. */
119 static final String ADD_CALL_MODE_KEY = "add_call_mode";
120 /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
121 private boolean mIsAddCallMode;
122
123 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
124 /**
125 * Listen for phone state changes so that we can take down the
126 * "dialpad chooser" if the phone becomes idle while the
127 * chooser UI is visible.
128 */
129 @Override
130 public void onCallStateChanged(int state, String incomingNumber) {
131 // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
132 // + state + ", '" + incomingNumber + "'");
133 if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
134 // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down...");
135 // Note there's a race condition in the UI here: the
136 // dialpad chooser could conceivably disappear (on its
137 // own) at the exact moment the user was trying to select
138 // one of the choices, which would be confusing. (But at
139 // least that's better than leaving the dialpad chooser
140 // onscreen, but useless...)
141 showDialpadChooser(false);
142 }
143 }
144 };
145
146 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
147 // Do nothing
148 }
149
150 public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
151 // Do nothing
Eric Laurentd9efc872009-07-17 11:52:06 -0700152 // DTMF Tones do not need to be played here any longer -
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800153 // the DTMF dialer handles that functionality now.
154 }
155
156 public void afterTextChanged(Editable input) {
157 if (SpecialCharSequenceMgr.handleChars(this, input.toString(), mDigits)) {
158 // A special sequence was entered, clear the digits
159 mDigits.getText().clear();
160 }
161
162 // Set the proper background for the dial input area
163 if (mDigits.length() != 0) {
164 mDelete.setBackgroundDrawable(mDeleteBackground);
165 mDigits.setBackgroundDrawable(mDigitsBackground);
166 mDigits.setCompoundDrawablesWithIntrinsicBounds(
167 getResources().getDrawable(R.drawable.ic_dial_number), null, null, null);
168 } else {
169 mDelete.setBackgroundDrawable(mDeleteEmptyBackground);
170 mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
171 mDigits.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
172 }
David Brown3d07e6d2009-08-04 20:30:09 -0700173
174 // Update the enabledness of the "Dial" button
175 if (mDialButton != null) {
176 mDialButton.setEnabled(mDigits.length() != 0);
177 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800178 }
179
180 @Override
181 protected void onCreate(Bundle icicle) {
182 super.onCreate(icicle);
183
184 // Set the content view
185 setContentView(getContentViewResource());
186
187 // Load up the resources for the text field and delete button
188 Resources r = getResources();
189 mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
190 //mDigitsBackground.setDither(true);
191 mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
192 //mDigitsEmptyBackground.setDither(true);
193 mDeleteBackground = r.getDrawable(R.drawable.btn_dial_delete_active);
194 //mDeleteBackground.setDither(true);
195 mDeleteEmptyBackground = r.getDrawable(R.drawable.btn_dial_delete);
196 //mDeleteEmptyBackground.setDither(true);
197
198 mDigits = (EditText) findViewById(R.id.digits);
199 mDigits.setKeyListener(DialerKeyListener.getInstance());
200 mDigits.setOnClickListener(this);
201 mDigits.setOnKeyListener(this);
202 maybeAddNumberFormatting();
203
204 // Check for the presence of the keypad
205 View view = findViewById(R.id.one);
206 if (view != null) {
207 setupKeypad();
208 }
209
David Brown3d07e6d2009-08-04 20:30:09 -0700210 // Check whether we should show the onscreen "Dial" button.
211 if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
212 mDialButton = findViewById(R.id.dialButton);
213 mDialButton.setVisibility(View.VISIBLE); // It's GONE by default
214 mDialButton.setOnClickListener(this);
215 }
216
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800217 view = findViewById(R.id.backspace);
218 view.setOnClickListener(this);
219 view.setOnLongClickListener(this);
220 mDelete = view;
221
Dmitri Plotnikov032bb362009-05-06 17:05:39 -0700222 mDigitsAndBackspace = findViewById(R.id.digitsAndBackspace);
223 mDialpad = findViewById(R.id.dialpad); // This is null in landscape mode
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800224
225 // Set up the "dialpad chooser" UI; see showDialpadChooser().
226 mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
227 mDialpadChooser.setOnItemClickListener(this);
228
229 if (!resolveIntent() && icicle != null) {
230 super.onRestoreInstanceState(icicle);
231 }
232
David Brownc29c7ab2009-07-07 16:00:18 -0700233 // Initialize vibration parameters.
234 // TODO: We might eventually need to make mVibrateOn come from a
235 // user preference rather than a per-platform resource, in which
236 // case we would need to update it in onResume() rather than here.
237 mVibrateOn = r.getBoolean(R.bool.config_enable_dialer_key_vibration);
238 mVibrateDuration = (long) r.getInteger(R.integer.config_dialer_key_vibrate_duration);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800239 }
240
241 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800242 protected void onRestoreInstanceState(Bundle icicle) {
243 // Do nothing, state is restored in onCreate() if needed
244 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700245
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800246 protected void maybeAddNumberFormatting() {
247 mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
248 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700249
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800250 /**
Eric Laurentd9efc872009-07-17 11:52:06 -0700251 * Overridden by subclasses to control the resource used by the content view.
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800252 */
253 protected int getContentViewResource() {
254 return R.layout.twelve_key_dialer;
255 }
256
257 private boolean resolveIntent() {
258 boolean ignoreState = false;
259
260 // Find the proper intent
261 final Intent intent;
262 if (isChild()) {
263 intent = getParent().getIntent();
264 ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
265 } else {
266 intent = getIntent();
267 }
268 // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
269
270 // by default we are not adding a call.
271 mIsAddCallMode = false;
272
273 // By default we don't show the "dialpad chooser" UI.
274 boolean needToShowDialpadChooser = false;
275
276 // Resolve the intent
277 final String action = intent.getAction();
278 if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
279 // see if we are "adding a call" from the InCallScreen; false by default.
280 mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
281 Uri uri = intent.getData();
282 if (uri != null) {
283 if ("tel".equals(uri.getScheme())) {
284 // Put the requested number into the input area
285 String data = uri.getSchemeSpecificPart();
286 setFormattedDigits(data);
287 } else {
288 String type = intent.getType();
289 if (People.CONTENT_ITEM_TYPE.equals(type)
290 || Phones.CONTENT_ITEM_TYPE.equals(type)) {
291 // Query the phone number
292 Cursor c = getContentResolver().query(intent.getData(),
293 new String[] {PhonesColumns.NUMBER}, null, null, null);
294 if (c != null) {
295 if (c.moveToFirst()) {
296 // Put the number into the input area
297 setFormattedDigits(c.getString(0));
298 }
299 c.close();
300 }
301 }
302 }
303 }
304 } else if (Intent.ACTION_MAIN.equals(action)) {
305 // The MAIN action means we're bringing up a blank dialer
306 // (e.g. by selecting the Home shortcut, or tabbing over from
307 // Contacts or Call log.)
308 //
309 // At this point, IF there's already an active call, there's a
310 // good chance that the user got here accidentally (but really
311 // wanted the in-call dialpad instead). So we bring up an
312 // intermediate UI to make the user confirm what they really
313 // want to do.
314 if (phoneIsInUse()) {
315 // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
316 needToShowDialpadChooser = true;
317 }
318 }
319
320 // Bring up the "dialpad chooser" IFF we need to make the user
321 // confirm which dialpad they really want.
322 showDialpadChooser(needToShowDialpadChooser);
323
324 return ignoreState;
325 }
326
327 protected void setFormattedDigits(String data) {
328 // strip the non-dialable numbers out of the data string.
329 String dialString = PhoneNumberUtils.extractNetworkPortion(data);
330 dialString = PhoneNumberUtils.formatNumber(dialString);
331 if (!TextUtils.isEmpty(dialString)) {
332 Editable digits = mDigits.getText();
333 digits.replace(0, digits.length(), dialString);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700334 // for some reason this isn't getting called in the digits.replace call above..
335 // but in any case, this will make sure the background drawable looks right
336 afterTextChanged(digits);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800337 }
338 }
339
340 @Override
341 protected void onNewIntent(Intent newIntent) {
342 setIntent(newIntent);
343 resolveIntent();
344 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700345
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800346 @Override
347 protected void onPostCreate(Bundle savedInstanceState) {
348 super.onPostCreate(savedInstanceState);
349
350 // This can't be done in onCreate(), since the auto-restoring of the digits
351 // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
352 // is called. This method will be called every time the activity is created, and
353 // will always happen after onRestoreSavedInstanceState().
354 mDigits.addTextChangedListener(this);
355 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700356
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800357 private void setupKeypad() {
358 // Setup the listeners for the buttons
359 View view = findViewById(R.id.one);
360 view.setOnClickListener(this);
361 view.setOnLongClickListener(this);
362
363 findViewById(R.id.two).setOnClickListener(this);
364 findViewById(R.id.three).setOnClickListener(this);
365 findViewById(R.id.four).setOnClickListener(this);
366 findViewById(R.id.five).setOnClickListener(this);
367 findViewById(R.id.six).setOnClickListener(this);
368 findViewById(R.id.seven).setOnClickListener(this);
369 findViewById(R.id.eight).setOnClickListener(this);
370 findViewById(R.id.nine).setOnClickListener(this);
371 findViewById(R.id.star).setOnClickListener(this);
372
373 view = findViewById(R.id.zero);
374 view.setOnClickListener(this);
375 view.setOnLongClickListener(this);
376
377 findViewById(R.id.pound).setOnClickListener(this);
378 }
379
380 @Override
381 protected void onResume() {
382 super.onResume();
David Brownc29c7ab2009-07-07 16:00:18 -0700383
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800384 // retrieve the DTMF tone play back setting.
385 mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
386 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
387
Eric Laurentd9efc872009-07-17 11:52:06 -0700388 // if the mToneGenerator creation fails, just continue without it. It is
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800389 // a local audio signal, and is not as important as the dtmf tone itself.
390 synchronized(mToneGeneratorLock) {
391 if (mToneGenerator == null) {
392 try {
Eric Laurentd9efc872009-07-17 11:52:06 -0700393 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800394 TONE_RELATIVE_VOLUME);
395 } catch (RuntimeException e) {
396 Log.w(TAG, "Exception caught while creating local tone generator: " + e);
397 mToneGenerator = null;
398 }
399 }
400 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700401
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800402 Activity parent = getParent();
403 // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
404 // digits in the dialer field.
405 if (parent != null && parent instanceof DialtactsActivity) {
406 Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
407 if (dialUri != null) {
408 resolveIntent();
409 }
410 }
411
412 // While we're in the foreground, listen for phone state changes,
413 // purely so that we can take down the "dialpad chooser" if the
414 // phone becomes idle while the chooser UI is visible.
415 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
416 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
417
418 // Potentially show hint text in the mDigits field when the user
419 // hasn't typed any digits yet. (If there's already an active call,
420 // this hint text will remind the user that he's about to add a new
421 // call.)
422 //
423 // TODO: consider adding better UI for the case where *both* lines
424 // are currently in use. (Right now we let the user try to add
425 // another call, but that call is guaranteed to fail. Perhaps the
426 // entire dialer UI should be disabled instead.)
427 if (phoneIsInUse()) {
428 mDigits.setHint(R.string.dialerDialpadHintText);
429 } else {
430 // Common case; no hint necessary.
431 mDigits.setHint(null);
432
433 // Also, a sanity-check: the "dialpad chooser" UI should NEVER
434 // be visible if the phone is idle!
435 showDialpadChooser(false);
436 }
437 }
438
439 @Override
Karl Rosaenf46bc312009-03-24 18:20:48 -0700440 public void onWindowFocusChanged(boolean hasFocus) {
441 if (hasFocus) {
442 // Hide soft keyboard, if visible (it's fugly over button dialer).
443 // The only known case where this will be true is when launching the dialer with
444 // ACTION_DIAL via a soft keyboard. we dismiss it here because we don't
445 // have a window token yet in onCreate / onNewIntent
446 InputMethodManager inputMethodManager = (InputMethodManager)
447 getSystemService(Context.INPUT_METHOD_SERVICE);
Eric Laurentd9efc872009-07-17 11:52:06 -0700448 inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700449 }
450 }
451
452 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800453 protected void onPause() {
454 super.onPause();
455
456 // Stop listening for phone state changes.
457 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
458 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
459
460 synchronized(mToneGeneratorLock) {
461 if (mToneGenerator != null) {
462 mToneStopper.removeMessages(STOP_TONE);
463 mToneGenerator.release();
464 mToneGenerator = null;
465 }
466 }
467 }
468
469 @Override
470 public boolean onCreateOptionsMenu(Menu menu) {
Reli Talc2a2a512009-06-10 16:48:00 -0400471 mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800472 .setIcon(android.R.drawable.ic_menu_add);
Reli Talc2a2a512009-06-10 16:48:00 -0400473 m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
474 .setIcon(R.drawable.ic_menu_2sec_pause);
475 mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
476 .setIcon(R.drawable.ic_menu_wait);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800477 return true;
478 }
479
480 @Override
481 public boolean onPrepareOptionsMenu(Menu menu) {
482 // We never show a menu if the "choose dialpad" UI is up.
483 if (dialpadChooserVisible()) {
484 return false;
485 }
486
487 CharSequence digits = mDigits.getText();
488 if (digits == null || !TextUtils.isGraphic(digits)) {
489 mAddToContactMenuItem.setVisible(false);
Reli Talc2a2a512009-06-10 16:48:00 -0400490 m2SecPauseMenuItem.setVisible(false);
491 mWaitMenuItem.setVisible(false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800492 } else {
493 // Put the current digits string into an intent
494 Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
495 intent.putExtra(Insert.PHONE, mDigits.getText());
496 intent.setType(People.CONTENT_ITEM_TYPE);
497 mAddToContactMenuItem.setIntent(intent);
498 mAddToContactMenuItem.setVisible(true);
Reli Talc2a2a512009-06-10 16:48:00 -0400499
500 // Check out whether to show Pause & Wait option menu items
501 int selectionStart;
502 int selectionEnd;
503 String strDigits = digits.toString();
504
505 selectionStart = mDigits.getSelectionStart();
506 selectionEnd = mDigits.getSelectionEnd();
507
508 if (selectionStart != -1) {
509 if (selectionStart > selectionEnd) {
510 // swap it as we want start to be less then end
511 int tmp = selectionStart;
512 selectionStart = selectionEnd;
513 selectionEnd = tmp;
514 }
515
516 if (selectionStart != 0) {
517 // Pause can be visible if cursor is not in the begining
518 m2SecPauseMenuItem.setVisible(true);
519
520 // For Wait to be visible set of condition to meet
521 mWaitMenuItem.setVisible(showWait(selectionStart,
522 selectionEnd, strDigits));
523 } else {
524 // cursor in the beginning both pause and wait to be invisible
525 m2SecPauseMenuItem.setVisible(false);
526 mWaitMenuItem.setVisible(false);
527 }
528 } else {
529 // cursor is not selected so assume new digit is added to the end
530 int strLength = strDigits.length();
531 mWaitMenuItem.setVisible(showWait(strLength,
532 strLength, strDigits));
533 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800534 }
535 return true;
536 }
537
538 @Override
539 public boolean onKeyDown(int keyCode, KeyEvent event) {
540 switch (keyCode) {
541 case KeyEvent.KEYCODE_CALL: {
542 long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
543 if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
544 // Launch voice dialer
545 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
546 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
547 try {
548 startActivity(intent);
549 } catch (ActivityNotFoundException e) {
550 }
551 }
552 return true;
553 }
554 case KeyEvent.KEYCODE_1: {
Eric Laurentd9efc872009-07-17 11:52:06 -0700555 long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800556 if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
557 // Long press detected, call voice mail
558 callVoicemail();
559 }
560 return true;
561 }
562 }
563 return super.onKeyDown(keyCode, event);
564 }
565
566 @Override
567 public boolean onKeyUp(int keyCode, KeyEvent event) {
568 switch (keyCode) {
569 case KeyEvent.KEYCODE_CALL: {
570 if (mIsAddCallMode && (TextUtils.isEmpty(mDigits.getText().toString()))) {
571 // if we are adding a call from the InCallScreen and the phone
572 // number entered is empty, we just close the dialer to expose
573 // the InCallScreen under it.
574 finish();
575 } else {
576 // otherwise, we place the call.
577 placeCall();
578 }
579 return true;
580 }
581 }
582 return super.onKeyUp(keyCode, event);
583 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700584
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800585 private void keyPressed(int keyCode) {
David Brownc29c7ab2009-07-07 16:00:18 -0700586 vibrate();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800587 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
588 mDigits.onKeyDown(keyCode, event);
589 }
590
591 public boolean onKey(View view, int keyCode, KeyEvent event) {
592 switch (view.getId()) {
593 case R.id.digits:
594 if (keyCode == KeyEvent.KEYCODE_ENTER) {
595 placeCall();
596 return true;
597 }
598 break;
599 }
600 return false;
601 }
602
603 public void onClick(View view) {
604 switch (view.getId()) {
605 case R.id.one: {
606 playTone(ToneGenerator.TONE_DTMF_1);
607 keyPressed(KeyEvent.KEYCODE_1);
608 return;
609 }
610 case R.id.two: {
611 playTone(ToneGenerator.TONE_DTMF_2);
612 keyPressed(KeyEvent.KEYCODE_2);
613 return;
614 }
615 case R.id.three: {
616 playTone(ToneGenerator.TONE_DTMF_3);
617 keyPressed(KeyEvent.KEYCODE_3);
618 return;
619 }
620 case R.id.four: {
621 playTone(ToneGenerator.TONE_DTMF_4);
622 keyPressed(KeyEvent.KEYCODE_4);
623 return;
624 }
625 case R.id.five: {
626 playTone(ToneGenerator.TONE_DTMF_5);
627 keyPressed(KeyEvent.KEYCODE_5);
628 return;
629 }
630 case R.id.six: {
631 playTone(ToneGenerator.TONE_DTMF_6);
632 keyPressed(KeyEvent.KEYCODE_6);
633 return;
634 }
635 case R.id.seven: {
636 playTone(ToneGenerator.TONE_DTMF_7);
637 keyPressed(KeyEvent.KEYCODE_7);
638 return;
639 }
640 case R.id.eight: {
641 playTone(ToneGenerator.TONE_DTMF_8);
642 keyPressed(KeyEvent.KEYCODE_8);
643 return;
644 }
645 case R.id.nine: {
646 playTone(ToneGenerator.TONE_DTMF_9);
647 keyPressed(KeyEvent.KEYCODE_9);
648 return;
649 }
650 case R.id.zero: {
651 playTone(ToneGenerator.TONE_DTMF_0);
652 keyPressed(KeyEvent.KEYCODE_0);
653 return;
654 }
655 case R.id.pound: {
656 playTone(ToneGenerator.TONE_DTMF_P);
657 keyPressed(KeyEvent.KEYCODE_POUND);
658 return;
659 }
660 case R.id.star: {
661 playTone(ToneGenerator.TONE_DTMF_S);
662 keyPressed(KeyEvent.KEYCODE_STAR);
663 return;
664 }
665 case R.id.backspace: {
666 keyPressed(KeyEvent.KEYCODE_DEL);
667 return;
668 }
David Brown3d07e6d2009-08-04 20:30:09 -0700669 case R.id.dialButton:
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800670 case R.id.digits: {
David Brownc29c7ab2009-07-07 16:00:18 -0700671 vibrate(); // Vibrate here too, just like we do for the regular keys
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800672 placeCall();
673 return;
674 }
675 }
676 }
677
678 public boolean onLongClick(View view) {
679 final Editable digits = mDigits.getText();
680 int id = view.getId();
681 switch (id) {
682 case R.id.backspace: {
683 digits.clear();
684 return true;
685 }
686 case R.id.one: {
687 if (digits.length() == 0) {
688 callVoicemail();
689 return true;
690 }
691 return false;
692 }
693 case R.id.zero: {
694 keyPressed(KeyEvent.KEYCODE_PLUS);
695 return true;
696 }
697 }
698 return false;
699 }
700
701 void callVoicemail() {
702 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
703 Uri.fromParts("voicemail", "", null));
704 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
705 startActivity(intent);
706 mDigits.getText().clear();
707 finish();
708 }
709
710 void placeCall() {
711 final String number = mDigits.getText().toString();
712 if (number == null || !TextUtils.isGraphic(number)) {
713 // There is no number entered.
714 playTone(ToneGenerator.TONE_PROP_NACK);
715 return;
716 }
717 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
718 Uri.fromParts("tel", number, null));
719 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
720 startActivity(intent);
721 mDigits.getText().clear();
722 finish();
723 }
724
725 Handler mToneStopper = new Handler() {
726 @Override
727 public void handleMessage(Message msg) {
728 switch (msg.what) {
729 case STOP_TONE:
730 synchronized(mToneGeneratorLock) {
731 if (mToneGenerator == null) {
732 Log.w(TAG, "mToneStopper: mToneGenerator == null");
733 } else {
734 mToneGenerator.stopTone();
735 }
736 }
737 break;
738 }
739 }
740 };
741
742 /**
David Brown22f615f2009-06-25 16:19:19 -0700743 * Plays the specified tone for TONE_LENGTH_MS milliseconds.
744 *
745 * The tone is played locally, using the audio stream for phone calls.
746 * Tones are played only if the "Audible touch tones" user preference
747 * is checked, and are NOT played if the device is in silent mode.
748 *
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800749 * @param tone a tone code from {@link ToneGenerator}
750 */
751 void playTone(int tone) {
752 // if local tone playback is disabled, just return.
753 if (!mDTMFToneEnabled) {
754 return;
755 }
David Brown22f615f2009-06-25 16:19:19 -0700756
757 // Also do nothing if the phone is in silent mode.
758 // We need to re-check the ringer mode for *every* playTone()
759 // call, rather than keeping a local flag that's updated in
760 // onResume(), since it's possible to toggle silent mode without
761 // leaving the current activity (via the ENDCALL-longpress menu.)
762 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
David Brownd5a15302009-07-20 16:39:47 -0700763 int ringerMode = audioManager.getRingerMode();
764 if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
765 || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
David Brown22f615f2009-06-25 16:19:19 -0700766 return;
767 }
768
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800769 synchronized(mToneGeneratorLock) {
770 if (mToneGenerator == null) {
771 Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
772 return;
773 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700774
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800775 // Remove pending STOP_TONE messages
776 mToneStopper.removeMessages(STOP_TONE);
Eric Laurentd9efc872009-07-17 11:52:06 -0700777
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800778 // Start the new tone (will stop any playing tone)
779 mToneGenerator.startTone(tone);
780 mToneStopper.sendEmptyMessageDelayed(STOP_TONE, TONE_LENGTH_MS);
781 }
782 }
783
784 /**
785 * Brings up the "dialpad chooser" UI in place of the usual Dialer
786 * elements (the textfield/button and the dialpad underneath).
787 *
788 * We show this UI if the user brings up the Dialer while a call is
789 * already in progress, since there's a good chance we got here
790 * accidentally (and the user really wanted the in-call dialpad instead).
791 * So in this situation we display an intermediate UI that lets the user
792 * explicitly choose between the in-call dialpad ("Use touch tone
793 * keypad") and the regular Dialer ("Add call"). (Or, the option "Return
794 * to call in progress" just goes back to the in-call UI with no dialpad
795 * at all.)
796 *
797 * @param enabled If true, show the "dialpad chooser" instead
798 * of the regular Dialer UI
799 */
800 private void showDialpadChooser(boolean enabled) {
801 if (enabled) {
802 // Log.i(TAG, "Showing dialpad chooser!");
803 mDigitsAndBackspace.setVisibility(View.GONE);
804 if (mDialpad != null) mDialpad.setVisibility(View.GONE);
David Brown3d07e6d2009-08-04 20:30:09 -0700805 if (mDialButton != null) mDialButton.setVisibility(View.GONE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800806 mDialpadChooser.setVisibility(View.VISIBLE);
807
808 // Instantiate the DialpadChooserAdapter and hook it up to the
809 // ListView. We do this only once.
810 if (mDialpadChooserAdapter == null) {
811 mDialpadChooserAdapter = new DialpadChooserAdapter(this);
812 mDialpadChooser.setAdapter(mDialpadChooserAdapter);
813 }
814 } else {
815 // Log.i(TAG, "Displaying normal Dialer UI.");
816 mDigitsAndBackspace.setVisibility(View.VISIBLE);
817 if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
David Brown3d07e6d2009-08-04 20:30:09 -0700818 if (mDialButton != null) mDialButton.setVisibility(View.VISIBLE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800819 mDialpadChooser.setVisibility(View.GONE);
820 }
821 }
822
823 /**
824 * @return true if we're currently showing the "dialpad chooser" UI.
825 */
826 private boolean dialpadChooserVisible() {
827 return mDialpadChooser.getVisibility() == View.VISIBLE;
828 }
829
830 /**
831 * Simple list adapter, binding to an icon + text label
832 * for each item in the "dialpad chooser" list.
833 */
834 private static class DialpadChooserAdapter extends BaseAdapter {
835 private LayoutInflater mInflater;
836
837 // Simple struct for a single "choice" item.
838 static class ChoiceItem {
839 String text;
840 Bitmap icon;
841 int id;
842
843 public ChoiceItem(String s, Bitmap b, int i) {
844 text = s;
845 icon = b;
846 id = i;
847 }
848 }
849
850 // IDs for the possible "choices":
851 static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
852 static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
853 static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
854
855 private static final int NUM_ITEMS = 3;
856 private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
857
858 public DialpadChooserAdapter(Context context) {
859 // Cache the LayoutInflate to avoid asking for a new one each time.
860 mInflater = LayoutInflater.from(context);
861
862 // Initialize the possible choices.
863 // TODO: could this be specified entirely in XML?
864
865 // - "Use touch tone keypad"
866 mChoiceItems[0] = new ChoiceItem(
867 context.getString(R.string.dialer_useDtmfDialpad),
868 BitmapFactory.decodeResource(context.getResources(),
869 R.drawable.ic_dialer_fork_tt_keypad),
870 DIALPAD_CHOICE_USE_DTMF_DIALPAD);
871
872 // - "Return to call in progress"
873 mChoiceItems[1] = new ChoiceItem(
874 context.getString(R.string.dialer_returnToInCallScreen),
875 BitmapFactory.decodeResource(context.getResources(),
876 R.drawable.ic_dialer_fork_current_call),
877 DIALPAD_CHOICE_RETURN_TO_CALL);
878
879 // - "Add call"
880 mChoiceItems[2] = new ChoiceItem(
881 context.getString(R.string.dialer_addAnotherCall),
882 BitmapFactory.decodeResource(context.getResources(),
883 R.drawable.ic_dialer_fork_add_call),
884 DIALPAD_CHOICE_ADD_NEW_CALL);
885 }
886
887 public int getCount() {
888 return NUM_ITEMS;
889 }
890
891 /**
892 * Return the ChoiceItem for a given position.
893 */
894 public Object getItem(int position) {
895 return mChoiceItems[position];
896 }
897
898 /**
899 * Return a unique ID for each possible choice.
900 */
901 public long getItemId(int position) {
902 return position;
903 }
904
905 /**
906 * Make a view for each row.
907 */
908 public View getView(int position, View convertView, ViewGroup parent) {
909 // When convertView is non-null, we can reuse it (there's no need
910 // to reinflate it.)
911 if (convertView == null) {
912 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
913 }
914
915 TextView text = (TextView) convertView.findViewById(R.id.text);
916 text.setText(mChoiceItems[position].text);
917
918 ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
919 icon.setImageBitmap(mChoiceItems[position].icon);
920
921 return convertView;
922 }
923 }
924
925 /**
926 * Handle clicks from the dialpad chooser.
927 */
928 public void onItemClick(AdapterView parent, View v, int position, long id) {
929 DialpadChooserAdapter.ChoiceItem item =
930 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
931 int itemId = item.id;
932 switch (itemId) {
933 case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
934 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
935 // Fire off an intent to go back to the in-call UI
936 // with the dialpad visible.
937 returnToInCallScreen(true);
938 break;
939
940 case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
941 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
942 // Fire off an intent to go back to the in-call UI
943 // (with the dialpad hidden).
944 returnToInCallScreen(false);
945 break;
946
947 case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
948 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
949 // Ok, guess the user really did want to be here (in the
950 // regular Dialer) after all. Bring back the normal Dialer UI.
951 showDialpadChooser(false);
952 break;
953
954 default:
955 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
956 break;
957 }
958 }
959
960 /**
961 * Returns to the in-call UI (where there's presumably a call in
962 * progress) in response to the user selecting "use touch tone keypad"
963 * or "return to call" from the dialpad chooser.
964 */
965 private void returnToInCallScreen(boolean showDialpad) {
966 try {
967 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
968 if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
969 } catch (RemoteException e) {
970 Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
971 }
972
973 // Finally, finish() ourselves so that we don't stay on the
974 // activity stack.
975 // Note that we do this whether or not the showCallScreenWithDialpad()
976 // call above had any effect or not! (That call is a no-op if the
977 // phone is idle, which can happen if the current call ends while
978 // the dialpad chooser is up. In this case we can't show the
979 // InCallScreen, and there's no point staying here in the Dialer,
980 // so we just take the user back where he came from...)
981 finish();
982 }
983
984 /**
985 * @return true if the phone is "in use", meaning that at least one line
986 * is active (ie. off hook or ringing or dialing).
987 */
988 private boolean phoneIsInUse() {
989 boolean phoneInUse = false;
990 try {
991 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
992 if (phone != null) phoneInUse = !phone.isIdle();
993 } catch (RemoteException e) {
994 Log.w(TAG, "phone.isIdle() failed", e);
995 }
996 return phoneInUse;
997 }
David Brownc29c7ab2009-07-07 16:00:18 -0700998
999 /**
1000 * Triggers haptic feedback (if enabled) for dialer key presses.
1001 */
1002 private synchronized void vibrate() {
1003 if (!mVibrateOn) {
1004 return;
1005 }
1006 if (mVibrator == null) {
1007 mVibrator = new Vibrator();
1008 }
1009 mVibrator.vibrate(mVibrateDuration);
1010 }
Reli Talc2a2a512009-06-10 16:48:00 -04001011
1012 /**
1013 * Returns true whenever any one of the options from the menu is selected.
1014 * Code changes to support dialpad options
1015 */
1016 @Override
1017 public boolean onOptionsItemSelected(MenuItem item) {
1018 switch (item.getItemId()) {
1019 case MENU_2S_PAUSE:
1020 updateDialString(",");
1021 return true;
1022 case MENU_WAIT:
1023 updateDialString(";");
1024 return true;
1025 }
1026 return false;
1027 }
1028
1029 /**
1030 * Updates the dial string (mDigits) after inserting a Pause character (,)
1031 * or Wait character (;).
1032 */
1033 private void updateDialString(String newDigits) {
1034 int selectionStart;
1035 int selectionEnd;
1036
1037 // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
1038 selectionStart = mDigits.getSelectionStart();
1039 selectionEnd = mDigits.getSelectionEnd();
1040
1041 Editable digits = mDigits.getText();
1042 if (selectionStart != -1 ) {
1043 if (selectionStart == selectionEnd) {
1044 // then there is no selection. So insert the pause at this
1045 // position and update the mDigits.
1046 digits.replace(selectionStart, selectionStart, newDigits);
1047 } else {
1048 digits.delete(selectionStart, selectionEnd);
1049 digits.replace(selectionStart, selectionStart, newDigits);
1050 }
1051 } else {
1052 int len = mDigits.length();
1053 digits.replace(len, len, newDigits);
1054 }
1055 }
1056
1057 /**
1058 * This function return true if Wait menu item can be shown
1059 * otherwise returns false. Assumes the passed string is non-empty
1060 * and the 0th index check is not required.
1061 */
1062 private boolean showWait(int start, int end, String digits) {
1063 if (start == end) {
1064 // visible false in this case
1065 if (start > digits.length()) return false;
1066
1067 // preceding char is ';', so visible should be false
1068 if (digits.charAt(start-1) == ';') return false;
1069
1070 // next char is ';', so visible should be false
1071 if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
1072 } else {
1073 // visible false in this case
1074 if (start > digits.length() || end > digits.length()) return false;
1075
1076 // In this case we need to just check for ';' preceding to start
1077 // or next to end
1078 if (digits.charAt(start-1) == ';') return false;
1079 }
1080 return true;
1081 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08001082}