blob: 9284e055becd263cea7d1bfaf3a021809fbb715c [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);
726 if (audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT) {
727 return;
728 }
729
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800730 synchronized(mToneGeneratorLock) {
731 if (mToneGenerator == null) {
732 Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
733 return;
734 }
735
736 // Remove pending STOP_TONE messages
737 mToneStopper.removeMessages(STOP_TONE);
738
739 // Start the new tone (will stop any playing tone)
740 mToneGenerator.startTone(tone);
741 mToneStopper.sendEmptyMessageDelayed(STOP_TONE, TONE_LENGTH_MS);
742 }
743 }
744
745 /**
746 * Brings up the "dialpad chooser" UI in place of the usual Dialer
747 * elements (the textfield/button and the dialpad underneath).
748 *
749 * We show this UI if the user brings up the Dialer while a call is
750 * already in progress, since there's a good chance we got here
751 * accidentally (and the user really wanted the in-call dialpad instead).
752 * So in this situation we display an intermediate UI that lets the user
753 * explicitly choose between the in-call dialpad ("Use touch tone
754 * keypad") and the regular Dialer ("Add call"). (Or, the option "Return
755 * to call in progress" just goes back to the in-call UI with no dialpad
756 * at all.)
757 *
758 * @param enabled If true, show the "dialpad chooser" instead
759 * of the regular Dialer UI
760 */
761 private void showDialpadChooser(boolean enabled) {
762 if (enabled) {
763 // Log.i(TAG, "Showing dialpad chooser!");
764 mDigitsAndBackspace.setVisibility(View.GONE);
765 if (mDialpad != null) mDialpad.setVisibility(View.GONE);
766 mDialpadChooser.setVisibility(View.VISIBLE);
767
768 // Instantiate the DialpadChooserAdapter and hook it up to the
769 // ListView. We do this only once.
770 if (mDialpadChooserAdapter == null) {
771 mDialpadChooserAdapter = new DialpadChooserAdapter(this);
772 mDialpadChooser.setAdapter(mDialpadChooserAdapter);
773 }
774 } else {
775 // Log.i(TAG, "Displaying normal Dialer UI.");
776 mDigitsAndBackspace.setVisibility(View.VISIBLE);
777 if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
778 mDialpadChooser.setVisibility(View.GONE);
779 }
780 }
781
782 /**
783 * @return true if we're currently showing the "dialpad chooser" UI.
784 */
785 private boolean dialpadChooserVisible() {
786 return mDialpadChooser.getVisibility() == View.VISIBLE;
787 }
788
789 /**
790 * Simple list adapter, binding to an icon + text label
791 * for each item in the "dialpad chooser" list.
792 */
793 private static class DialpadChooserAdapter extends BaseAdapter {
794 private LayoutInflater mInflater;
795
796 // Simple struct for a single "choice" item.
797 static class ChoiceItem {
798 String text;
799 Bitmap icon;
800 int id;
801
802 public ChoiceItem(String s, Bitmap b, int i) {
803 text = s;
804 icon = b;
805 id = i;
806 }
807 }
808
809 // IDs for the possible "choices":
810 static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
811 static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
812 static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
813
814 private static final int NUM_ITEMS = 3;
815 private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
816
817 public DialpadChooserAdapter(Context context) {
818 // Cache the LayoutInflate to avoid asking for a new one each time.
819 mInflater = LayoutInflater.from(context);
820
821 // Initialize the possible choices.
822 // TODO: could this be specified entirely in XML?
823
824 // - "Use touch tone keypad"
825 mChoiceItems[0] = new ChoiceItem(
826 context.getString(R.string.dialer_useDtmfDialpad),
827 BitmapFactory.decodeResource(context.getResources(),
828 R.drawable.ic_dialer_fork_tt_keypad),
829 DIALPAD_CHOICE_USE_DTMF_DIALPAD);
830
831 // - "Return to call in progress"
832 mChoiceItems[1] = new ChoiceItem(
833 context.getString(R.string.dialer_returnToInCallScreen),
834 BitmapFactory.decodeResource(context.getResources(),
835 R.drawable.ic_dialer_fork_current_call),
836 DIALPAD_CHOICE_RETURN_TO_CALL);
837
838 // - "Add call"
839 mChoiceItems[2] = new ChoiceItem(
840 context.getString(R.string.dialer_addAnotherCall),
841 BitmapFactory.decodeResource(context.getResources(),
842 R.drawable.ic_dialer_fork_add_call),
843 DIALPAD_CHOICE_ADD_NEW_CALL);
844 }
845
846 public int getCount() {
847 return NUM_ITEMS;
848 }
849
850 /**
851 * Return the ChoiceItem for a given position.
852 */
853 public Object getItem(int position) {
854 return mChoiceItems[position];
855 }
856
857 /**
858 * Return a unique ID for each possible choice.
859 */
860 public long getItemId(int position) {
861 return position;
862 }
863
864 /**
865 * Make a view for each row.
866 */
867 public View getView(int position, View convertView, ViewGroup parent) {
868 // When convertView is non-null, we can reuse it (there's no need
869 // to reinflate it.)
870 if (convertView == null) {
871 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
872 }
873
874 TextView text = (TextView) convertView.findViewById(R.id.text);
875 text.setText(mChoiceItems[position].text);
876
877 ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
878 icon.setImageBitmap(mChoiceItems[position].icon);
879
880 return convertView;
881 }
882 }
883
884 /**
885 * Handle clicks from the dialpad chooser.
886 */
887 public void onItemClick(AdapterView parent, View v, int position, long id) {
888 DialpadChooserAdapter.ChoiceItem item =
889 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
890 int itemId = item.id;
891 switch (itemId) {
892 case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
893 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
894 // Fire off an intent to go back to the in-call UI
895 // with the dialpad visible.
896 returnToInCallScreen(true);
897 break;
898
899 case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
900 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
901 // Fire off an intent to go back to the in-call UI
902 // (with the dialpad hidden).
903 returnToInCallScreen(false);
904 break;
905
906 case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
907 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
908 // Ok, guess the user really did want to be here (in the
909 // regular Dialer) after all. Bring back the normal Dialer UI.
910 showDialpadChooser(false);
911 break;
912
913 default:
914 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
915 break;
916 }
917 }
918
919 /**
920 * Returns to the in-call UI (where there's presumably a call in
921 * progress) in response to the user selecting "use touch tone keypad"
922 * or "return to call" from the dialpad chooser.
923 */
924 private void returnToInCallScreen(boolean showDialpad) {
925 try {
926 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
927 if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
928 } catch (RemoteException e) {
929 Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
930 }
931
932 // Finally, finish() ourselves so that we don't stay on the
933 // activity stack.
934 // Note that we do this whether or not the showCallScreenWithDialpad()
935 // call above had any effect or not! (That call is a no-op if the
936 // phone is idle, which can happen if the current call ends while
937 // the dialpad chooser is up. In this case we can't show the
938 // InCallScreen, and there's no point staying here in the Dialer,
939 // so we just take the user back where he came from...)
940 finish();
941 }
942
943 /**
944 * @return true if the phone is "in use", meaning that at least one line
945 * is active (ie. off hook or ringing or dialing).
946 */
947 private boolean phoneIsInUse() {
948 boolean phoneInUse = false;
949 try {
950 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
951 if (phone != null) phoneInUse = !phone.isIdle();
952 } catch (RemoteException e) {
953 Log.w(TAG, "phone.isIdle() failed", e);
954 }
955 return phoneInUse;
956 }
David Brownc29c7ab2009-07-07 16:00:18 -0700957
958 /**
959 * Triggers haptic feedback (if enabled) for dialer key presses.
960 */
961 private synchronized void vibrate() {
962 if (!mVibrateOn) {
963 return;
964 }
965 if (mVibrator == null) {
966 mVibrator = new Vibrator();
967 }
968 mVibrator.vibrate(mVibrateDuration);
969 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800970}