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