blob: 2be677a5ac633502b17bf7de2062f6bb3c9c43f3 [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 /** The length of DTMF tones in milliseconds */
80 private static final int TONE_LENGTH_MS = 150;
Eric Laurentd9efc872009-07-17 11:52:06 -070081
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080082 /** 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;
Nicolas Cataniadea164e2009-09-18 06:26:16 -070096 private View mVoicemailDialAndBackspaceRow;
Nicolas Catania80bda0f2009-09-19 09:17:14 -070097 private View mVoicemailButton;
98 private View mDialButton;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080099 private ListView mDialpadChooser;
100 private DialpadChooserAdapter mDialpadChooserAdapter;
Reli Talc2a2a512009-06-10 16:48:00 -0400101 //Member variables for dialpad options
102 private MenuItem m2SecPauseMenuItem;
103 private MenuItem mWaitMenuItem;
104 private static final int MENU_ADD_CONTACTS = 1;
105 private static final int MENU_2S_PAUSE = 2;
106 private static final int MENU_WAIT = 3;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800107
108 // determines if we want to playback local DTMF tones.
109 private boolean mDTMFToneEnabled;
David Brownc29c7ab2009-07-07 16:00:18 -0700110
111 // Vibration (haptic feedback) for dialer key presses.
112 private Vibrator mVibrator;
113 private boolean mVibrateOn;
114 private long mVibrateDuration;
115
Eric Laurentd9efc872009-07-17 11:52:06 -0700116
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800117 /** Identifier for the "Add Call" intent extra. */
118 static final String ADD_CALL_MODE_KEY = "add_call_mode";
119 /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
120 private boolean mIsAddCallMode;
121
122 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
123 /**
124 * Listen for phone state changes so that we can take down the
125 * "dialpad chooser" if the phone becomes idle while the
126 * chooser UI is visible.
127 */
128 @Override
129 public void onCallStateChanged(int state, String incomingNumber) {
130 // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
131 // + state + ", '" + incomingNumber + "'");
132 if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
133 // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down...");
134 // Note there's a race condition in the UI here: the
135 // dialpad chooser could conceivably disappear (on its
136 // own) at the exact moment the user was trying to select
137 // one of the choices, which would be confusing. (But at
138 // least that's better than leaving the dialpad chooser
139 // onscreen, but useless...)
140 showDialpadChooser(false);
141 }
142 }
143 };
144
145 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
146 // Do nothing
147 }
148
149 public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
150 // Do nothing
Eric Laurentd9efc872009-07-17 11:52:06 -0700151 // DTMF Tones do not need to be played here any longer -
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800152 // the DTMF dialer handles that functionality now.
153 }
154
155 public void afterTextChanged(Editable input) {
156 if (SpecialCharSequenceMgr.handleChars(this, input.toString(), mDigits)) {
157 // A special sequence was entered, clear the digits
158 mDigits.getText().clear();
159 }
160
161 // Set the proper background for the dial input area
162 if (mDigits.length() != 0) {
163 mDelete.setBackgroundDrawable(mDeleteBackground);
164 mDigits.setBackgroundDrawable(mDigitsBackground);
165 mDigits.setCompoundDrawablesWithIntrinsicBounds(
166 getResources().getDrawable(R.drawable.ic_dial_number), null, null, null);
167 } else {
168 mDelete.setBackgroundDrawable(mDeleteEmptyBackground);
169 mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
170 mDigits.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null);
171 }
David Brown3d07e6d2009-08-04 20:30:09 -0700172
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700173 updateDialButtonStateEnabledAttr();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800174 }
175
176 @Override
177 protected void onCreate(Bundle icicle) {
178 super.onCreate(icicle);
179
180 // Set the content view
181 setContentView(getContentViewResource());
182
183 // Load up the resources for the text field and delete button
184 Resources r = getResources();
185 mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
186 //mDigitsBackground.setDither(true);
187 mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
188 //mDigitsEmptyBackground.setDither(true);
189 mDeleteBackground = r.getDrawable(R.drawable.btn_dial_delete_active);
190 //mDeleteBackground.setDither(true);
191 mDeleteEmptyBackground = r.getDrawable(R.drawable.btn_dial_delete);
192 //mDeleteEmptyBackground.setDither(true);
193
194 mDigits = (EditText) findViewById(R.id.digits);
195 mDigits.setKeyListener(DialerKeyListener.getInstance());
196 mDigits.setOnClickListener(this);
197 mDigits.setOnKeyListener(this);
198 maybeAddNumberFormatting();
199
200 // Check for the presence of the keypad
201 View view = findViewById(R.id.one);
202 if (view != null) {
203 setupKeypad();
204 }
205
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700206 mVoicemailDialAndBackspaceRow = findViewById(R.id.voicemailAndDialAndBackspace);
207
Nicolas Catania80bda0f2009-09-19 09:17:14 -0700208 initVoicemailButton();
209
David Brown3d07e6d2009-08-04 20:30:09 -0700210 // Check whether we should show the onscreen "Dial" button.
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700211 mDialButton = mVoicemailDialAndBackspaceRow.findViewById(R.id.dialButton);
212
David Brown3d07e6d2009-08-04 20:30:09 -0700213 if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
David Brown3d07e6d2009-08-04 20:30:09 -0700214 mDialButton.setOnClickListener(this);
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700215 } else {
216 mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
217 mDialButton = null;
David Brown3d07e6d2009-08-04 20:30:09 -0700218 }
219
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800220 view = findViewById(R.id.backspace);
221 view.setOnClickListener(this);
222 view.setOnLongClickListener(this);
223 mDelete = view;
224
Dmitri Plotnikov032bb362009-05-06 17:05:39 -0700225 mDigitsAndBackspace = findViewById(R.id.digitsAndBackspace);
226 mDialpad = findViewById(R.id.dialpad); // This is null in landscape mode
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800227
228 // Set up the "dialpad chooser" UI; see showDialpadChooser().
229 mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
230 mDialpadChooser.setOnItemClickListener(this);
231
232 if (!resolveIntent() && icicle != null) {
233 super.onRestoreInstanceState(icicle);
234 }
235
David Brownc29c7ab2009-07-07 16:00:18 -0700236 // Initialize vibration parameters.
237 // TODO: We might eventually need to make mVibrateOn come from a
238 // user preference rather than a per-platform resource, in which
239 // case we would need to update it in onResume() rather than here.
240 mVibrateOn = r.getBoolean(R.bool.config_enable_dialer_key_vibration);
241 mVibrateDuration = (long) r.getInteger(R.integer.config_dialer_key_vibrate_duration);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800242 }
243
244 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800245 protected void onRestoreInstanceState(Bundle icicle) {
246 // Do nothing, state is restored in onCreate() if needed
247 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700248
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800249 protected void maybeAddNumberFormatting() {
250 mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
251 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700252
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800253 /**
Eric Laurentd9efc872009-07-17 11:52:06 -0700254 * Overridden by subclasses to control the resource used by the content view.
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800255 */
256 protected int getContentViewResource() {
257 return R.layout.twelve_key_dialer;
258 }
259
260 private boolean resolveIntent() {
261 boolean ignoreState = false;
262
263 // Find the proper intent
264 final Intent intent;
265 if (isChild()) {
266 intent = getParent().getIntent();
267 ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
268 } else {
269 intent = getIntent();
270 }
271 // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
272
273 // by default we are not adding a call.
274 mIsAddCallMode = false;
275
276 // By default we don't show the "dialpad chooser" UI.
277 boolean needToShowDialpadChooser = false;
278
279 // Resolve the intent
280 final String action = intent.getAction();
281 if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
282 // see if we are "adding a call" from the InCallScreen; false by default.
283 mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
284 Uri uri = intent.getData();
285 if (uri != null) {
286 if ("tel".equals(uri.getScheme())) {
287 // Put the requested number into the input area
288 String data = uri.getSchemeSpecificPart();
289 setFormattedDigits(data);
290 } else {
291 String type = intent.getType();
292 if (People.CONTENT_ITEM_TYPE.equals(type)
293 || Phones.CONTENT_ITEM_TYPE.equals(type)) {
294 // Query the phone number
295 Cursor c = getContentResolver().query(intent.getData(),
296 new String[] {PhonesColumns.NUMBER}, null, null, null);
297 if (c != null) {
298 if (c.moveToFirst()) {
299 // Put the number into the input area
300 setFormattedDigits(c.getString(0));
301 }
302 c.close();
303 }
304 }
305 }
306 }
307 } else if (Intent.ACTION_MAIN.equals(action)) {
308 // The MAIN action means we're bringing up a blank dialer
309 // (e.g. by selecting the Home shortcut, or tabbing over from
310 // Contacts or Call log.)
311 //
312 // At this point, IF there's already an active call, there's a
313 // good chance that the user got here accidentally (but really
314 // wanted the in-call dialpad instead). So we bring up an
315 // intermediate UI to make the user confirm what they really
316 // want to do.
317 if (phoneIsInUse()) {
318 // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
319 needToShowDialpadChooser = true;
320 }
321 }
322
323 // Bring up the "dialpad chooser" IFF we need to make the user
324 // confirm which dialpad they really want.
325 showDialpadChooser(needToShowDialpadChooser);
326
327 return ignoreState;
328 }
329
330 protected void setFormattedDigits(String data) {
331 // strip the non-dialable numbers out of the data string.
332 String dialString = PhoneNumberUtils.extractNetworkPortion(data);
333 dialString = PhoneNumberUtils.formatNumber(dialString);
334 if (!TextUtils.isEmpty(dialString)) {
335 Editable digits = mDigits.getText();
336 digits.replace(0, digits.length(), dialString);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700337 // for some reason this isn't getting called in the digits.replace call above..
338 // but in any case, this will make sure the background drawable looks right
339 afterTextChanged(digits);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800340 }
341 }
342
343 @Override
344 protected void onNewIntent(Intent newIntent) {
345 setIntent(newIntent);
346 resolveIntent();
347 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700348
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800349 @Override
350 protected void onPostCreate(Bundle savedInstanceState) {
351 super.onPostCreate(savedInstanceState);
352
353 // This can't be done in onCreate(), since the auto-restoring of the digits
354 // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
355 // is called. This method will be called every time the activity is created, and
356 // will always happen after onRestoreSavedInstanceState().
357 mDigits.addTextChangedListener(this);
358 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700359
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800360 private void setupKeypad() {
361 // Setup the listeners for the buttons
362 View view = findViewById(R.id.one);
363 view.setOnClickListener(this);
364 view.setOnLongClickListener(this);
365
366 findViewById(R.id.two).setOnClickListener(this);
367 findViewById(R.id.three).setOnClickListener(this);
368 findViewById(R.id.four).setOnClickListener(this);
369 findViewById(R.id.five).setOnClickListener(this);
370 findViewById(R.id.six).setOnClickListener(this);
371 findViewById(R.id.seven).setOnClickListener(this);
372 findViewById(R.id.eight).setOnClickListener(this);
373 findViewById(R.id.nine).setOnClickListener(this);
374 findViewById(R.id.star).setOnClickListener(this);
375
376 view = findViewById(R.id.zero);
377 view.setOnClickListener(this);
378 view.setOnLongClickListener(this);
379
380 findViewById(R.id.pound).setOnClickListener(this);
381 }
382
383 @Override
384 protected void onResume() {
385 super.onResume();
David Brownc29c7ab2009-07-07 16:00:18 -0700386
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800387 // retrieve the DTMF tone play back setting.
388 mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
389 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
390
Eric Laurentd9efc872009-07-17 11:52:06 -0700391 // if the mToneGenerator creation fails, just continue without it. It is
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800392 // a local audio signal, and is not as important as the dtmf tone itself.
393 synchronized(mToneGeneratorLock) {
394 if (mToneGenerator == null) {
395 try {
Eric Laurentd9efc872009-07-17 11:52:06 -0700396 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800397 TONE_RELATIVE_VOLUME);
398 } catch (RuntimeException e) {
399 Log.w(TAG, "Exception caught while creating local tone generator: " + e);
400 mToneGenerator = null;
401 }
402 }
403 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700404
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800405 Activity parent = getParent();
406 // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
407 // digits in the dialer field.
408 if (parent != null && parent instanceof DialtactsActivity) {
409 Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
410 if (dialUri != null) {
411 resolveIntent();
412 }
413 }
414
415 // While we're in the foreground, listen for phone state changes,
416 // purely so that we can take down the "dialpad chooser" if the
417 // phone becomes idle while the chooser UI is visible.
418 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
419 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
420
421 // Potentially show hint text in the mDigits field when the user
422 // hasn't typed any digits yet. (If there's already an active call,
423 // this hint text will remind the user that he's about to add a new
424 // call.)
425 //
426 // TODO: consider adding better UI for the case where *both* lines
427 // are currently in use. (Right now we let the user try to add
428 // another call, but that call is guaranteed to fail. Perhaps the
429 // entire dialer UI should be disabled instead.)
430 if (phoneIsInUse()) {
431 mDigits.setHint(R.string.dialerDialpadHintText);
432 } else {
433 // Common case; no hint necessary.
434 mDigits.setHint(null);
435
436 // Also, a sanity-check: the "dialpad chooser" UI should NEVER
437 // be visible if the phone is idle!
438 showDialpadChooser(false);
439 }
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700440
441 updateDialButtonStateEnabledAttr();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800442 }
443
444 @Override
Karl Rosaenf46bc312009-03-24 18:20:48 -0700445 public void onWindowFocusChanged(boolean hasFocus) {
446 if (hasFocus) {
447 // Hide soft keyboard, if visible (it's fugly over button dialer).
448 // The only known case where this will be true is when launching the dialer with
449 // ACTION_DIAL via a soft keyboard. we dismiss it here because we don't
450 // have a window token yet in onCreate / onNewIntent
451 InputMethodManager inputMethodManager = (InputMethodManager)
452 getSystemService(Context.INPUT_METHOD_SERVICE);
Eric Laurentd9efc872009-07-17 11:52:06 -0700453 inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700454 }
455 }
456
457 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800458 protected void onPause() {
459 super.onPause();
460
461 // Stop listening for phone state changes.
462 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
463 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
464
465 synchronized(mToneGeneratorLock) {
466 if (mToneGenerator != null) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800467 mToneGenerator.release();
468 mToneGenerator = null;
469 }
470 }
471 }
472
473 @Override
474 public boolean onCreateOptionsMenu(Menu menu) {
Reli Talc2a2a512009-06-10 16:48:00 -0400475 mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800476 .setIcon(android.R.drawable.ic_menu_add);
Reli Talc2a2a512009-06-10 16:48:00 -0400477 m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
478 .setIcon(R.drawable.ic_menu_2sec_pause);
479 mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
480 .setIcon(R.drawable.ic_menu_wait);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800481 return true;
482 }
483
484 @Override
485 public boolean onPrepareOptionsMenu(Menu menu) {
486 // We never show a menu if the "choose dialpad" UI is up.
487 if (dialpadChooserVisible()) {
488 return false;
489 }
490
491 CharSequence digits = mDigits.getText();
492 if (digits == null || !TextUtils.isGraphic(digits)) {
493 mAddToContactMenuItem.setVisible(false);
Reli Talc2a2a512009-06-10 16:48:00 -0400494 m2SecPauseMenuItem.setVisible(false);
495 mWaitMenuItem.setVisible(false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800496 } else {
497 // Put the current digits string into an intent
498 Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
499 intent.putExtra(Insert.PHONE, mDigits.getText());
500 intent.setType(People.CONTENT_ITEM_TYPE);
501 mAddToContactMenuItem.setIntent(intent);
502 mAddToContactMenuItem.setVisible(true);
Reli Talc2a2a512009-06-10 16:48:00 -0400503
504 // Check out whether to show Pause & Wait option menu items
505 int selectionStart;
506 int selectionEnd;
507 String strDigits = digits.toString();
508
509 selectionStart = mDigits.getSelectionStart();
510 selectionEnd = mDigits.getSelectionEnd();
511
512 if (selectionStart != -1) {
513 if (selectionStart > selectionEnd) {
514 // swap it as we want start to be less then end
515 int tmp = selectionStart;
516 selectionStart = selectionEnd;
517 selectionEnd = tmp;
518 }
519
520 if (selectionStart != 0) {
521 // Pause can be visible if cursor is not in the begining
522 m2SecPauseMenuItem.setVisible(true);
523
524 // For Wait to be visible set of condition to meet
525 mWaitMenuItem.setVisible(showWait(selectionStart,
526 selectionEnd, strDigits));
527 } else {
528 // cursor in the beginning both pause and wait to be invisible
529 m2SecPauseMenuItem.setVisible(false);
530 mWaitMenuItem.setVisible(false);
531 }
532 } else {
533 // cursor is not selected so assume new digit is added to the end
534 int strLength = strDigits.length();
535 mWaitMenuItem.setVisible(showWait(strLength,
536 strLength, strDigits));
537 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800538 }
539 return true;
540 }
541
542 @Override
543 public boolean onKeyDown(int keyCode, KeyEvent event) {
544 switch (keyCode) {
545 case KeyEvent.KEYCODE_CALL: {
546 long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
547 if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
548 // Launch voice dialer
549 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
550 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
551 try {
552 startActivity(intent);
553 } catch (ActivityNotFoundException e) {
554 }
555 }
556 return true;
557 }
558 case KeyEvent.KEYCODE_1: {
Eric Laurentd9efc872009-07-17 11:52:06 -0700559 long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800560 if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
561 // Long press detected, call voice mail
562 callVoicemail();
563 }
564 return true;
565 }
566 }
567 return super.onKeyDown(keyCode, event);
568 }
569
570 @Override
571 public boolean onKeyUp(int keyCode, KeyEvent event) {
572 switch (keyCode) {
573 case KeyEvent.KEYCODE_CALL: {
574 if (mIsAddCallMode && (TextUtils.isEmpty(mDigits.getText().toString()))) {
575 // if we are adding a call from the InCallScreen and the phone
576 // number entered is empty, we just close the dialer to expose
577 // the InCallScreen under it.
578 finish();
579 } else {
580 // otherwise, we place the call.
581 placeCall();
582 }
583 return true;
584 }
585 }
586 return super.onKeyUp(keyCode, event);
587 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700588
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800589 private void keyPressed(int keyCode) {
David Brownc29c7ab2009-07-07 16:00:18 -0700590 vibrate();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800591 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
592 mDigits.onKeyDown(keyCode, event);
593 }
594
595 public boolean onKey(View view, int keyCode, KeyEvent event) {
596 switch (view.getId()) {
597 case R.id.digits:
598 if (keyCode == KeyEvent.KEYCODE_ENTER) {
599 placeCall();
600 return true;
601 }
602 break;
603 }
604 return false;
605 }
606
607 public void onClick(View view) {
608 switch (view.getId()) {
609 case R.id.one: {
610 playTone(ToneGenerator.TONE_DTMF_1);
611 keyPressed(KeyEvent.KEYCODE_1);
612 return;
613 }
614 case R.id.two: {
615 playTone(ToneGenerator.TONE_DTMF_2);
616 keyPressed(KeyEvent.KEYCODE_2);
617 return;
618 }
619 case R.id.three: {
620 playTone(ToneGenerator.TONE_DTMF_3);
621 keyPressed(KeyEvent.KEYCODE_3);
622 return;
623 }
624 case R.id.four: {
625 playTone(ToneGenerator.TONE_DTMF_4);
626 keyPressed(KeyEvent.KEYCODE_4);
627 return;
628 }
629 case R.id.five: {
630 playTone(ToneGenerator.TONE_DTMF_5);
631 keyPressed(KeyEvent.KEYCODE_5);
632 return;
633 }
634 case R.id.six: {
635 playTone(ToneGenerator.TONE_DTMF_6);
636 keyPressed(KeyEvent.KEYCODE_6);
637 return;
638 }
639 case R.id.seven: {
640 playTone(ToneGenerator.TONE_DTMF_7);
641 keyPressed(KeyEvent.KEYCODE_7);
642 return;
643 }
644 case R.id.eight: {
645 playTone(ToneGenerator.TONE_DTMF_8);
646 keyPressed(KeyEvent.KEYCODE_8);
647 return;
648 }
649 case R.id.nine: {
650 playTone(ToneGenerator.TONE_DTMF_9);
651 keyPressed(KeyEvent.KEYCODE_9);
652 return;
653 }
654 case R.id.zero: {
655 playTone(ToneGenerator.TONE_DTMF_0);
656 keyPressed(KeyEvent.KEYCODE_0);
657 return;
658 }
659 case R.id.pound: {
660 playTone(ToneGenerator.TONE_DTMF_P);
661 keyPressed(KeyEvent.KEYCODE_POUND);
662 return;
663 }
664 case R.id.star: {
665 playTone(ToneGenerator.TONE_DTMF_S);
666 keyPressed(KeyEvent.KEYCODE_STAR);
667 return;
668 }
669 case R.id.backspace: {
670 keyPressed(KeyEvent.KEYCODE_DEL);
671 return;
672 }
David Brown3d07e6d2009-08-04 20:30:09 -0700673 case R.id.dialButton:
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800674 case R.id.digits: {
David Brownc29c7ab2009-07-07 16:00:18 -0700675 vibrate(); // Vibrate here too, just like we do for the regular keys
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800676 placeCall();
677 return;
678 }
Nicolas Catania80bda0f2009-09-19 09:17:14 -0700679 case R.id.voicemailButton: {
680 callVoicemail();
681 vibrate();
682 return;
683 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800684 }
685 }
686
687 public boolean onLongClick(View view) {
688 final Editable digits = mDigits.getText();
689 int id = view.getId();
690 switch (id) {
691 case R.id.backspace: {
692 digits.clear();
693 return true;
694 }
695 case R.id.one: {
696 if (digits.length() == 0) {
697 callVoicemail();
698 return true;
699 }
700 return false;
701 }
702 case R.id.zero: {
703 keyPressed(KeyEvent.KEYCODE_PLUS);
704 return true;
705 }
706 }
707 return false;
708 }
709
710 void callVoicemail() {
711 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
712 Uri.fromParts("voicemail", "", null));
713 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
714 startActivity(intent);
715 mDigits.getText().clear();
716 finish();
717 }
718
719 void placeCall() {
720 final String number = mDigits.getText().toString();
721 if (number == null || !TextUtils.isGraphic(number)) {
722 // There is no number entered.
723 playTone(ToneGenerator.TONE_PROP_NACK);
724 return;
725 }
726 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
727 Uri.fromParts("tel", number, null));
728 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
729 startActivity(intent);
730 mDigits.getText().clear();
731 finish();
732 }
733
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800734
735 /**
David Brown22f615f2009-06-25 16:19:19 -0700736 * Plays the specified tone for TONE_LENGTH_MS milliseconds.
737 *
738 * The tone is played locally, using the audio stream for phone calls.
739 * Tones are played only if the "Audible touch tones" user preference
740 * is checked, and are NOT played if the device is in silent mode.
741 *
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800742 * @param tone a tone code from {@link ToneGenerator}
743 */
744 void playTone(int tone) {
745 // if local tone playback is disabled, just return.
746 if (!mDTMFToneEnabled) {
747 return;
748 }
David Brown22f615f2009-06-25 16:19:19 -0700749
750 // Also do nothing if the phone is in silent mode.
751 // We need to re-check the ringer mode for *every* playTone()
752 // call, rather than keeping a local flag that's updated in
753 // onResume(), since it's possible to toggle silent mode without
754 // leaving the current activity (via the ENDCALL-longpress menu.)
755 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
David Brownd5a15302009-07-20 16:39:47 -0700756 int ringerMode = audioManager.getRingerMode();
757 if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
758 || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
David Brown22f615f2009-06-25 16:19:19 -0700759 return;
760 }
761
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800762 synchronized(mToneGeneratorLock) {
763 if (mToneGenerator == null) {
764 Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
765 return;
766 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700767
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800768 // Start the new tone (will stop any playing tone)
Eric Laurent8487fed2009-09-07 08:45:14 -0700769 mToneGenerator.startTone(tone, TONE_LENGTH_MS);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800770 }
771 }
772
773 /**
774 * Brings up the "dialpad chooser" UI in place of the usual Dialer
775 * elements (the textfield/button and the dialpad underneath).
776 *
777 * We show this UI if the user brings up the Dialer while a call is
778 * already in progress, since there's a good chance we got here
779 * accidentally (and the user really wanted the in-call dialpad instead).
780 * So in this situation we display an intermediate UI that lets the user
781 * explicitly choose between the in-call dialpad ("Use touch tone
782 * keypad") and the regular Dialer ("Add call"). (Or, the option "Return
783 * to call in progress" just goes back to the in-call UI with no dialpad
784 * at all.)
785 *
786 * @param enabled If true, show the "dialpad chooser" instead
787 * of the regular Dialer UI
788 */
789 private void showDialpadChooser(boolean enabled) {
790 if (enabled) {
791 // Log.i(TAG, "Showing dialpad chooser!");
792 mDigitsAndBackspace.setVisibility(View.GONE);
793 if (mDialpad != null) mDialpad.setVisibility(View.GONE);
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700794 mVoicemailDialAndBackspaceRow.setVisibility(View.GONE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800795 mDialpadChooser.setVisibility(View.VISIBLE);
796
797 // Instantiate the DialpadChooserAdapter and hook it up to the
798 // ListView. We do this only once.
799 if (mDialpadChooserAdapter == null) {
800 mDialpadChooserAdapter = new DialpadChooserAdapter(this);
801 mDialpadChooser.setAdapter(mDialpadChooserAdapter);
802 }
803 } else {
804 // Log.i(TAG, "Displaying normal Dialer UI.");
805 mDigitsAndBackspace.setVisibility(View.VISIBLE);
806 if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700807 mVoicemailDialAndBackspaceRow.setVisibility(View.VISIBLE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800808 mDialpadChooser.setVisibility(View.GONE);
809 }
810 }
811
812 /**
813 * @return true if we're currently showing the "dialpad chooser" UI.
814 */
815 private boolean dialpadChooserVisible() {
816 return mDialpadChooser.getVisibility() == View.VISIBLE;
817 }
818
819 /**
820 * Simple list adapter, binding to an icon + text label
821 * for each item in the "dialpad chooser" list.
822 */
823 private static class DialpadChooserAdapter extends BaseAdapter {
824 private LayoutInflater mInflater;
825
826 // Simple struct for a single "choice" item.
827 static class ChoiceItem {
828 String text;
829 Bitmap icon;
830 int id;
831
832 public ChoiceItem(String s, Bitmap b, int i) {
833 text = s;
834 icon = b;
835 id = i;
836 }
837 }
838
839 // IDs for the possible "choices":
840 static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
841 static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
842 static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
843
844 private static final int NUM_ITEMS = 3;
845 private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
846
847 public DialpadChooserAdapter(Context context) {
848 // Cache the LayoutInflate to avoid asking for a new one each time.
849 mInflater = LayoutInflater.from(context);
850
851 // Initialize the possible choices.
852 // TODO: could this be specified entirely in XML?
853
854 // - "Use touch tone keypad"
855 mChoiceItems[0] = new ChoiceItem(
856 context.getString(R.string.dialer_useDtmfDialpad),
857 BitmapFactory.decodeResource(context.getResources(),
858 R.drawable.ic_dialer_fork_tt_keypad),
859 DIALPAD_CHOICE_USE_DTMF_DIALPAD);
860
861 // - "Return to call in progress"
862 mChoiceItems[1] = new ChoiceItem(
863 context.getString(R.string.dialer_returnToInCallScreen),
864 BitmapFactory.decodeResource(context.getResources(),
865 R.drawable.ic_dialer_fork_current_call),
866 DIALPAD_CHOICE_RETURN_TO_CALL);
867
868 // - "Add call"
869 mChoiceItems[2] = new ChoiceItem(
870 context.getString(R.string.dialer_addAnotherCall),
871 BitmapFactory.decodeResource(context.getResources(),
872 R.drawable.ic_dialer_fork_add_call),
873 DIALPAD_CHOICE_ADD_NEW_CALL);
874 }
875
876 public int getCount() {
877 return NUM_ITEMS;
878 }
879
880 /**
881 * Return the ChoiceItem for a given position.
882 */
883 public Object getItem(int position) {
884 return mChoiceItems[position];
885 }
886
887 /**
888 * Return a unique ID for each possible choice.
889 */
890 public long getItemId(int position) {
891 return position;
892 }
893
894 /**
895 * Make a view for each row.
896 */
897 public View getView(int position, View convertView, ViewGroup parent) {
898 // When convertView is non-null, we can reuse it (there's no need
899 // to reinflate it.)
900 if (convertView == null) {
901 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
902 }
903
904 TextView text = (TextView) convertView.findViewById(R.id.text);
905 text.setText(mChoiceItems[position].text);
906
907 ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
908 icon.setImageBitmap(mChoiceItems[position].icon);
909
910 return convertView;
911 }
912 }
913
914 /**
915 * Handle clicks from the dialpad chooser.
916 */
917 public void onItemClick(AdapterView parent, View v, int position, long id) {
918 DialpadChooserAdapter.ChoiceItem item =
919 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
920 int itemId = item.id;
921 switch (itemId) {
922 case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
923 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
924 // Fire off an intent to go back to the in-call UI
925 // with the dialpad visible.
926 returnToInCallScreen(true);
927 break;
928
929 case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
930 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
931 // Fire off an intent to go back to the in-call UI
932 // (with the dialpad hidden).
933 returnToInCallScreen(false);
934 break;
935
936 case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
937 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
938 // Ok, guess the user really did want to be here (in the
939 // regular Dialer) after all. Bring back the normal Dialer UI.
940 showDialpadChooser(false);
941 break;
942
943 default:
944 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
945 break;
946 }
947 }
948
949 /**
950 * Returns to the in-call UI (where there's presumably a call in
951 * progress) in response to the user selecting "use touch tone keypad"
952 * or "return to call" from the dialpad chooser.
953 */
954 private void returnToInCallScreen(boolean showDialpad) {
955 try {
956 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
957 if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
958 } catch (RemoteException e) {
959 Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
960 }
961
962 // Finally, finish() ourselves so that we don't stay on the
963 // activity stack.
964 // Note that we do this whether or not the showCallScreenWithDialpad()
965 // call above had any effect or not! (That call is a no-op if the
966 // phone is idle, which can happen if the current call ends while
967 // the dialpad chooser is up. In this case we can't show the
968 // InCallScreen, and there's no point staying here in the Dialer,
969 // so we just take the user back where he came from...)
970 finish();
971 }
972
973 /**
974 * @return true if the phone is "in use", meaning that at least one line
975 * is active (ie. off hook or ringing or dialing).
976 */
977 private boolean phoneIsInUse() {
978 boolean phoneInUse = false;
979 try {
980 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
981 if (phone != null) phoneInUse = !phone.isIdle();
982 } catch (RemoteException e) {
983 Log.w(TAG, "phone.isIdle() failed", e);
984 }
985 return phoneInUse;
986 }
David Brownc29c7ab2009-07-07 16:00:18 -0700987
988 /**
989 * Triggers haptic feedback (if enabled) for dialer key presses.
990 */
991 private synchronized void vibrate() {
992 if (!mVibrateOn) {
993 return;
994 }
995 if (mVibrator == null) {
996 mVibrator = new Vibrator();
997 }
998 mVibrator.vibrate(mVibrateDuration);
999 }
Reli Talc2a2a512009-06-10 16:48:00 -04001000
1001 /**
1002 * Returns true whenever any one of the options from the menu is selected.
1003 * Code changes to support dialpad options
1004 */
1005 @Override
1006 public boolean onOptionsItemSelected(MenuItem item) {
1007 switch (item.getItemId()) {
1008 case MENU_2S_PAUSE:
1009 updateDialString(",");
1010 return true;
1011 case MENU_WAIT:
1012 updateDialString(";");
1013 return true;
1014 }
1015 return false;
1016 }
1017
1018 /**
1019 * Updates the dial string (mDigits) after inserting a Pause character (,)
1020 * or Wait character (;).
1021 */
1022 private void updateDialString(String newDigits) {
1023 int selectionStart;
1024 int selectionEnd;
1025
1026 // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
Eric Fischer686782e2009-09-10 17:57:45 -07001027 int anchor = mDigits.getSelectionStart();
1028 int point = mDigits.getSelectionEnd();
1029
1030 selectionStart = Math.min(anchor, point);
1031 selectionEnd = Math.max(anchor, point);
Reli Talc2a2a512009-06-10 16:48:00 -04001032
1033 Editable digits = mDigits.getText();
1034 if (selectionStart != -1 ) {
1035 if (selectionStart == selectionEnd) {
1036 // then there is no selection. So insert the pause at this
1037 // position and update the mDigits.
1038 digits.replace(selectionStart, selectionStart, newDigits);
1039 } else {
Eric Fischer1e2d3a22009-09-17 10:53:10 -07001040 digits.replace(selectionStart, selectionEnd, newDigits);
Reli Talc2a2a512009-06-10 16:48:00 -04001041 }
1042 } else {
1043 int len = mDigits.length();
1044 digits.replace(len, len, newDigits);
1045 }
1046 }
1047
1048 /**
Nicolas Cataniadea164e2009-09-18 06:26:16 -07001049 * Update the enabledness of the "Dial" button if applicable.
1050 */
1051 private void updateDialButtonStateEnabledAttr() {
1052 if (mDialButton != null) {
1053 mDialButton.setEnabled(mDigits.length() != 0);
1054 }
1055 }
1056
1057 /**
Nicolas Catania80bda0f2009-09-19 09:17:14 -07001058 * Check if voicemail is enabled/accessible.
1059 */
1060 private void initVoicemailButton() {
1061 boolean hasVoicemail = false;
1062 try {
1063 hasVoicemail = TelephonyManager.getDefault().getVoiceMailNumber() != null;
1064 } catch (SecurityException se) {
1065 // Possibly no READ_PHONE_STATE privilege.
1066 }
1067
1068 mVoicemailButton = mVoicemailDialAndBackspaceRow.findViewById(R.id.voicemailButton);
1069 if (hasVoicemail) {
1070 mVoicemailButton.setOnClickListener(this);
1071 } else {
1072 mVoicemailButton.setEnabled(false);
1073 }
1074 }
1075
1076 /**
Reli Talc2a2a512009-06-10 16:48:00 -04001077 * This function return true if Wait menu item can be shown
1078 * otherwise returns false. Assumes the passed string is non-empty
1079 * and the 0th index check is not required.
1080 */
1081 private boolean showWait(int start, int end, String digits) {
1082 if (start == end) {
1083 // visible false in this case
1084 if (start > digits.length()) return false;
1085
1086 // preceding char is ';', so visible should be false
1087 if (digits.charAt(start-1) == ';') return false;
1088
1089 // next char is ';', so visible should be false
1090 if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
1091 } else {
1092 // visible false in this case
1093 if (start > digits.length() || end > digits.length()) return false;
1094
1095 // In this case we need to just check for ';' preceding to start
1096 // or next to end
1097 if (digits.charAt(start-1) == ';') return false;
1098 }
1099 return true;
1100 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08001101}