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