blob: 428cc8ea6593b13ff488b7d4e23817a6d0582f61 [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;
David Brown3d07e6d2009-08-04 20:30:09 -070098 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
173 // Update the enabledness of the "Dial" button
174 if (mDialButton != null) {
175 mDialButton.setEnabled(mDigits.length() != 0);
176 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800177 }
178
179 @Override
180 protected void onCreate(Bundle icicle) {
181 super.onCreate(icicle);
182
183 // Set the content view
184 setContentView(getContentViewResource());
185
186 // Load up the resources for the text field and delete button
187 Resources r = getResources();
188 mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
189 //mDigitsBackground.setDither(true);
190 mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
191 //mDigitsEmptyBackground.setDither(true);
192 mDeleteBackground = r.getDrawable(R.drawable.btn_dial_delete_active);
193 //mDeleteBackground.setDither(true);
194 mDeleteEmptyBackground = r.getDrawable(R.drawable.btn_dial_delete);
195 //mDeleteEmptyBackground.setDither(true);
196
197 mDigits = (EditText) findViewById(R.id.digits);
198 mDigits.setKeyListener(DialerKeyListener.getInstance());
199 mDigits.setOnClickListener(this);
200 mDigits.setOnKeyListener(this);
201 maybeAddNumberFormatting();
202
203 // Check for the presence of the keypad
204 View view = findViewById(R.id.one);
205 if (view != null) {
206 setupKeypad();
207 }
208
David Brown3d07e6d2009-08-04 20:30:09 -0700209 // Check whether we should show the onscreen "Dial" button.
210 if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
211 mDialButton = findViewById(R.id.dialButton);
212 mDialButton.setVisibility(View.VISIBLE); // It's GONE by default
213 mDialButton.setOnClickListener(this);
214 }
215
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800216 view = findViewById(R.id.backspace);
217 view.setOnClickListener(this);
218 view.setOnLongClickListener(this);
219 mDelete = view;
220
221 mDigitsAndBackspace = (View) findViewById(R.id.digitsAndBackspace);
222 mDialpad = (View) findViewById(R.id.dialpad); // This is null in landscape mode
223
224 // Set up the "dialpad chooser" UI; see showDialpadChooser().
225 mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
226 mDialpadChooser.setOnItemClickListener(this);
227
228 if (!resolveIntent() && icicle != null) {
229 super.onRestoreInstanceState(icicle);
230 }
231
232 // If the mToneGenerator creation fails, just continue without it. It is
233 // a local audio signal, and is not as important as the dtmf tone itself.
234 synchronized (mToneGeneratorLock) {
235 if (mToneGenerator == null) {
236 try {
Eric Laurentd9efc872009-07-17 11:52:06 -0700237 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800238 TONE_RELATIVE_VOLUME);
239 } catch (RuntimeException e) {
240 Log.w(TAG, "Exception caught while creating local tone generator: " + e);
241 mToneGenerator = null;
242 }
243 }
244 }
David Brownc29c7ab2009-07-07 16:00:18 -0700245
246 // Initialize vibration parameters.
247 // TODO: We might eventually need to make mVibrateOn come from a
248 // user preference rather than a per-platform resource, in which
249 // case we would need to update it in onResume() rather than here.
250 mVibrateOn = r.getBoolean(R.bool.config_enable_dialer_key_vibration);
251 mVibrateDuration = (long) r.getInteger(R.integer.config_dialer_key_vibrate_duration);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800252 }
253
254 @Override
255 protected void onDestroy() {
256 super.onDestroy();
257 synchronized(mToneGeneratorLock) {
258 if (mToneGenerator != null) {
259 mToneStopper.removeMessages(STOP_TONE);
260 mToneGenerator.release();
261 mToneGenerator = null;
262 }
263 }
264 }
265
266 @Override
267 protected void onRestoreInstanceState(Bundle icicle) {
268 // Do nothing, state is restored in onCreate() if needed
269 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700270
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800271 protected void maybeAddNumberFormatting() {
272 mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
273 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700274
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800275 /**
Eric Laurentd9efc872009-07-17 11:52:06 -0700276 * Overridden by subclasses to control the resource used by the content view.
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800277 */
278 protected int getContentViewResource() {
279 return R.layout.twelve_key_dialer;
280 }
281
282 private boolean resolveIntent() {
283 boolean ignoreState = false;
284
285 // Find the proper intent
286 final Intent intent;
287 if (isChild()) {
288 intent = getParent().getIntent();
289 ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
290 } else {
291 intent = getIntent();
292 }
293 // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
294
295 // by default we are not adding a call.
296 mIsAddCallMode = false;
297
298 // By default we don't show the "dialpad chooser" UI.
299 boolean needToShowDialpadChooser = false;
300
301 // Resolve the intent
302 final String action = intent.getAction();
303 if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
304 // see if we are "adding a call" from the InCallScreen; false by default.
305 mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
306 Uri uri = intent.getData();
307 if (uri != null) {
308 if ("tel".equals(uri.getScheme())) {
309 // Put the requested number into the input area
310 String data = uri.getSchemeSpecificPart();
311 setFormattedDigits(data);
312 } else {
313 String type = intent.getType();
314 if (People.CONTENT_ITEM_TYPE.equals(type)
315 || Phones.CONTENT_ITEM_TYPE.equals(type)) {
316 // Query the phone number
317 Cursor c = getContentResolver().query(intent.getData(),
318 new String[] {PhonesColumns.NUMBER}, null, null, null);
319 if (c != null) {
320 if (c.moveToFirst()) {
321 // Put the number into the input area
322 setFormattedDigits(c.getString(0));
323 }
324 c.close();
325 }
326 }
327 }
328 }
329 } else if (Intent.ACTION_MAIN.equals(action)) {
330 // The MAIN action means we're bringing up a blank dialer
331 // (e.g. by selecting the Home shortcut, or tabbing over from
332 // Contacts or Call log.)
333 //
334 // At this point, IF there's already an active call, there's a
335 // good chance that the user got here accidentally (but really
336 // wanted the in-call dialpad instead). So we bring up an
337 // intermediate UI to make the user confirm what they really
338 // want to do.
339 if (phoneIsInUse()) {
340 // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
341 needToShowDialpadChooser = true;
342 }
343 }
344
345 // Bring up the "dialpad chooser" IFF we need to make the user
346 // confirm which dialpad they really want.
347 showDialpadChooser(needToShowDialpadChooser);
348
349 return ignoreState;
350 }
351
352 protected void setFormattedDigits(String data) {
353 // strip the non-dialable numbers out of the data string.
354 String dialString = PhoneNumberUtils.extractNetworkPortion(data);
355 dialString = PhoneNumberUtils.formatNumber(dialString);
356 if (!TextUtils.isEmpty(dialString)) {
357 Editable digits = mDigits.getText();
358 digits.replace(0, digits.length(), dialString);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700359 // for some reason this isn't getting called in the digits.replace call above..
360 // but in any case, this will make sure the background drawable looks right
361 afterTextChanged(digits);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800362 }
363 }
364
365 @Override
366 protected void onNewIntent(Intent newIntent) {
367 setIntent(newIntent);
368 resolveIntent();
369 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700370
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800371 @Override
372 protected void onPostCreate(Bundle savedInstanceState) {
373 super.onPostCreate(savedInstanceState);
374
375 // This can't be done in onCreate(), since the auto-restoring of the digits
376 // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
377 // is called. This method will be called every time the activity is created, and
378 // will always happen after onRestoreSavedInstanceState().
379 mDigits.addTextChangedListener(this);
380 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700381
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800382 private void setupKeypad() {
383 // Setup the listeners for the buttons
384 View view = findViewById(R.id.one);
385 view.setOnClickListener(this);
386 view.setOnLongClickListener(this);
387
388 findViewById(R.id.two).setOnClickListener(this);
389 findViewById(R.id.three).setOnClickListener(this);
390 findViewById(R.id.four).setOnClickListener(this);
391 findViewById(R.id.five).setOnClickListener(this);
392 findViewById(R.id.six).setOnClickListener(this);
393 findViewById(R.id.seven).setOnClickListener(this);
394 findViewById(R.id.eight).setOnClickListener(this);
395 findViewById(R.id.nine).setOnClickListener(this);
396 findViewById(R.id.star).setOnClickListener(this);
397
398 view = findViewById(R.id.zero);
399 view.setOnClickListener(this);
400 view.setOnLongClickListener(this);
401
402 findViewById(R.id.pound).setOnClickListener(this);
403 }
404
405 @Override
406 protected void onResume() {
407 super.onResume();
David Brownc29c7ab2009-07-07 16:00:18 -0700408
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800409 // retrieve the DTMF tone play back setting.
410 mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
411 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
412
Eric Laurentd9efc872009-07-17 11:52:06 -0700413 // if the mToneGenerator creation fails, just continue without it. It is
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800414 // a local audio signal, and is not as important as the dtmf tone itself.
415 synchronized(mToneGeneratorLock) {
416 if (mToneGenerator == null) {
417 try {
Eric Laurentd9efc872009-07-17 11:52:06 -0700418 mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800419 TONE_RELATIVE_VOLUME);
420 } catch (RuntimeException e) {
421 Log.w(TAG, "Exception caught while creating local tone generator: " + e);
422 mToneGenerator = null;
423 }
424 }
425 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700426
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800427 Activity parent = getParent();
428 // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
429 // digits in the dialer field.
430 if (parent != null && parent instanceof DialtactsActivity) {
431 Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
432 if (dialUri != null) {
433 resolveIntent();
434 }
435 }
436
437 // While we're in the foreground, listen for phone state changes,
438 // purely so that we can take down the "dialpad chooser" if the
439 // phone becomes idle while the chooser UI is visible.
440 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
441 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
442
443 // Potentially show hint text in the mDigits field when the user
444 // hasn't typed any digits yet. (If there's already an active call,
445 // this hint text will remind the user that he's about to add a new
446 // call.)
447 //
448 // TODO: consider adding better UI for the case where *both* lines
449 // are currently in use. (Right now we let the user try to add
450 // another call, but that call is guaranteed to fail. Perhaps the
451 // entire dialer UI should be disabled instead.)
452 if (phoneIsInUse()) {
453 mDigits.setHint(R.string.dialerDialpadHintText);
454 } else {
455 // Common case; no hint necessary.
456 mDigits.setHint(null);
457
458 // Also, a sanity-check: the "dialpad chooser" UI should NEVER
459 // be visible if the phone is idle!
460 showDialpadChooser(false);
461 }
462 }
463
464 @Override
Karl Rosaenf46bc312009-03-24 18:20:48 -0700465 public void onWindowFocusChanged(boolean hasFocus) {
466 if (hasFocus) {
467 // Hide soft keyboard, if visible (it's fugly over button dialer).
468 // The only known case where this will be true is when launching the dialer with
469 // ACTION_DIAL via a soft keyboard. we dismiss it here because we don't
470 // have a window token yet in onCreate / onNewIntent
471 InputMethodManager inputMethodManager = (InputMethodManager)
472 getSystemService(Context.INPUT_METHOD_SERVICE);
Eric Laurentd9efc872009-07-17 11:52:06 -0700473 inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700474 }
475 }
476
477 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800478 protected void onPause() {
479 super.onPause();
480
481 // Stop listening for phone state changes.
482 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
483 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
484
485 synchronized(mToneGeneratorLock) {
486 if (mToneGenerator != null) {
487 mToneStopper.removeMessages(STOP_TONE);
488 mToneGenerator.release();
489 mToneGenerator = null;
490 }
491 }
492 }
493
494 @Override
495 public boolean onCreateOptionsMenu(Menu menu) {
Reli Talc2a2a512009-06-10 16:48:00 -0400496 mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800497 .setIcon(android.R.drawable.ic_menu_add);
Reli Talc2a2a512009-06-10 16:48:00 -0400498 m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
499 .setIcon(R.drawable.ic_menu_2sec_pause);
500 mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
501 .setIcon(R.drawable.ic_menu_wait);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800502 return true;
503 }
504
505 @Override
506 public boolean onPrepareOptionsMenu(Menu menu) {
507 // We never show a menu if the "choose dialpad" UI is up.
508 if (dialpadChooserVisible()) {
509 return false;
510 }
511
512 CharSequence digits = mDigits.getText();
513 if (digits == null || !TextUtils.isGraphic(digits)) {
514 mAddToContactMenuItem.setVisible(false);
Reli Talc2a2a512009-06-10 16:48:00 -0400515 m2SecPauseMenuItem.setVisible(false);
516 mWaitMenuItem.setVisible(false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800517 } else {
518 // Put the current digits string into an intent
519 Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
520 intent.putExtra(Insert.PHONE, mDigits.getText());
521 intent.setType(People.CONTENT_ITEM_TYPE);
522 mAddToContactMenuItem.setIntent(intent);
523 mAddToContactMenuItem.setVisible(true);
Reli Talc2a2a512009-06-10 16:48:00 -0400524
525 // Check out whether to show Pause & Wait option menu items
526 int selectionStart;
527 int selectionEnd;
528 String strDigits = digits.toString();
529
530 selectionStart = mDigits.getSelectionStart();
531 selectionEnd = mDigits.getSelectionEnd();
532
533 if (selectionStart != -1) {
534 if (selectionStart > selectionEnd) {
535 // swap it as we want start to be less then end
536 int tmp = selectionStart;
537 selectionStart = selectionEnd;
538 selectionEnd = tmp;
539 }
540
541 if (selectionStart != 0) {
542 // Pause can be visible if cursor is not in the begining
543 m2SecPauseMenuItem.setVisible(true);
544
545 // For Wait to be visible set of condition to meet
546 mWaitMenuItem.setVisible(showWait(selectionStart,
547 selectionEnd, strDigits));
548 } else {
549 // cursor in the beginning both pause and wait to be invisible
550 m2SecPauseMenuItem.setVisible(false);
551 mWaitMenuItem.setVisible(false);
552 }
553 } else {
554 // cursor is not selected so assume new digit is added to the end
555 int strLength = strDigits.length();
556 mWaitMenuItem.setVisible(showWait(strLength,
557 strLength, strDigits));
558 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800559 }
560 return true;
561 }
562
563 @Override
564 public boolean onKeyDown(int keyCode, KeyEvent event) {
565 switch (keyCode) {
566 case KeyEvent.KEYCODE_CALL: {
567 long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
568 if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
569 // Launch voice dialer
570 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
571 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
572 try {
573 startActivity(intent);
574 } catch (ActivityNotFoundException e) {
575 }
576 }
577 return true;
578 }
579 case KeyEvent.KEYCODE_1: {
Eric Laurentd9efc872009-07-17 11:52:06 -0700580 long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800581 if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
582 // Long press detected, call voice mail
583 callVoicemail();
584 }
585 return true;
586 }
587 }
588 return super.onKeyDown(keyCode, event);
589 }
590
591 @Override
592 public boolean onKeyUp(int keyCode, KeyEvent event) {
593 switch (keyCode) {
594 case KeyEvent.KEYCODE_CALL: {
595 if (mIsAddCallMode && (TextUtils.isEmpty(mDigits.getText().toString()))) {
596 // if we are adding a call from the InCallScreen and the phone
597 // number entered is empty, we just close the dialer to expose
598 // the InCallScreen under it.
599 finish();
600 } else {
601 // otherwise, we place the call.
602 placeCall();
603 }
604 return true;
605 }
606 }
607 return super.onKeyUp(keyCode, event);
608 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700609
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800610 private void keyPressed(int keyCode) {
David Brownc29c7ab2009-07-07 16:00:18 -0700611 vibrate();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800612 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
613 mDigits.onKeyDown(keyCode, event);
614 }
615
616 public boolean onKey(View view, int keyCode, KeyEvent event) {
617 switch (view.getId()) {
618 case R.id.digits:
619 if (keyCode == KeyEvent.KEYCODE_ENTER) {
620 placeCall();
621 return true;
622 }
623 break;
624 }
625 return false;
626 }
627
628 public void onClick(View view) {
629 switch (view.getId()) {
630 case R.id.one: {
631 playTone(ToneGenerator.TONE_DTMF_1);
632 keyPressed(KeyEvent.KEYCODE_1);
633 return;
634 }
635 case R.id.two: {
636 playTone(ToneGenerator.TONE_DTMF_2);
637 keyPressed(KeyEvent.KEYCODE_2);
638 return;
639 }
640 case R.id.three: {
641 playTone(ToneGenerator.TONE_DTMF_3);
642 keyPressed(KeyEvent.KEYCODE_3);
643 return;
644 }
645 case R.id.four: {
646 playTone(ToneGenerator.TONE_DTMF_4);
647 keyPressed(KeyEvent.KEYCODE_4);
648 return;
649 }
650 case R.id.five: {
651 playTone(ToneGenerator.TONE_DTMF_5);
652 keyPressed(KeyEvent.KEYCODE_5);
653 return;
654 }
655 case R.id.six: {
656 playTone(ToneGenerator.TONE_DTMF_6);
657 keyPressed(KeyEvent.KEYCODE_6);
658 return;
659 }
660 case R.id.seven: {
661 playTone(ToneGenerator.TONE_DTMF_7);
662 keyPressed(KeyEvent.KEYCODE_7);
663 return;
664 }
665 case R.id.eight: {
666 playTone(ToneGenerator.TONE_DTMF_8);
667 keyPressed(KeyEvent.KEYCODE_8);
668 return;
669 }
670 case R.id.nine: {
671 playTone(ToneGenerator.TONE_DTMF_9);
672 keyPressed(KeyEvent.KEYCODE_9);
673 return;
674 }
675 case R.id.zero: {
676 playTone(ToneGenerator.TONE_DTMF_0);
677 keyPressed(KeyEvent.KEYCODE_0);
678 return;
679 }
680 case R.id.pound: {
681 playTone(ToneGenerator.TONE_DTMF_P);
682 keyPressed(KeyEvent.KEYCODE_POUND);
683 return;
684 }
685 case R.id.star: {
686 playTone(ToneGenerator.TONE_DTMF_S);
687 keyPressed(KeyEvent.KEYCODE_STAR);
688 return;
689 }
690 case R.id.backspace: {
691 keyPressed(KeyEvent.KEYCODE_DEL);
692 return;
693 }
David Brown3d07e6d2009-08-04 20:30:09 -0700694 case R.id.dialButton:
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800695 case R.id.digits: {
David Brownc29c7ab2009-07-07 16:00:18 -0700696 vibrate(); // Vibrate here too, just like we do for the regular keys
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800697 placeCall();
698 return;
699 }
700 }
701 }
702
703 public boolean onLongClick(View view) {
704 final Editable digits = mDigits.getText();
705 int id = view.getId();
706 switch (id) {
707 case R.id.backspace: {
708 digits.clear();
709 return true;
710 }
711 case R.id.one: {
712 if (digits.length() == 0) {
713 callVoicemail();
714 return true;
715 }
716 return false;
717 }
718 case R.id.zero: {
719 keyPressed(KeyEvent.KEYCODE_PLUS);
720 return true;
721 }
722 }
723 return false;
724 }
725
726 void callVoicemail() {
727 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
728 Uri.fromParts("voicemail", "", null));
729 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
730 startActivity(intent);
731 mDigits.getText().clear();
732 finish();
733 }
734
735 void placeCall() {
736 final String number = mDigits.getText().toString();
737 if (number == null || !TextUtils.isGraphic(number)) {
738 // There is no number entered.
739 playTone(ToneGenerator.TONE_PROP_NACK);
740 return;
741 }
742 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
743 Uri.fromParts("tel", number, null));
744 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
745 startActivity(intent);
746 mDigits.getText().clear();
747 finish();
748 }
749
750 Handler mToneStopper = new Handler() {
751 @Override
752 public void handleMessage(Message msg) {
753 switch (msg.what) {
754 case STOP_TONE:
755 synchronized(mToneGeneratorLock) {
756 if (mToneGenerator == null) {
757 Log.w(TAG, "mToneStopper: mToneGenerator == null");
758 } else {
759 mToneGenerator.stopTone();
760 }
761 }
762 break;
763 }
764 }
765 };
766
767 /**
David Brown22f615f2009-06-25 16:19:19 -0700768 * Plays the specified tone for TONE_LENGTH_MS milliseconds.
769 *
770 * The tone is played locally, using the audio stream for phone calls.
771 * Tones are played only if the "Audible touch tones" user preference
772 * is checked, and are NOT played if the device is in silent mode.
773 *
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800774 * @param tone a tone code from {@link ToneGenerator}
775 */
776 void playTone(int tone) {
777 // if local tone playback is disabled, just return.
778 if (!mDTMFToneEnabled) {
779 return;
780 }
David Brown22f615f2009-06-25 16:19:19 -0700781
782 // Also do nothing if the phone is in silent mode.
783 // We need to re-check the ringer mode for *every* playTone()
784 // call, rather than keeping a local flag that's updated in
785 // onResume(), since it's possible to toggle silent mode without
786 // leaving the current activity (via the ENDCALL-longpress menu.)
787 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
David Brownd5a15302009-07-20 16:39:47 -0700788 int ringerMode = audioManager.getRingerMode();
789 if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
790 || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
David Brown22f615f2009-06-25 16:19:19 -0700791 return;
792 }
793
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800794 synchronized(mToneGeneratorLock) {
795 if (mToneGenerator == null) {
796 Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
797 return;
798 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700799
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800800 // Remove pending STOP_TONE messages
801 mToneStopper.removeMessages(STOP_TONE);
Eric Laurentd9efc872009-07-17 11:52:06 -0700802
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800803 // Start the new tone (will stop any playing tone)
804 mToneGenerator.startTone(tone);
805 mToneStopper.sendEmptyMessageDelayed(STOP_TONE, TONE_LENGTH_MS);
806 }
807 }
808
809 /**
810 * Brings up the "dialpad chooser" UI in place of the usual Dialer
811 * elements (the textfield/button and the dialpad underneath).
812 *
813 * We show this UI if the user brings up the Dialer while a call is
814 * already in progress, since there's a good chance we got here
815 * accidentally (and the user really wanted the in-call dialpad instead).
816 * So in this situation we display an intermediate UI that lets the user
817 * explicitly choose between the in-call dialpad ("Use touch tone
818 * keypad") and the regular Dialer ("Add call"). (Or, the option "Return
819 * to call in progress" just goes back to the in-call UI with no dialpad
820 * at all.)
821 *
822 * @param enabled If true, show the "dialpad chooser" instead
823 * of the regular Dialer UI
824 */
825 private void showDialpadChooser(boolean enabled) {
826 if (enabled) {
827 // Log.i(TAG, "Showing dialpad chooser!");
828 mDigitsAndBackspace.setVisibility(View.GONE);
829 if (mDialpad != null) mDialpad.setVisibility(View.GONE);
David Brown3d07e6d2009-08-04 20:30:09 -0700830 if (mDialButton != null) mDialButton.setVisibility(View.GONE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800831 mDialpadChooser.setVisibility(View.VISIBLE);
832
833 // Instantiate the DialpadChooserAdapter and hook it up to the
834 // ListView. We do this only once.
835 if (mDialpadChooserAdapter == null) {
836 mDialpadChooserAdapter = new DialpadChooserAdapter(this);
837 mDialpadChooser.setAdapter(mDialpadChooserAdapter);
838 }
839 } else {
840 // Log.i(TAG, "Displaying normal Dialer UI.");
841 mDigitsAndBackspace.setVisibility(View.VISIBLE);
842 if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
David Brown3d07e6d2009-08-04 20:30:09 -0700843 if (mDialButton != null) mDialButton.setVisibility(View.VISIBLE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800844 mDialpadChooser.setVisibility(View.GONE);
845 }
846 }
847
848 /**
849 * @return true if we're currently showing the "dialpad chooser" UI.
850 */
851 private boolean dialpadChooserVisible() {
852 return mDialpadChooser.getVisibility() == View.VISIBLE;
853 }
854
855 /**
856 * Simple list adapter, binding to an icon + text label
857 * for each item in the "dialpad chooser" list.
858 */
859 private static class DialpadChooserAdapter extends BaseAdapter {
860 private LayoutInflater mInflater;
861
862 // Simple struct for a single "choice" item.
863 static class ChoiceItem {
864 String text;
865 Bitmap icon;
866 int id;
867
868 public ChoiceItem(String s, Bitmap b, int i) {
869 text = s;
870 icon = b;
871 id = i;
872 }
873 }
874
875 // IDs for the possible "choices":
876 static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
877 static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
878 static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
879
880 private static final int NUM_ITEMS = 3;
881 private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
882
883 public DialpadChooserAdapter(Context context) {
884 // Cache the LayoutInflate to avoid asking for a new one each time.
885 mInflater = LayoutInflater.from(context);
886
887 // Initialize the possible choices.
888 // TODO: could this be specified entirely in XML?
889
890 // - "Use touch tone keypad"
891 mChoiceItems[0] = new ChoiceItem(
892 context.getString(R.string.dialer_useDtmfDialpad),
893 BitmapFactory.decodeResource(context.getResources(),
894 R.drawable.ic_dialer_fork_tt_keypad),
895 DIALPAD_CHOICE_USE_DTMF_DIALPAD);
896
897 // - "Return to call in progress"
898 mChoiceItems[1] = new ChoiceItem(
899 context.getString(R.string.dialer_returnToInCallScreen),
900 BitmapFactory.decodeResource(context.getResources(),
901 R.drawable.ic_dialer_fork_current_call),
902 DIALPAD_CHOICE_RETURN_TO_CALL);
903
904 // - "Add call"
905 mChoiceItems[2] = new ChoiceItem(
906 context.getString(R.string.dialer_addAnotherCall),
907 BitmapFactory.decodeResource(context.getResources(),
908 R.drawable.ic_dialer_fork_add_call),
909 DIALPAD_CHOICE_ADD_NEW_CALL);
910 }
911
912 public int getCount() {
913 return NUM_ITEMS;
914 }
915
916 /**
917 * Return the ChoiceItem for a given position.
918 */
919 public Object getItem(int position) {
920 return mChoiceItems[position];
921 }
922
923 /**
924 * Return a unique ID for each possible choice.
925 */
926 public long getItemId(int position) {
927 return position;
928 }
929
930 /**
931 * Make a view for each row.
932 */
933 public View getView(int position, View convertView, ViewGroup parent) {
934 // When convertView is non-null, we can reuse it (there's no need
935 // to reinflate it.)
936 if (convertView == null) {
937 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
938 }
939
940 TextView text = (TextView) convertView.findViewById(R.id.text);
941 text.setText(mChoiceItems[position].text);
942
943 ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
944 icon.setImageBitmap(mChoiceItems[position].icon);
945
946 return convertView;
947 }
948 }
949
950 /**
951 * Handle clicks from the dialpad chooser.
952 */
953 public void onItemClick(AdapterView parent, View v, int position, long id) {
954 DialpadChooserAdapter.ChoiceItem item =
955 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
956 int itemId = item.id;
957 switch (itemId) {
958 case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
959 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
960 // Fire off an intent to go back to the in-call UI
961 // with the dialpad visible.
962 returnToInCallScreen(true);
963 break;
964
965 case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
966 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
967 // Fire off an intent to go back to the in-call UI
968 // (with the dialpad hidden).
969 returnToInCallScreen(false);
970 break;
971
972 case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
973 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
974 // Ok, guess the user really did want to be here (in the
975 // regular Dialer) after all. Bring back the normal Dialer UI.
976 showDialpadChooser(false);
977 break;
978
979 default:
980 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
981 break;
982 }
983 }
984
985 /**
986 * Returns to the in-call UI (where there's presumably a call in
987 * progress) in response to the user selecting "use touch tone keypad"
988 * or "return to call" from the dialpad chooser.
989 */
990 private void returnToInCallScreen(boolean showDialpad) {
991 try {
992 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
993 if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
994 } catch (RemoteException e) {
995 Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
996 }
997
998 // Finally, finish() ourselves so that we don't stay on the
999 // activity stack.
1000 // Note that we do this whether or not the showCallScreenWithDialpad()
1001 // call above had any effect or not! (That call is a no-op if the
1002 // phone is idle, which can happen if the current call ends while
1003 // the dialpad chooser is up. In this case we can't show the
1004 // InCallScreen, and there's no point staying here in the Dialer,
1005 // so we just take the user back where he came from...)
1006 finish();
1007 }
1008
1009 /**
1010 * @return true if the phone is "in use", meaning that at least one line
1011 * is active (ie. off hook or ringing or dialing).
1012 */
1013 private boolean phoneIsInUse() {
1014 boolean phoneInUse = false;
1015 try {
1016 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1017 if (phone != null) phoneInUse = !phone.isIdle();
1018 } catch (RemoteException e) {
1019 Log.w(TAG, "phone.isIdle() failed", e);
1020 }
1021 return phoneInUse;
1022 }
David Brownc29c7ab2009-07-07 16:00:18 -07001023
1024 /**
1025 * Triggers haptic feedback (if enabled) for dialer key presses.
1026 */
1027 private synchronized void vibrate() {
1028 if (!mVibrateOn) {
1029 return;
1030 }
1031 if (mVibrator == null) {
1032 mVibrator = new Vibrator();
1033 }
1034 mVibrator.vibrate(mVibrateDuration);
1035 }
Reli Talc2a2a512009-06-10 16:48:00 -04001036
1037 /**
1038 * Returns true whenever any one of the options from the menu is selected.
1039 * Code changes to support dialpad options
1040 */
1041 @Override
1042 public boolean onOptionsItemSelected(MenuItem item) {
1043 switch (item.getItemId()) {
1044 case MENU_2S_PAUSE:
1045 updateDialString(",");
1046 return true;
1047 case MENU_WAIT:
1048 updateDialString(";");
1049 return true;
1050 }
1051 return false;
1052 }
1053
1054 /**
1055 * Updates the dial string (mDigits) after inserting a Pause character (,)
1056 * or Wait character (;).
1057 */
1058 private void updateDialString(String newDigits) {
1059 int selectionStart;
1060 int selectionEnd;
1061
1062 // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
1063 selectionStart = mDigits.getSelectionStart();
1064 selectionEnd = mDigits.getSelectionEnd();
1065
1066 Editable digits = mDigits.getText();
1067 if (selectionStart != -1 ) {
1068 if (selectionStart == selectionEnd) {
1069 // then there is no selection. So insert the pause at this
1070 // position and update the mDigits.
1071 digits.replace(selectionStart, selectionStart, newDigits);
1072 } else {
1073 digits.delete(selectionStart, selectionEnd);
1074 digits.replace(selectionStart, selectionStart, newDigits);
1075 }
1076 } else {
1077 int len = mDigits.length();
1078 digits.replace(len, len, newDigits);
1079 }
1080 }
1081
1082 /**
1083 * This function return true if Wait menu item can be shown
1084 * otherwise returns false. Assumes the passed string is non-empty
1085 * and the 0th index check is not required.
1086 */
1087 private boolean showWait(int start, int end, String digits) {
1088 if (start == end) {
1089 // visible false in this case
1090 if (start > digits.length()) return false;
1091
1092 // preceding char is ';', so visible should be false
1093 if (digits.charAt(start-1) == ';') return false;
1094
1095 // next char is ';', so visible should be false
1096 if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
1097 } else {
1098 // visible false in this case
1099 if (start > digits.length() || end > digits.length()) return false;
1100
1101 // In this case we need to just check for ';' preceding to start
1102 // or next to end
1103 if (digits.charAt(start-1) == ';') return false;
1104 }
1105 return true;
1106 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08001107}