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