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