blob: 2277e5683420aec9611fc38795f14679bf717ff6 [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;
48import android.text.TextUtils;
49import android.text.TextWatcher;
50import android.text.method.DialerKeyListener;
51import android.util.Log;
52import android.view.KeyEvent;
53import android.view.LayoutInflater;
54import android.view.Menu;
55import android.view.MenuItem;
56import android.view.View;
57import android.view.ViewConfiguration;
58import android.view.ViewGroup;
Karl Rosaenf46bc312009-03-24 18:20:48 -070059import android.view.inputmethod.InputMethodManager;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080060import android.widget.AdapterView;
61import android.widget.BaseAdapter;
62import android.widget.EditText;
63import android.widget.ImageView;
64import android.widget.ListView;
65import android.widget.TextView;
66
67import com.android.internal.telephony.ITelephony;
68
69/**
70 * Dialer activity that displays the typical twelve key interface.
71 */
72public class TwelveKeyDialer extends Activity implements View.OnClickListener,
73 View.OnLongClickListener, View.OnKeyListener,
74 AdapterView.OnItemClickListener, TextWatcher {
75
76 private static final String TAG = "TwelveKeyDialer";
77
78 private static final int STOP_TONE = 1;
79
80 /** The length of DTMF tones in milliseconds */
81 private static final int TONE_LENGTH_MS = 150;
82
83 /** The DTMF tone volume relative to other sounds in the stream */
84 private static final int TONE_RELATIVE_VOLUME = 50;
85
86 private EditText mDigits;
87 private View mDelete;
88 private MenuItem mAddToContactMenuItem;
89 private ToneGenerator mToneGenerator;
90 private Object mToneGeneratorLock = new Object();
91 private Drawable mDigitsBackground;
92 private Drawable mDigitsEmptyBackground;
93 private Drawable mDeleteBackground;
94 private Drawable mDeleteEmptyBackground;
95 private View mDigitsAndBackspace;
96 private View mDialpad;
97 private ListView mDialpadChooser;
98 private DialpadChooserAdapter mDialpadChooserAdapter;
99
100 // determines if we want to playback local DTMF tones.
101 private boolean mDTMFToneEnabled;
David Brownc29c7ab2009-07-07 16:00:18 -0700102
103 // Vibration (haptic feedback) for dialer key presses.
104 private Vibrator mVibrator;
105 private boolean mVibrateOn;
106 private long mVibrateDuration;
107
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800108 /** Identifier for the "Add Call" intent extra. */
109 static final String ADD_CALL_MODE_KEY = "add_call_mode";
110 /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
111 private boolean mIsAddCallMode;
112
113 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
114 /**
115 * Listen for phone state changes so that we can take down the
116 * "dialpad chooser" if the phone becomes idle while the
117 * chooser UI is visible.
118 */
119 @Override
120 public void onCallStateChanged(int state, String incomingNumber) {
121 // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
122 // + state + ", '" + incomingNumber + "'");
123 if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
124 // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down...");
125 // Note there's a race condition in the UI here: the
126 // dialpad chooser could conceivably disappear (on its
127 // own) at the exact moment the user was trying to select
128 // one of the choices, which would be confusing. (But at
129 // least that's better than leaving the dialpad chooser
130 // onscreen, but useless...)
131 showDialpadChooser(false);
132 }
133 }
134 };
135
136 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
137 // Do nothing
138 }
139
140 public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
141 // Do nothing
142 // DTMF Tones do not need to be played here any longer -
143 // the DTMF dialer handles that functionality now.
144 }
145
146 public void afterTextChanged(Editable input) {
147 if (SpecialCharSequenceMgr.handleChars(this, input.toString(), mDigits)) {
148 // A special sequence was entered, clear the digits
149 mDigits.getText().clear();
150 }
151
152 // Set the proper background for the dial input area
153 if (mDigits.length() != 0) {
154 mDelete.setBackgroundDrawable(mDeleteBackground);
155 mDigits.setBackgroundDrawable(mDigitsBackground);
156 mDigits.setCompoundDrawablesWithIntrinsicBounds(
157 getResources().getDrawable(R.drawable.ic_dial_number), null, null, null);
158 } else {
159 mDelete.setBackgroundDrawable(mDeleteEmptyBackground);
160 mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
161 mDigits.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
162 }
163 }
164
165 @Override
166 protected void onCreate(Bundle icicle) {
167 super.onCreate(icicle);
168
169 // Set the content view
170 setContentView(getContentViewResource());
171
172 // Load up the resources for the text field and delete button
173 Resources r = getResources();
174 mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
175 //mDigitsBackground.setDither(true);
176 mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
177 //mDigitsEmptyBackground.setDither(true);
178 mDeleteBackground = r.getDrawable(R.drawable.btn_dial_delete_active);
179 //mDeleteBackground.setDither(true);
180 mDeleteEmptyBackground = r.getDrawable(R.drawable.btn_dial_delete);
181 //mDeleteEmptyBackground.setDither(true);
182
183 mDigits = (EditText) findViewById(R.id.digits);
184 mDigits.setKeyListener(DialerKeyListener.getInstance());
185 mDigits.setOnClickListener(this);
186 mDigits.setOnKeyListener(this);
187 maybeAddNumberFormatting();
188
189 // Check for the presence of the keypad
190 View view = findViewById(R.id.one);
191 if (view != null) {
192 setupKeypad();
193 }
194
195 view = findViewById(R.id.backspace);
196 view.setOnClickListener(this);
197 view.setOnLongClickListener(this);
198 mDelete = view;
199
200 mDigitsAndBackspace = (View) findViewById(R.id.digitsAndBackspace);
201 mDialpad = (View) findViewById(R.id.dialpad); // This is null in landscape mode
202
203 // Set up the "dialpad chooser" UI; see showDialpadChooser().
204 mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
205 mDialpadChooser.setOnItemClickListener(this);
206
207 if (!resolveIntent() && icicle != null) {
208 super.onRestoreInstanceState(icicle);
209 }
210
211 // If the mToneGenerator creation fails, just continue without it. It is
212 // a local audio signal, and is not as important as the dtmf tone itself.
213 synchronized (mToneGeneratorLock) {
214 if (mToneGenerator == null) {
215 try {
216 mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
217 TONE_RELATIVE_VOLUME);
218 } catch (RuntimeException e) {
219 Log.w(TAG, "Exception caught while creating local tone generator: " + e);
220 mToneGenerator = null;
221 }
222 }
223 }
David Brownc29c7ab2009-07-07 16:00:18 -0700224
225 // Initialize vibration parameters.
226 // TODO: We might eventually need to make mVibrateOn come from a
227 // user preference rather than a per-platform resource, in which
228 // case we would need to update it in onResume() rather than here.
229 mVibrateOn = r.getBoolean(R.bool.config_enable_dialer_key_vibration);
230 mVibrateDuration = (long) r.getInteger(R.integer.config_dialer_key_vibrate_duration);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800231 }
232
233 @Override
234 protected void onDestroy() {
235 super.onDestroy();
236 synchronized(mToneGeneratorLock) {
237 if (mToneGenerator != null) {
238 mToneStopper.removeMessages(STOP_TONE);
239 mToneGenerator.release();
240 mToneGenerator = null;
241 }
242 }
243 }
244
245 @Override
246 protected void onRestoreInstanceState(Bundle icicle) {
247 // Do nothing, state is restored in onCreate() if needed
248 }
249
250 protected void maybeAddNumberFormatting() {
251 mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
252 }
253
254 /**
255 * Overridden by subclasses to control the resource used by the content view.
256 */
257 protected int getContentViewResource() {
258 return R.layout.twelve_key_dialer;
259 }
260
261 private boolean resolveIntent() {
262 boolean ignoreState = false;
263
264 // Find the proper intent
265 final Intent intent;
266 if (isChild()) {
267 intent = getParent().getIntent();
268 ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
269 } else {
270 intent = getIntent();
271 }
272 // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
273
274 // by default we are not adding a call.
275 mIsAddCallMode = false;
276
277 // By default we don't show the "dialpad chooser" UI.
278 boolean needToShowDialpadChooser = false;
279
280 // Resolve the intent
281 final String action = intent.getAction();
282 if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
283 // see if we are "adding a call" from the InCallScreen; false by default.
284 mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
285 Uri uri = intent.getData();
286 if (uri != null) {
287 if ("tel".equals(uri.getScheme())) {
288 // Put the requested number into the input area
289 String data = uri.getSchemeSpecificPart();
290 setFormattedDigits(data);
291 } else {
292 String type = intent.getType();
293 if (People.CONTENT_ITEM_TYPE.equals(type)
294 || Phones.CONTENT_ITEM_TYPE.equals(type)) {
295 // Query the phone number
296 Cursor c = getContentResolver().query(intent.getData(),
297 new String[] {PhonesColumns.NUMBER}, null, null, null);
298 if (c != null) {
299 if (c.moveToFirst()) {
300 // Put the number into the input area
301 setFormattedDigits(c.getString(0));
302 }
303 c.close();
304 }
305 }
306 }
307 }
308 } else if (Intent.ACTION_MAIN.equals(action)) {
309 // The MAIN action means we're bringing up a blank dialer
310 // (e.g. by selecting the Home shortcut, or tabbing over from
311 // Contacts or Call log.)
312 //
313 // At this point, IF there's already an active call, there's a
314 // good chance that the user got here accidentally (but really
315 // wanted the in-call dialpad instead). So we bring up an
316 // intermediate UI to make the user confirm what they really
317 // want to do.
318 if (phoneIsInUse()) {
319 // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
320 needToShowDialpadChooser = true;
321 }
322 }
323
324 // Bring up the "dialpad chooser" IFF we need to make the user
325 // confirm which dialpad they really want.
326 showDialpadChooser(needToShowDialpadChooser);
327
328 return ignoreState;
329 }
330
331 protected void setFormattedDigits(String data) {
332 // strip the non-dialable numbers out of the data string.
333 String dialString = PhoneNumberUtils.extractNetworkPortion(data);
334 dialString = PhoneNumberUtils.formatNumber(dialString);
335 if (!TextUtils.isEmpty(dialString)) {
336 Editable digits = mDigits.getText();
337 digits.replace(0, digits.length(), dialString);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700338 // for some reason this isn't getting called in the digits.replace call above..
339 // but in any case, this will make sure the background drawable looks right
340 afterTextChanged(digits);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800341 }
342 }
343
344 @Override
345 protected void onNewIntent(Intent newIntent) {
346 setIntent(newIntent);
347 resolveIntent();
348 }
349
350 @Override
351 protected void onPostCreate(Bundle savedInstanceState) {
352 super.onPostCreate(savedInstanceState);
353
354 // This can't be done in onCreate(), since the auto-restoring of the digits
355 // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
356 // is called. This method will be called every time the activity is created, and
357 // will always happen after onRestoreSavedInstanceState().
358 mDigits.addTextChangedListener(this);
359 }
360
361 private void setupKeypad() {
362 // Setup the listeners for the buttons
363 View view = findViewById(R.id.one);
364 view.setOnClickListener(this);
365 view.setOnLongClickListener(this);
366
367 findViewById(R.id.two).setOnClickListener(this);
368 findViewById(R.id.three).setOnClickListener(this);
369 findViewById(R.id.four).setOnClickListener(this);
370 findViewById(R.id.five).setOnClickListener(this);
371 findViewById(R.id.six).setOnClickListener(this);
372 findViewById(R.id.seven).setOnClickListener(this);
373 findViewById(R.id.eight).setOnClickListener(this);
374 findViewById(R.id.nine).setOnClickListener(this);
375 findViewById(R.id.star).setOnClickListener(this);
376
377 view = findViewById(R.id.zero);
378 view.setOnClickListener(this);
379 view.setOnLongClickListener(this);
380
381 findViewById(R.id.pound).setOnClickListener(this);
382 }
383
384 @Override
385 protected void onResume() {
386 super.onResume();
David Brownc29c7ab2009-07-07 16:00:18 -0700387
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800388 // retrieve the DTMF tone play back setting.
389 mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
390 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
391
392 // if the mToneGenerator creation fails, just continue without it. It is
393 // a local audio signal, and is not as important as the dtmf tone itself.
394 synchronized(mToneGeneratorLock) {
395 if (mToneGenerator == null) {
396 try {
397 mToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
398 TONE_RELATIVE_VOLUME);
399 } catch (RuntimeException e) {
400 Log.w(TAG, "Exception caught while creating local tone generator: " + e);
401 mToneGenerator = null;
402 }
403 }
404 }
405
406 Activity parent = getParent();
407 // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
408 // digits in the dialer field.
409 if (parent != null && parent instanceof DialtactsActivity) {
410 Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
411 if (dialUri != null) {
412 resolveIntent();
413 }
414 }
415
416 // While we're in the foreground, listen for phone state changes,
417 // purely so that we can take down the "dialpad chooser" if the
418 // phone becomes idle while the chooser UI is visible.
419 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
420 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
421
422 // Potentially show hint text in the mDigits field when the user
423 // hasn't typed any digits yet. (If there's already an active call,
424 // this hint text will remind the user that he's about to add a new
425 // call.)
426 //
427 // TODO: consider adding better UI for the case where *both* lines
428 // are currently in use. (Right now we let the user try to add
429 // another call, but that call is guaranteed to fail. Perhaps the
430 // entire dialer UI should be disabled instead.)
431 if (phoneIsInUse()) {
432 mDigits.setHint(R.string.dialerDialpadHintText);
433 } else {
434 // Common case; no hint necessary.
435 mDigits.setHint(null);
436
437 // Also, a sanity-check: the "dialpad chooser" UI should NEVER
438 // be visible if the phone is idle!
439 showDialpadChooser(false);
440 }
441 }
442
443 @Override
Karl Rosaenf46bc312009-03-24 18:20:48 -0700444 public void onWindowFocusChanged(boolean hasFocus) {
445 if (hasFocus) {
446 // Hide soft keyboard, if visible (it's fugly over button dialer).
447 // The only known case where this will be true is when launching the dialer with
448 // ACTION_DIAL via a soft keyboard. we dismiss it here because we don't
449 // have a window token yet in onCreate / onNewIntent
450 InputMethodManager inputMethodManager = (InputMethodManager)
451 getSystemService(Context.INPUT_METHOD_SERVICE);
452 inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
453 }
454 }
455
456 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800457 protected void onPause() {
458 super.onPause();
459
460 // Stop listening for phone state changes.
461 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
462 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
463
464 synchronized(mToneGeneratorLock) {
465 if (mToneGenerator != null) {
466 mToneStopper.removeMessages(STOP_TONE);
467 mToneGenerator.release();
468 mToneGenerator = null;
469 }
470 }
471 }
472
473 @Override
474 public boolean onCreateOptionsMenu(Menu menu) {
475 mAddToContactMenuItem = menu.add(0, 0, 0, R.string.recentCalls_addToContact)
476 .setIcon(android.R.drawable.ic_menu_add);
477
478 return true;
479 }
480
481 @Override
482 public boolean onPrepareOptionsMenu(Menu menu) {
483 // We never show a menu if the "choose dialpad" UI is up.
484 if (dialpadChooserVisible()) {
485 return false;
486 }
487
488 CharSequence digits = mDigits.getText();
489 if (digits == null || !TextUtils.isGraphic(digits)) {
490 mAddToContactMenuItem.setVisible(false);
491 } else {
492 // Put the current digits string into an intent
493 Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
494 intent.putExtra(Insert.PHONE, mDigits.getText());
495 intent.setType(People.CONTENT_ITEM_TYPE);
496 mAddToContactMenuItem.setIntent(intent);
497 mAddToContactMenuItem.setVisible(true);
498 }
499 return true;
500 }
501
502 @Override
503 public boolean onKeyDown(int keyCode, KeyEvent event) {
504 switch (keyCode) {
505 case KeyEvent.KEYCODE_CALL: {
506 long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
507 if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
508 // Launch voice dialer
509 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
510 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
511 try {
512 startActivity(intent);
513 } catch (ActivityNotFoundException e) {
514 }
515 }
516 return true;
517 }
518 case KeyEvent.KEYCODE_1: {
519 long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
520 if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
521 // Long press detected, call voice mail
522 callVoicemail();
523 }
524 return true;
525 }
526 }
527 return super.onKeyDown(keyCode, event);
528 }
529
530 @Override
531 public boolean onKeyUp(int keyCode, KeyEvent event) {
532 switch (keyCode) {
533 case KeyEvent.KEYCODE_CALL: {
534 if (mIsAddCallMode && (TextUtils.isEmpty(mDigits.getText().toString()))) {
535 // if we are adding a call from the InCallScreen and the phone
536 // number entered is empty, we just close the dialer to expose
537 // the InCallScreen under it.
538 finish();
539 } else {
540 // otherwise, we place the call.
541 placeCall();
542 }
543 return true;
544 }
545 }
546 return super.onKeyUp(keyCode, event);
547 }
548
549 private void keyPressed(int keyCode) {
David Brownc29c7ab2009-07-07 16:00:18 -0700550 vibrate();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800551 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
552 mDigits.onKeyDown(keyCode, event);
553 }
554
555 public boolean onKey(View view, int keyCode, KeyEvent event) {
556 switch (view.getId()) {
557 case R.id.digits:
558 if (keyCode == KeyEvent.KEYCODE_ENTER) {
559 placeCall();
560 return true;
561 }
562 break;
563 }
564 return false;
565 }
566
567 public void onClick(View view) {
568 switch (view.getId()) {
569 case R.id.one: {
570 playTone(ToneGenerator.TONE_DTMF_1);
571 keyPressed(KeyEvent.KEYCODE_1);
572 return;
573 }
574 case R.id.two: {
575 playTone(ToneGenerator.TONE_DTMF_2);
576 keyPressed(KeyEvent.KEYCODE_2);
577 return;
578 }
579 case R.id.three: {
580 playTone(ToneGenerator.TONE_DTMF_3);
581 keyPressed(KeyEvent.KEYCODE_3);
582 return;
583 }
584 case R.id.four: {
585 playTone(ToneGenerator.TONE_DTMF_4);
586 keyPressed(KeyEvent.KEYCODE_4);
587 return;
588 }
589 case R.id.five: {
590 playTone(ToneGenerator.TONE_DTMF_5);
591 keyPressed(KeyEvent.KEYCODE_5);
592 return;
593 }
594 case R.id.six: {
595 playTone(ToneGenerator.TONE_DTMF_6);
596 keyPressed(KeyEvent.KEYCODE_6);
597 return;
598 }
599 case R.id.seven: {
600 playTone(ToneGenerator.TONE_DTMF_7);
601 keyPressed(KeyEvent.KEYCODE_7);
602 return;
603 }
604 case R.id.eight: {
605 playTone(ToneGenerator.TONE_DTMF_8);
606 keyPressed(KeyEvent.KEYCODE_8);
607 return;
608 }
609 case R.id.nine: {
610 playTone(ToneGenerator.TONE_DTMF_9);
611 keyPressed(KeyEvent.KEYCODE_9);
612 return;
613 }
614 case R.id.zero: {
615 playTone(ToneGenerator.TONE_DTMF_0);
616 keyPressed(KeyEvent.KEYCODE_0);
617 return;
618 }
619 case R.id.pound: {
620 playTone(ToneGenerator.TONE_DTMF_P);
621 keyPressed(KeyEvent.KEYCODE_POUND);
622 return;
623 }
624 case R.id.star: {
625 playTone(ToneGenerator.TONE_DTMF_S);
626 keyPressed(KeyEvent.KEYCODE_STAR);
627 return;
628 }
629 case R.id.backspace: {
630 keyPressed(KeyEvent.KEYCODE_DEL);
631 return;
632 }
633 case R.id.digits: {
David Brownc29c7ab2009-07-07 16:00:18 -0700634 vibrate(); // Vibrate here too, just like we do for the regular keys
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800635 placeCall();
636 return;
637 }
638 }
639 }
640
641 public boolean onLongClick(View view) {
642 final Editable digits = mDigits.getText();
643 int id = view.getId();
644 switch (id) {
645 case R.id.backspace: {
646 digits.clear();
647 return true;
648 }
649 case R.id.one: {
650 if (digits.length() == 0) {
651 callVoicemail();
652 return true;
653 }
654 return false;
655 }
656 case R.id.zero: {
657 keyPressed(KeyEvent.KEYCODE_PLUS);
658 return true;
659 }
660 }
661 return false;
662 }
663
664 void callVoicemail() {
665 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
666 Uri.fromParts("voicemail", "", null));
667 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
668 startActivity(intent);
669 mDigits.getText().clear();
670 finish();
671 }
672
673 void placeCall() {
674 final String number = mDigits.getText().toString();
675 if (number == null || !TextUtils.isGraphic(number)) {
676 // There is no number entered.
677 playTone(ToneGenerator.TONE_PROP_NACK);
678 return;
679 }
680 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
681 Uri.fromParts("tel", number, null));
682 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
683 startActivity(intent);
684 mDigits.getText().clear();
685 finish();
686 }
687
688 Handler mToneStopper = new Handler() {
689 @Override
690 public void handleMessage(Message msg) {
691 switch (msg.what) {
692 case STOP_TONE:
693 synchronized(mToneGeneratorLock) {
694 if (mToneGenerator == null) {
695 Log.w(TAG, "mToneStopper: mToneGenerator == null");
696 } else {
697 mToneGenerator.stopTone();
698 }
699 }
700 break;
701 }
702 }
703 };
704
705 /**
David Brown22f615f2009-06-25 16:19:19 -0700706 * Plays the specified tone for TONE_LENGTH_MS milliseconds.
707 *
708 * The tone is played locally, using the audio stream for phone calls.
709 * Tones are played only if the "Audible touch tones" user preference
710 * is checked, and are NOT played if the device is in silent mode.
711 *
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800712 * @param tone a tone code from {@link ToneGenerator}
713 */
714 void playTone(int tone) {
715 // if local tone playback is disabled, just return.
716 if (!mDTMFToneEnabled) {
717 return;
718 }
David Brown22f615f2009-06-25 16:19:19 -0700719
720 // Also do nothing if the phone is in silent mode.
721 // We need to re-check the ringer mode for *every* playTone()
722 // call, rather than keeping a local flag that's updated in
723 // onResume(), since it's possible to toggle silent mode without
724 // leaving the current activity (via the ENDCALL-longpress menu.)
725 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
David Brownd5a15302009-07-20 16:39:47 -0700726 int ringerMode = audioManager.getRingerMode();
727 if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
728 || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
David Brown22f615f2009-06-25 16:19:19 -0700729 return;
730 }
731
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800732 synchronized(mToneGeneratorLock) {
733 if (mToneGenerator == null) {
734 Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
735 return;
736 }
737
738 // Remove pending STOP_TONE messages
739 mToneStopper.removeMessages(STOP_TONE);
740
741 // Start the new tone (will stop any playing tone)
742 mToneGenerator.startTone(tone);
743 mToneStopper.sendEmptyMessageDelayed(STOP_TONE, TONE_LENGTH_MS);
744 }
745 }
746
747 /**
748 * Brings up the "dialpad chooser" UI in place of the usual Dialer
749 * elements (the textfield/button and the dialpad underneath).
750 *
751 * We show this UI if the user brings up the Dialer while a call is
752 * already in progress, since there's a good chance we got here
753 * accidentally (and the user really wanted the in-call dialpad instead).
754 * So in this situation we display an intermediate UI that lets the user
755 * explicitly choose between the in-call dialpad ("Use touch tone
756 * keypad") and the regular Dialer ("Add call"). (Or, the option "Return
757 * to call in progress" just goes back to the in-call UI with no dialpad
758 * at all.)
759 *
760 * @param enabled If true, show the "dialpad chooser" instead
761 * of the regular Dialer UI
762 */
763 private void showDialpadChooser(boolean enabled) {
764 if (enabled) {
765 // Log.i(TAG, "Showing dialpad chooser!");
766 mDigitsAndBackspace.setVisibility(View.GONE);
767 if (mDialpad != null) mDialpad.setVisibility(View.GONE);
768 mDialpadChooser.setVisibility(View.VISIBLE);
769
770 // Instantiate the DialpadChooserAdapter and hook it up to the
771 // ListView. We do this only once.
772 if (mDialpadChooserAdapter == null) {
773 mDialpadChooserAdapter = new DialpadChooserAdapter(this);
774 mDialpadChooser.setAdapter(mDialpadChooserAdapter);
775 }
776 } else {
777 // Log.i(TAG, "Displaying normal Dialer UI.");
778 mDigitsAndBackspace.setVisibility(View.VISIBLE);
779 if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
780 mDialpadChooser.setVisibility(View.GONE);
781 }
782 }
783
784 /**
785 * @return true if we're currently showing the "dialpad chooser" UI.
786 */
787 private boolean dialpadChooserVisible() {
788 return mDialpadChooser.getVisibility() == View.VISIBLE;
789 }
790
791 /**
792 * Simple list adapter, binding to an icon + text label
793 * for each item in the "dialpad chooser" list.
794 */
795 private static class DialpadChooserAdapter extends BaseAdapter {
796 private LayoutInflater mInflater;
797
798 // Simple struct for a single "choice" item.
799 static class ChoiceItem {
800 String text;
801 Bitmap icon;
802 int id;
803
804 public ChoiceItem(String s, Bitmap b, int i) {
805 text = s;
806 icon = b;
807 id = i;
808 }
809 }
810
811 // IDs for the possible "choices":
812 static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
813 static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
814 static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
815
816 private static final int NUM_ITEMS = 3;
817 private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
818
819 public DialpadChooserAdapter(Context context) {
820 // Cache the LayoutInflate to avoid asking for a new one each time.
821 mInflater = LayoutInflater.from(context);
822
823 // Initialize the possible choices.
824 // TODO: could this be specified entirely in XML?
825
826 // - "Use touch tone keypad"
827 mChoiceItems[0] = new ChoiceItem(
828 context.getString(R.string.dialer_useDtmfDialpad),
829 BitmapFactory.decodeResource(context.getResources(),
830 R.drawable.ic_dialer_fork_tt_keypad),
831 DIALPAD_CHOICE_USE_DTMF_DIALPAD);
832
833 // - "Return to call in progress"
834 mChoiceItems[1] = new ChoiceItem(
835 context.getString(R.string.dialer_returnToInCallScreen),
836 BitmapFactory.decodeResource(context.getResources(),
837 R.drawable.ic_dialer_fork_current_call),
838 DIALPAD_CHOICE_RETURN_TO_CALL);
839
840 // - "Add call"
841 mChoiceItems[2] = new ChoiceItem(
842 context.getString(R.string.dialer_addAnotherCall),
843 BitmapFactory.decodeResource(context.getResources(),
844 R.drawable.ic_dialer_fork_add_call),
845 DIALPAD_CHOICE_ADD_NEW_CALL);
846 }
847
848 public int getCount() {
849 return NUM_ITEMS;
850 }
851
852 /**
853 * Return the ChoiceItem for a given position.
854 */
855 public Object getItem(int position) {
856 return mChoiceItems[position];
857 }
858
859 /**
860 * Return a unique ID for each possible choice.
861 */
862 public long getItemId(int position) {
863 return position;
864 }
865
866 /**
867 * Make a view for each row.
868 */
869 public View getView(int position, View convertView, ViewGroup parent) {
870 // When convertView is non-null, we can reuse it (there's no need
871 // to reinflate it.)
872 if (convertView == null) {
873 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
874 }
875
876 TextView text = (TextView) convertView.findViewById(R.id.text);
877 text.setText(mChoiceItems[position].text);
878
879 ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
880 icon.setImageBitmap(mChoiceItems[position].icon);
881
882 return convertView;
883 }
884 }
885
886 /**
887 * Handle clicks from the dialpad chooser.
888 */
889 public void onItemClick(AdapterView parent, View v, int position, long id) {
890 DialpadChooserAdapter.ChoiceItem item =
891 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
892 int itemId = item.id;
893 switch (itemId) {
894 case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
895 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
896 // Fire off an intent to go back to the in-call UI
897 // with the dialpad visible.
898 returnToInCallScreen(true);
899 break;
900
901 case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
902 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
903 // Fire off an intent to go back to the in-call UI
904 // (with the dialpad hidden).
905 returnToInCallScreen(false);
906 break;
907
908 case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
909 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
910 // Ok, guess the user really did want to be here (in the
911 // regular Dialer) after all. Bring back the normal Dialer UI.
912 showDialpadChooser(false);
913 break;
914
915 default:
916 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
917 break;
918 }
919 }
920
921 /**
922 * Returns to the in-call UI (where there's presumably a call in
923 * progress) in response to the user selecting "use touch tone keypad"
924 * or "return to call" from the dialpad chooser.
925 */
926 private void returnToInCallScreen(boolean showDialpad) {
927 try {
928 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
929 if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
930 } catch (RemoteException e) {
931 Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
932 }
933
934 // Finally, finish() ourselves so that we don't stay on the
935 // activity stack.
936 // Note that we do this whether or not the showCallScreenWithDialpad()
937 // call above had any effect or not! (That call is a no-op if the
938 // phone is idle, which can happen if the current call ends while
939 // the dialpad chooser is up. In this case we can't show the
940 // InCallScreen, and there's no point staying here in the Dialer,
941 // so we just take the user back where he came from...)
942 finish();
943 }
944
945 /**
946 * @return true if the phone is "in use", meaning that at least one line
947 * is active (ie. off hook or ringing or dialing).
948 */
949 private boolean phoneIsInUse() {
950 boolean phoneInUse = false;
951 try {
952 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
953 if (phone != null) phoneInUse = !phone.isIdle();
954 } catch (RemoteException e) {
955 Log.w(TAG, "phone.isIdle() failed", e);
956 }
957 return phoneInUse;
958 }
David Brownc29c7ab2009-07-07 16:00:18 -0700959
960 /**
961 * Triggers haptic feedback (if enabled) for dialer key presses.
962 */
963 private synchronized void vibrate() {
964 if (!mVibrateOn) {
965 return;
966 }
967 if (mVibrator == null) {
968 mVibrator = new Vibrator();
969 }
970 mVibrator.vibrate(mVibrateDuration);
971 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800972}