blob: 2d8b4e636428aee940f187dfbda1e5d4e3daff70 [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 */
Jean-Michel Trividd44f8c2009-11-10 13:00:45 -080083 private static final int TONE_RELATIVE_VOLUME = 80;
84
85 /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
86 private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_MUSIC;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080087
Nicolas Catania4c0704a2009-09-23 11:42:00 -070088 /** Play the vibrate pattern only once. */
89 private static final int VIBRATE_NO_REPEAT = -1;
90
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080091 private EditText mDigits;
92 private View mDelete;
93 private MenuItem mAddToContactMenuItem;
94 private ToneGenerator mToneGenerator;
95 private Object mToneGeneratorLock = new Object();
96 private Drawable mDigitsBackground;
97 private Drawable mDigitsEmptyBackground;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080098 private View mDialpad;
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -070099 private View mVoicemailDialAndDeleteRow;
Nicolas Catania80bda0f2009-09-19 09:17:14 -0700100 private View mVoicemailButton;
101 private View mDialButton;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800102 private ListView mDialpadChooser;
103 private DialpadChooserAdapter mDialpadChooserAdapter;
Reli Talc2a2a512009-06-10 16:48:00 -0400104 //Member variables for dialpad options
105 private MenuItem m2SecPauseMenuItem;
106 private MenuItem mWaitMenuItem;
107 private static final int MENU_ADD_CONTACTS = 1;
108 private static final int MENU_2S_PAUSE = 2;
109 private static final int MENU_WAIT = 3;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800110
111 // determines if we want to playback local DTMF tones.
112 private boolean mDTMFToneEnabled;
David Brownc29c7ab2009-07-07 16:00:18 -0700113
114 // Vibration (haptic feedback) for dialer key presses.
115 private Vibrator mVibrator;
116 private boolean mVibrateOn;
Nicolas Catania4c0704a2009-09-23 11:42:00 -0700117 private long[] mVibratePattern;
David Brownc29c7ab2009-07-07 16:00:18 -0700118
Eric Laurentd9efc872009-07-17 11:52:06 -0700119
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800120 /** Identifier for the "Add Call" intent extra. */
121 static final String ADD_CALL_MODE_KEY = "add_call_mode";
Paul Bermandbdcde22009-10-09 12:04:10 -0400122
123 /**
124 * Identifier for intent extra for sending an empty Flash message for
125 * CDMA networks. This message is used by the network to simulate a
126 * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
127 *
128 * TODO: Using an intent extra to tell the phone to send this flash is a
129 * temporary measure. To be replaced with an ITelephony call in the future.
130 * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java
131 * in Phone app until this is replaced with the ITelephony API.
132 */
133 static final String EXTRA_SEND_EMPTY_FLASH
134 = "com.android.phone.extra.SEND_EMPTY_FLASH";
135
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800136 /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
137 private boolean mIsAddCallMode;
138
139 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
140 /**
141 * Listen for phone state changes so that we can take down the
142 * "dialpad chooser" if the phone becomes idle while the
143 * chooser UI is visible.
144 */
145 @Override
146 public void onCallStateChanged(int state, String incomingNumber) {
147 // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
148 // + state + ", '" + incomingNumber + "'");
149 if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
150 // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down...");
151 // Note there's a race condition in the UI here: the
152 // dialpad chooser could conceivably disappear (on its
153 // own) at the exact moment the user was trying to select
154 // one of the choices, which would be confusing. (But at
155 // least that's better than leaving the dialpad chooser
156 // onscreen, but useless...)
157 showDialpadChooser(false);
158 }
159 }
160 };
161
162 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
163 // Do nothing
164 }
165
166 public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
167 // Do nothing
Eric Laurentd9efc872009-07-17 11:52:06 -0700168 // DTMF Tones do not need to be played here any longer -
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800169 // the DTMF dialer handles that functionality now.
170 }
171
172 public void afterTextChanged(Editable input) {
173 if (SpecialCharSequenceMgr.handleChars(this, input.toString(), mDigits)) {
174 // A special sequence was entered, clear the digits
175 mDigits.getText().clear();
176 }
177
Nicolas Catania75993762009-09-21 16:42:00 -0700178 final boolean notEmpty = mDigits.length() != 0;
179 if (notEmpty) {
180 mDigits.setBackgroundDrawable(mDigitsBackground);
181 } else {
Nicolas Catania3040fa32009-10-01 13:00:53 -0700182 mDigits.setCursorVisible(false);
Nicolas Catania75993762009-09-21 16:42:00 -0700183 mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
184 }
185
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700186 updateDialAndDeleteButtonStateEnabledAttr();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800187 }
188
189 @Override
190 protected void onCreate(Bundle icicle) {
191 super.onCreate(icicle);
192
193 // Set the content view
194 setContentView(getContentViewResource());
195
Nicolas Catania75993762009-09-21 16:42:00 -0700196 // Load up the resources for the text field.
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800197 Resources r = getResources();
198 mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800199 mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800200
201 mDigits = (EditText) findViewById(R.id.digits);
202 mDigits.setKeyListener(DialerKeyListener.getInstance());
203 mDigits.setOnClickListener(this);
204 mDigits.setOnKeyListener(this);
Nicolas Catania3040fa32009-10-01 13:00:53 -0700205
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800206 maybeAddNumberFormatting();
207
208 // Check for the presence of the keypad
209 View view = findViewById(R.id.one);
210 if (view != null) {
211 setupKeypad();
212 }
213
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700214 mVoicemailDialAndDeleteRow = findViewById(R.id.voicemailAndDialAndDelete);
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700215
Nicolas Catania80bda0f2009-09-19 09:17:14 -0700216 initVoicemailButton();
217
David Brown3d07e6d2009-08-04 20:30:09 -0700218 // Check whether we should show the onscreen "Dial" button.
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700219 mDialButton = mVoicemailDialAndDeleteRow.findViewById(R.id.dialButton);
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700220
David Brown3d07e6d2009-08-04 20:30:09 -0700221 if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
David Brown3d07e6d2009-08-04 20:30:09 -0700222 mDialButton.setOnClickListener(this);
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700223 } else {
224 mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
225 mDialButton = null;
David Brown3d07e6d2009-08-04 20:30:09 -0700226 }
227
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700228 view = mVoicemailDialAndDeleteRow.findViewById(R.id.deleteButton);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800229 view.setOnClickListener(this);
230 view.setOnLongClickListener(this);
231 mDelete = view;
232
Nicolas Catania901f8562009-10-09 11:09:45 -0700233 mDialpad = findViewById(R.id.dialpad); // This is null in landscape mode.
234
235 // In landscape we put the keyboard in phone mode.
236 // In portrait we prevent the soft keyboard to show since the
237 // dialpad acts as one already.
238 if (null == mDialpad) {
239 mDigits.setInputType(android.text.InputType.TYPE_CLASS_PHONE);
240 } else {
241 mDigits.setInputType(android.text.InputType.TYPE_NULL);
242 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800243
244 // Set up the "dialpad chooser" UI; see showDialpadChooser().
245 mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
246 mDialpadChooser.setOnItemClickListener(this);
247
248 if (!resolveIntent() && icicle != null) {
249 super.onRestoreInstanceState(icicle);
250 }
251
David Brownc29c7ab2009-07-07 16:00:18 -0700252 // TODO: We might eventually need to make mVibrateOn come from a
253 // user preference rather than a per-platform resource, in which
254 // case we would need to update it in onResume() rather than here.
Nicolas Catania4c0704a2009-09-23 11:42:00 -0700255 initVibrationPattern(r);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800256 }
257
258 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800259 protected void onRestoreInstanceState(Bundle icicle) {
260 // Do nothing, state is restored in onCreate() if needed
261 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700262
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800263 protected void maybeAddNumberFormatting() {
264 mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
265 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700266
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800267 /**
Eric Laurentd9efc872009-07-17 11:52:06 -0700268 * Overridden by subclasses to control the resource used by the content view.
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800269 */
270 protected int getContentViewResource() {
271 return R.layout.twelve_key_dialer;
272 }
273
274 private boolean resolveIntent() {
275 boolean ignoreState = false;
276
277 // Find the proper intent
278 final Intent intent;
279 if (isChild()) {
280 intent = getParent().getIntent();
281 ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
282 } else {
283 intent = getIntent();
284 }
285 // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
286
287 // by default we are not adding a call.
288 mIsAddCallMode = false;
289
290 // By default we don't show the "dialpad chooser" UI.
291 boolean needToShowDialpadChooser = false;
292
293 // Resolve the intent
294 final String action = intent.getAction();
295 if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
296 // see if we are "adding a call" from the InCallScreen; false by default.
297 mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
298 Uri uri = intent.getData();
299 if (uri != null) {
300 if ("tel".equals(uri.getScheme())) {
301 // Put the requested number into the input area
302 String data = uri.getSchemeSpecificPart();
303 setFormattedDigits(data);
304 } else {
305 String type = intent.getType();
306 if (People.CONTENT_ITEM_TYPE.equals(type)
307 || Phones.CONTENT_ITEM_TYPE.equals(type)) {
308 // Query the phone number
309 Cursor c = getContentResolver().query(intent.getData(),
310 new String[] {PhonesColumns.NUMBER}, null, null, null);
311 if (c != null) {
312 if (c.moveToFirst()) {
313 // Put the number into the input area
314 setFormattedDigits(c.getString(0));
315 }
316 c.close();
317 }
318 }
319 }
320 }
321 } else if (Intent.ACTION_MAIN.equals(action)) {
322 // The MAIN action means we're bringing up a blank dialer
323 // (e.g. by selecting the Home shortcut, or tabbing over from
324 // Contacts or Call log.)
325 //
326 // At this point, IF there's already an active call, there's a
327 // good chance that the user got here accidentally (but really
328 // wanted the in-call dialpad instead). So we bring up an
329 // intermediate UI to make the user confirm what they really
330 // want to do.
331 if (phoneIsInUse()) {
332 // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
333 needToShowDialpadChooser = true;
334 }
335 }
336
337 // Bring up the "dialpad chooser" IFF we need to make the user
338 // confirm which dialpad they really want.
339 showDialpadChooser(needToShowDialpadChooser);
340
341 return ignoreState;
342 }
343
344 protected void setFormattedDigits(String data) {
345 // strip the non-dialable numbers out of the data string.
346 String dialString = PhoneNumberUtils.extractNetworkPortion(data);
347 dialString = PhoneNumberUtils.formatNumber(dialString);
348 if (!TextUtils.isEmpty(dialString)) {
349 Editable digits = mDigits.getText();
350 digits.replace(0, digits.length(), dialString);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700351 // for some reason this isn't getting called in the digits.replace call above..
352 // but in any case, this will make sure the background drawable looks right
353 afterTextChanged(digits);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800354 }
355 }
356
357 @Override
358 protected void onNewIntent(Intent newIntent) {
359 setIntent(newIntent);
360 resolveIntent();
361 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700362
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800363 @Override
364 protected void onPostCreate(Bundle savedInstanceState) {
365 super.onPostCreate(savedInstanceState);
366
367 // This can't be done in onCreate(), since the auto-restoring of the digits
368 // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
369 // is called. This method will be called every time the activity is created, and
370 // will always happen after onRestoreSavedInstanceState().
371 mDigits.addTextChangedListener(this);
372 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700373
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800374 private void setupKeypad() {
375 // Setup the listeners for the buttons
376 View view = findViewById(R.id.one);
377 view.setOnClickListener(this);
378 view.setOnLongClickListener(this);
379
380 findViewById(R.id.two).setOnClickListener(this);
381 findViewById(R.id.three).setOnClickListener(this);
382 findViewById(R.id.four).setOnClickListener(this);
383 findViewById(R.id.five).setOnClickListener(this);
384 findViewById(R.id.six).setOnClickListener(this);
385 findViewById(R.id.seven).setOnClickListener(this);
386 findViewById(R.id.eight).setOnClickListener(this);
387 findViewById(R.id.nine).setOnClickListener(this);
388 findViewById(R.id.star).setOnClickListener(this);
389
390 view = findViewById(R.id.zero);
391 view.setOnClickListener(this);
392 view.setOnLongClickListener(this);
393
394 findViewById(R.id.pound).setOnClickListener(this);
395 }
396
397 @Override
398 protected void onResume() {
399 super.onResume();
David Brownc29c7ab2009-07-07 16:00:18 -0700400
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800401 // retrieve the DTMF tone play back setting.
402 mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
403 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
404
Eric Laurentd9efc872009-07-17 11:52:06 -0700405 // if the mToneGenerator creation fails, just continue without it. It is
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800406 // a local audio signal, and is not as important as the dtmf tone itself.
407 synchronized(mToneGeneratorLock) {
408 if (mToneGenerator == null) {
409 try {
Jean-Michel Trividd44f8c2009-11-10 13:00:45 -0800410 // we want the user to be able to control the volume of the dial tones
411 // outside of a call, so we use the stream type that is also mapped to the
412 // volume control keys for this activity
413 mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
414 setVolumeControlStream(DIAL_TONE_STREAM_TYPE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800415 } catch (RuntimeException e) {
416 Log.w(TAG, "Exception caught while creating local tone generator: " + e);
417 mToneGenerator = null;
418 }
419 }
420 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700421
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800422 Activity parent = getParent();
423 // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
424 // digits in the dialer field.
425 if (parent != null && parent instanceof DialtactsActivity) {
426 Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
427 if (dialUri != null) {
428 resolveIntent();
429 }
430 }
431
432 // While we're in the foreground, listen for phone state changes,
433 // purely so that we can take down the "dialpad chooser" if the
434 // phone becomes idle while the chooser UI is visible.
435 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
436 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
437
438 // Potentially show hint text in the mDigits field when the user
439 // hasn't typed any digits yet. (If there's already an active call,
440 // this hint text will remind the user that he's about to add a new
441 // call.)
442 //
443 // TODO: consider adding better UI for the case where *both* lines
444 // are currently in use. (Right now we let the user try to add
445 // another call, but that call is guaranteed to fail. Perhaps the
446 // entire dialer UI should be disabled instead.)
447 if (phoneIsInUse()) {
448 mDigits.setHint(R.string.dialerDialpadHintText);
449 } else {
450 // Common case; no hint necessary.
451 mDigits.setHint(null);
452
453 // Also, a sanity-check: the "dialpad chooser" UI should NEVER
454 // be visible if the phone is idle!
455 showDialpadChooser(false);
456 }
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700457
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700458 updateDialAndDeleteButtonStateEnabledAttr();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800459 }
460
461 @Override
Karl Rosaenf46bc312009-03-24 18:20:48 -0700462 public void onWindowFocusChanged(boolean hasFocus) {
463 if (hasFocus) {
464 // Hide soft keyboard, if visible (it's fugly over button dialer).
465 // The only known case where this will be true is when launching the dialer with
466 // ACTION_DIAL via a soft keyboard. we dismiss it here because we don't
467 // have a window token yet in onCreate / onNewIntent
468 InputMethodManager inputMethodManager = (InputMethodManager)
469 getSystemService(Context.INPUT_METHOD_SERVICE);
Eric Laurentd9efc872009-07-17 11:52:06 -0700470 inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700471 }
472 }
473
474 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800475 protected void onPause() {
476 super.onPause();
477
478 // Stop listening for phone state changes.
479 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
480 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
481
482 synchronized(mToneGeneratorLock) {
483 if (mToneGenerator != null) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800484 mToneGenerator.release();
485 mToneGenerator = null;
486 }
487 }
488 }
489
490 @Override
491 public boolean onCreateOptionsMenu(Menu menu) {
Reli Talc2a2a512009-06-10 16:48:00 -0400492 mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800493 .setIcon(android.R.drawable.ic_menu_add);
Reli Talc2a2a512009-06-10 16:48:00 -0400494 m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
495 .setIcon(R.drawable.ic_menu_2sec_pause);
496 mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
497 .setIcon(R.drawable.ic_menu_wait);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800498 return true;
499 }
500
501 @Override
502 public boolean onPrepareOptionsMenu(Menu menu) {
503 // We never show a menu if the "choose dialpad" UI is up.
504 if (dialpadChooserVisible()) {
505 return false;
506 }
507
508 CharSequence digits = mDigits.getText();
509 if (digits == null || !TextUtils.isGraphic(digits)) {
510 mAddToContactMenuItem.setVisible(false);
Reli Talc2a2a512009-06-10 16:48:00 -0400511 m2SecPauseMenuItem.setVisible(false);
512 mWaitMenuItem.setVisible(false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800513 } else {
514 // Put the current digits string into an intent
515 Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
516 intent.putExtra(Insert.PHONE, mDigits.getText());
517 intent.setType(People.CONTENT_ITEM_TYPE);
518 mAddToContactMenuItem.setIntent(intent);
519 mAddToContactMenuItem.setVisible(true);
Reli Talc2a2a512009-06-10 16:48:00 -0400520
521 // Check out whether to show Pause & Wait option menu items
522 int selectionStart;
523 int selectionEnd;
524 String strDigits = digits.toString();
525
526 selectionStart = mDigits.getSelectionStart();
527 selectionEnd = mDigits.getSelectionEnd();
528
529 if (selectionStart != -1) {
530 if (selectionStart > selectionEnd) {
531 // swap it as we want start to be less then end
532 int tmp = selectionStart;
533 selectionStart = selectionEnd;
534 selectionEnd = tmp;
535 }
536
537 if (selectionStart != 0) {
538 // Pause can be visible if cursor is not in the begining
539 m2SecPauseMenuItem.setVisible(true);
540
541 // For Wait to be visible set of condition to meet
542 mWaitMenuItem.setVisible(showWait(selectionStart,
543 selectionEnd, strDigits));
544 } else {
545 // cursor in the beginning both pause and wait to be invisible
546 m2SecPauseMenuItem.setVisible(false);
547 mWaitMenuItem.setVisible(false);
548 }
549 } else {
550 // cursor is not selected so assume new digit is added to the end
551 int strLength = strDigits.length();
552 mWaitMenuItem.setVisible(showWait(strLength,
553 strLength, strDigits));
554 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800555 }
556 return true;
557 }
558
559 @Override
560 public boolean onKeyDown(int keyCode, KeyEvent event) {
561 switch (keyCode) {
562 case KeyEvent.KEYCODE_CALL: {
563 long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
564 if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
565 // Launch voice dialer
566 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
567 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
568 try {
569 startActivity(intent);
570 } catch (ActivityNotFoundException e) {
571 }
572 }
573 return true;
574 }
575 case KeyEvent.KEYCODE_1: {
Eric Laurentd9efc872009-07-17 11:52:06 -0700576 long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800577 if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
578 // Long press detected, call voice mail
579 callVoicemail();
580 }
581 return true;
582 }
583 }
584 return super.onKeyDown(keyCode, event);
585 }
586
587 @Override
588 public boolean onKeyUp(int keyCode, KeyEvent event) {
589 switch (keyCode) {
590 case KeyEvent.KEYCODE_CALL: {
Paul Bermandbdcde22009-10-09 12:04:10 -0400591 if (phoneIsCdma()) {
592 // If we're CDMA, regardless of where we are adding a call from (either
593 // InCallScreen or Dialtacts), the user may need to send an empty
594 // flash command to the network. So let's call placeCall() regardless
595 // and placeCall will handle this functionality for us.
596 placeCall();
597 } else if (mIsAddCallMode && (TextUtils.isEmpty(mDigits.getText().toString()))) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800598 // if we are adding a call from the InCallScreen and the phone
599 // number entered is empty, we just close the dialer to expose
600 // the InCallScreen under it.
601 finish();
602 } else {
603 // otherwise, we place the call.
604 placeCall();
605 }
606 return true;
607 }
608 }
609 return super.onKeyUp(keyCode, event);
610 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700611
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800612 private void keyPressed(int keyCode) {
David Brownc29c7ab2009-07-07 16:00:18 -0700613 vibrate();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800614 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
615 mDigits.onKeyDown(keyCode, event);
616 }
617
618 public boolean onKey(View view, int keyCode, KeyEvent event) {
619 switch (view.getId()) {
620 case R.id.digits:
621 if (keyCode == KeyEvent.KEYCODE_ENTER) {
622 placeCall();
623 return true;
624 }
625 break;
626 }
627 return false;
628 }
629
630 public void onClick(View view) {
631 switch (view.getId()) {
632 case R.id.one: {
633 playTone(ToneGenerator.TONE_DTMF_1);
634 keyPressed(KeyEvent.KEYCODE_1);
635 return;
636 }
637 case R.id.two: {
638 playTone(ToneGenerator.TONE_DTMF_2);
639 keyPressed(KeyEvent.KEYCODE_2);
640 return;
641 }
642 case R.id.three: {
643 playTone(ToneGenerator.TONE_DTMF_3);
644 keyPressed(KeyEvent.KEYCODE_3);
645 return;
646 }
647 case R.id.four: {
648 playTone(ToneGenerator.TONE_DTMF_4);
649 keyPressed(KeyEvent.KEYCODE_4);
650 return;
651 }
652 case R.id.five: {
653 playTone(ToneGenerator.TONE_DTMF_5);
654 keyPressed(KeyEvent.KEYCODE_5);
655 return;
656 }
657 case R.id.six: {
658 playTone(ToneGenerator.TONE_DTMF_6);
659 keyPressed(KeyEvent.KEYCODE_6);
660 return;
661 }
662 case R.id.seven: {
663 playTone(ToneGenerator.TONE_DTMF_7);
664 keyPressed(KeyEvent.KEYCODE_7);
665 return;
666 }
667 case R.id.eight: {
668 playTone(ToneGenerator.TONE_DTMF_8);
669 keyPressed(KeyEvent.KEYCODE_8);
670 return;
671 }
672 case R.id.nine: {
673 playTone(ToneGenerator.TONE_DTMF_9);
674 keyPressed(KeyEvent.KEYCODE_9);
675 return;
676 }
677 case R.id.zero: {
678 playTone(ToneGenerator.TONE_DTMF_0);
679 keyPressed(KeyEvent.KEYCODE_0);
680 return;
681 }
682 case R.id.pound: {
683 playTone(ToneGenerator.TONE_DTMF_P);
684 keyPressed(KeyEvent.KEYCODE_POUND);
685 return;
686 }
687 case R.id.star: {
688 playTone(ToneGenerator.TONE_DTMF_S);
689 keyPressed(KeyEvent.KEYCODE_STAR);
690 return;
691 }
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700692 case R.id.deleteButton: {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800693 keyPressed(KeyEvent.KEYCODE_DEL);
694 return;
695 }
Nicolas Catania3040fa32009-10-01 13:00:53 -0700696 case R.id.dialButton: {
David Brownc29c7ab2009-07-07 16:00:18 -0700697 vibrate(); // Vibrate here too, just like we do for the regular keys
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800698 placeCall();
699 return;
700 }
Nicolas Catania80bda0f2009-09-19 09:17:14 -0700701 case R.id.voicemailButton: {
702 callVoicemail();
703 vibrate();
704 return;
705 }
Nicolas Catania3040fa32009-10-01 13:00:53 -0700706 case R.id.digits: {
707 if (mDigits.length() != 0) {
708 mDigits.setCursorVisible(true);
709 }
710 return;
711 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800712 }
713 }
714
715 public boolean onLongClick(View view) {
716 final Editable digits = mDigits.getText();
717 int id = view.getId();
718 switch (id) {
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700719 case R.id.deleteButton: {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800720 digits.clear();
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700721 // TODO: The framework forgets to clear the pressed
722 // status of disabled button. Until this is fixed,
723 // clear manually the pressed status. b/2133127
724 mDelete.setPressed(false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800725 return true;
726 }
727 case R.id.one: {
728 if (digits.length() == 0) {
729 callVoicemail();
730 return true;
731 }
732 return false;
733 }
734 case R.id.zero: {
735 keyPressed(KeyEvent.KEYCODE_PLUS);
736 return true;
737 }
738 }
739 return false;
740 }
741
742 void callVoicemail() {
743 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
744 Uri.fromParts("voicemail", "", null));
745 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
746 startActivity(intent);
747 mDigits.getText().clear();
748 finish();
749 }
750
751 void placeCall() {
752 final String number = mDigits.getText().toString();
Paul Bermandbdcde22009-10-09 12:04:10 -0400753 boolean sendEmptyFlash = false;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800754 Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
755 Uri.fromParts("tel", number, null));
Paul Bermandbdcde22009-10-09 12:04:10 -0400756 if (number == null || !TextUtils.isGraphic(number)) {
757 // There is no number entered.
758 if (phoneIsCdma() && phoneIsOffhook()) {
759 // We only want to send this empty flash extra if we're CDMA and the
760 // phone is offhook (don't want to send if ringing or dialing)
761 intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
762 sendEmptyFlash = true;
763 } else {
764 playTone(ToneGenerator.TONE_PROP_NACK);
765 return;
766 }
767 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800768 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
769 startActivity(intent);
770 mDigits.getText().clear();
Paul Bermandbdcde22009-10-09 12:04:10 -0400771 // Don't finish TwelveKeyDialer yet if we're sending a blank flash for CDMA. CDMA
772 // networks use Flash messages when special processing needs to be done, mainly for
773 // 3-way or call waiting scenarios. Presumably, here we're in a special 3-way scenario
774 // where the network needs a blank flash before being able to add the new participant.
775 // (This is not the case with all 3-way calls, just certain CDMA infrastructures.)
776 if (!sendEmptyFlash) {
777 finish();
778 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800779 }
780
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800781
782 /**
David Brown22f615f2009-06-25 16:19:19 -0700783 * Plays the specified tone for TONE_LENGTH_MS milliseconds.
784 *
785 * The tone is played locally, using the audio stream for phone calls.
786 * Tones are played only if the "Audible touch tones" user preference
787 * is checked, and are NOT played if the device is in silent mode.
788 *
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800789 * @param tone a tone code from {@link ToneGenerator}
790 */
791 void playTone(int tone) {
792 // if local tone playback is disabled, just return.
793 if (!mDTMFToneEnabled) {
794 return;
795 }
David Brown22f615f2009-06-25 16:19:19 -0700796
797 // Also do nothing if the phone is in silent mode.
798 // We need to re-check the ringer mode for *every* playTone()
799 // call, rather than keeping a local flag that's updated in
800 // onResume(), since it's possible to toggle silent mode without
801 // leaving the current activity (via the ENDCALL-longpress menu.)
802 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
David Brownd5a15302009-07-20 16:39:47 -0700803 int ringerMode = audioManager.getRingerMode();
804 if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
805 || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
David Brown22f615f2009-06-25 16:19:19 -0700806 return;
807 }
808
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800809 synchronized(mToneGeneratorLock) {
810 if (mToneGenerator == null) {
811 Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
812 return;
813 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700814
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800815 // Start the new tone (will stop any playing tone)
Eric Laurent8487fed2009-09-07 08:45:14 -0700816 mToneGenerator.startTone(tone, TONE_LENGTH_MS);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800817 }
818 }
819
820 /**
821 * Brings up the "dialpad chooser" UI in place of the usual Dialer
822 * elements (the textfield/button and the dialpad underneath).
823 *
824 * We show this UI if the user brings up the Dialer while a call is
825 * already in progress, since there's a good chance we got here
826 * accidentally (and the user really wanted the in-call dialpad instead).
827 * So in this situation we display an intermediate UI that lets the user
828 * explicitly choose between the in-call dialpad ("Use touch tone
829 * keypad") and the regular Dialer ("Add call"). (Or, the option "Return
830 * to call in progress" just goes back to the in-call UI with no dialpad
831 * at all.)
832 *
833 * @param enabled If true, show the "dialpad chooser" instead
834 * of the regular Dialer UI
835 */
836 private void showDialpadChooser(boolean enabled) {
837 if (enabled) {
838 // Log.i(TAG, "Showing dialpad chooser!");
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700839 mDigits.setVisibility(View.GONE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800840 if (mDialpad != null) mDialpad.setVisibility(View.GONE);
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700841 mVoicemailDialAndDeleteRow.setVisibility(View.GONE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800842 mDialpadChooser.setVisibility(View.VISIBLE);
843
844 // Instantiate the DialpadChooserAdapter and hook it up to the
845 // ListView. We do this only once.
846 if (mDialpadChooserAdapter == null) {
847 mDialpadChooserAdapter = new DialpadChooserAdapter(this);
848 mDialpadChooser.setAdapter(mDialpadChooserAdapter);
849 }
850 } else {
851 // Log.i(TAG, "Displaying normal Dialer UI.");
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700852 mDigits.setVisibility(View.VISIBLE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800853 if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700854 mVoicemailDialAndDeleteRow.setVisibility(View.VISIBLE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800855 mDialpadChooser.setVisibility(View.GONE);
856 }
857 }
858
859 /**
860 * @return true if we're currently showing the "dialpad chooser" UI.
861 */
862 private boolean dialpadChooserVisible() {
863 return mDialpadChooser.getVisibility() == View.VISIBLE;
864 }
865
866 /**
867 * Simple list adapter, binding to an icon + text label
868 * for each item in the "dialpad chooser" list.
869 */
870 private static class DialpadChooserAdapter extends BaseAdapter {
871 private LayoutInflater mInflater;
872
873 // Simple struct for a single "choice" item.
874 static class ChoiceItem {
875 String text;
876 Bitmap icon;
877 int id;
878
879 public ChoiceItem(String s, Bitmap b, int i) {
880 text = s;
881 icon = b;
882 id = i;
883 }
884 }
885
886 // IDs for the possible "choices":
887 static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
888 static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
889 static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
890
891 private static final int NUM_ITEMS = 3;
892 private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
893
894 public DialpadChooserAdapter(Context context) {
895 // Cache the LayoutInflate to avoid asking for a new one each time.
896 mInflater = LayoutInflater.from(context);
897
898 // Initialize the possible choices.
899 // TODO: could this be specified entirely in XML?
900
901 // - "Use touch tone keypad"
902 mChoiceItems[0] = new ChoiceItem(
903 context.getString(R.string.dialer_useDtmfDialpad),
904 BitmapFactory.decodeResource(context.getResources(),
905 R.drawable.ic_dialer_fork_tt_keypad),
906 DIALPAD_CHOICE_USE_DTMF_DIALPAD);
907
908 // - "Return to call in progress"
909 mChoiceItems[1] = new ChoiceItem(
910 context.getString(R.string.dialer_returnToInCallScreen),
911 BitmapFactory.decodeResource(context.getResources(),
912 R.drawable.ic_dialer_fork_current_call),
913 DIALPAD_CHOICE_RETURN_TO_CALL);
914
915 // - "Add call"
916 mChoiceItems[2] = new ChoiceItem(
917 context.getString(R.string.dialer_addAnotherCall),
918 BitmapFactory.decodeResource(context.getResources(),
919 R.drawable.ic_dialer_fork_add_call),
920 DIALPAD_CHOICE_ADD_NEW_CALL);
921 }
922
923 public int getCount() {
924 return NUM_ITEMS;
925 }
926
927 /**
928 * Return the ChoiceItem for a given position.
929 */
930 public Object getItem(int position) {
931 return mChoiceItems[position];
932 }
933
934 /**
935 * Return a unique ID for each possible choice.
936 */
937 public long getItemId(int position) {
938 return position;
939 }
940
941 /**
942 * Make a view for each row.
943 */
944 public View getView(int position, View convertView, ViewGroup parent) {
945 // When convertView is non-null, we can reuse it (there's no need
946 // to reinflate it.)
947 if (convertView == null) {
948 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
949 }
950
951 TextView text = (TextView) convertView.findViewById(R.id.text);
952 text.setText(mChoiceItems[position].text);
953
954 ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
955 icon.setImageBitmap(mChoiceItems[position].icon);
956
957 return convertView;
958 }
959 }
960
961 /**
962 * Handle clicks from the dialpad chooser.
963 */
964 public void onItemClick(AdapterView parent, View v, int position, long id) {
965 DialpadChooserAdapter.ChoiceItem item =
966 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
967 int itemId = item.id;
968 switch (itemId) {
969 case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
970 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
971 // Fire off an intent to go back to the in-call UI
972 // with the dialpad visible.
973 returnToInCallScreen(true);
974 break;
975
976 case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
977 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
978 // Fire off an intent to go back to the in-call UI
979 // (with the dialpad hidden).
980 returnToInCallScreen(false);
981 break;
982
983 case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
984 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
985 // Ok, guess the user really did want to be here (in the
986 // regular Dialer) after all. Bring back the normal Dialer UI.
987 showDialpadChooser(false);
988 break;
989
990 default:
991 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
992 break;
993 }
994 }
995
996 /**
997 * Returns to the in-call UI (where there's presumably a call in
998 * progress) in response to the user selecting "use touch tone keypad"
999 * or "return to call" from the dialpad chooser.
1000 */
1001 private void returnToInCallScreen(boolean showDialpad) {
1002 try {
1003 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1004 if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
1005 } catch (RemoteException e) {
1006 Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
1007 }
1008
1009 // Finally, finish() ourselves so that we don't stay on the
1010 // activity stack.
1011 // Note that we do this whether or not the showCallScreenWithDialpad()
1012 // call above had any effect or not! (That call is a no-op if the
1013 // phone is idle, which can happen if the current call ends while
1014 // the dialpad chooser is up. In this case we can't show the
1015 // InCallScreen, and there's no point staying here in the Dialer,
1016 // so we just take the user back where he came from...)
1017 finish();
1018 }
1019
1020 /**
1021 * @return true if the phone is "in use", meaning that at least one line
1022 * is active (ie. off hook or ringing or dialing).
1023 */
1024 private boolean phoneIsInUse() {
1025 boolean phoneInUse = false;
1026 try {
1027 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1028 if (phone != null) phoneInUse = !phone.isIdle();
1029 } catch (RemoteException e) {
1030 Log.w(TAG, "phone.isIdle() failed", e);
1031 }
1032 return phoneInUse;
1033 }
David Brownc29c7ab2009-07-07 16:00:18 -07001034
1035 /**
Paul Bermandbdcde22009-10-09 12:04:10 -04001036 * @return true if the phone is a CDMA phone type
1037 */
1038 private boolean phoneIsCdma() {
1039 boolean isCdma = false;
1040 try {
1041 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1042 if (phone != null) {
1043 isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
1044 }
1045 } catch (RemoteException e) {
1046 Log.w(TAG, "phone.getActivePhoneType() failed", e);
1047 }
1048 return isCdma;
1049 }
1050
1051 /**
1052 * @return true if the phone state is OFFHOOK
1053 */
1054 private boolean phoneIsOffhook() {
1055 boolean phoneOffhook = false;
1056 try {
1057 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1058 if (phone != null) phoneOffhook = phone.isOffhook();
1059 } catch (RemoteException e) {
1060 Log.w(TAG, "phone.isOffhook() failed", e);
1061 }
1062 return phoneOffhook;
1063 }
1064
1065 /**
David Brownc29c7ab2009-07-07 16:00:18 -07001066 * Triggers haptic feedback (if enabled) for dialer key presses.
1067 */
1068 private synchronized void vibrate() {
1069 if (!mVibrateOn) {
1070 return;
1071 }
1072 if (mVibrator == null) {
1073 mVibrator = new Vibrator();
1074 }
Nicolas Catania4c0704a2009-09-23 11:42:00 -07001075 mVibrator.vibrate(mVibratePattern, VIBRATE_NO_REPEAT);
David Brownc29c7ab2009-07-07 16:00:18 -07001076 }
Reli Talc2a2a512009-06-10 16:48:00 -04001077
1078 /**
1079 * Returns true whenever any one of the options from the menu is selected.
1080 * Code changes to support dialpad options
1081 */
1082 @Override
1083 public boolean onOptionsItemSelected(MenuItem item) {
1084 switch (item.getItemId()) {
1085 case MENU_2S_PAUSE:
1086 updateDialString(",");
1087 return true;
1088 case MENU_WAIT:
1089 updateDialString(";");
1090 return true;
1091 }
1092 return false;
1093 }
1094
1095 /**
1096 * Updates the dial string (mDigits) after inserting a Pause character (,)
1097 * or Wait character (;).
1098 */
1099 private void updateDialString(String newDigits) {
1100 int selectionStart;
1101 int selectionEnd;
1102
1103 // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
Eric Fischer686782e2009-09-10 17:57:45 -07001104 int anchor = mDigits.getSelectionStart();
1105 int point = mDigits.getSelectionEnd();
1106
1107 selectionStart = Math.min(anchor, point);
1108 selectionEnd = Math.max(anchor, point);
Reli Talc2a2a512009-06-10 16:48:00 -04001109
1110 Editable digits = mDigits.getText();
1111 if (selectionStart != -1 ) {
1112 if (selectionStart == selectionEnd) {
1113 // then there is no selection. So insert the pause at this
1114 // position and update the mDigits.
1115 digits.replace(selectionStart, selectionStart, newDigits);
1116 } else {
Eric Fischer1e2d3a22009-09-17 10:53:10 -07001117 digits.replace(selectionStart, selectionEnd, newDigits);
Nicolas Catania7edbd0c2009-09-28 20:37:33 -07001118 // Unselect: back to a regular cursor, just pass the character inserted.
1119 mDigits.setSelection(selectionStart + 1);
Reli Talc2a2a512009-06-10 16:48:00 -04001120 }
1121 } else {
1122 int len = mDigits.length();
1123 digits.replace(len, len, newDigits);
1124 }
1125 }
1126
1127 /**
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -07001128 * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
Nicolas Cataniadea164e2009-09-18 06:26:16 -07001129 */
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -07001130 private void updateDialAndDeleteButtonStateEnabledAttr() {
1131 final boolean notEmpty = mDigits.length() != 0;
1132
Paul Bermandbdcde22009-10-09 12:04:10 -04001133 // If we're already on a CDMA call, then we want to enable the Call button
1134 if (phoneIsCdma() && phoneIsOffhook()) {
1135 if (mDialButton != null) {
1136 mDialButton.setEnabled(true);
1137 }
1138 } else {
1139 if (mDialButton != null) {
1140 mDialButton.setEnabled(notEmpty);
1141 }
Nicolas Cataniadea164e2009-09-18 06:26:16 -07001142 }
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -07001143 mDelete.setEnabled(notEmpty);
Nicolas Cataniadea164e2009-09-18 06:26:16 -07001144 }
1145
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -07001146
Nicolas Cataniadea164e2009-09-18 06:26:16 -07001147 /**
Nicolas Catania80bda0f2009-09-19 09:17:14 -07001148 * Check if voicemail is enabled/accessible.
1149 */
1150 private void initVoicemailButton() {
1151 boolean hasVoicemail = false;
1152 try {
1153 hasVoicemail = TelephonyManager.getDefault().getVoiceMailNumber() != null;
1154 } catch (SecurityException se) {
1155 // Possibly no READ_PHONE_STATE privilege.
1156 }
1157
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -07001158 mVoicemailButton = mVoicemailDialAndDeleteRow.findViewById(R.id.voicemailButton);
Nicolas Catania80bda0f2009-09-19 09:17:14 -07001159 if (hasVoicemail) {
1160 mVoicemailButton.setOnClickListener(this);
1161 } else {
1162 mVoicemailButton.setEnabled(false);
1163 }
1164 }
1165
1166 /**
Nicolas Catania4c0704a2009-09-23 11:42:00 -07001167 * Initialize the vibration parameters.
1168 * @param r The Resources with the vibration parameters.
1169 */
1170 private void initVibrationPattern(Resources r) {
1171 int[] pattern = null;
1172 try {
1173 mVibrateOn = r.getBoolean(R.bool.config_enable_dialer_key_vibration);
1174 pattern = r.getIntArray(com.android.internal.R.array.config_virtualKeyVibePattern);
1175 if (null == pattern) {
1176 Log.e(TAG, "Vibrate pattern is null.");
1177 mVibrateOn = false;
1178 }
1179 } catch (Resources.NotFoundException nfe) {
1180 Log.e(TAG, "Vibrate control bool or pattern missing.", nfe);
1181 mVibrateOn = false;
1182 }
1183
1184 if (!mVibrateOn) {
1185 return;
1186 }
1187
1188 // int[] to long[] conversion.
1189 mVibratePattern = new long[pattern.length];
1190 for (int i = 0; i < pattern.length; i++) {
1191 mVibratePattern[i] = pattern[i];
1192 }
1193 }
1194
1195 /**
Reli Talc2a2a512009-06-10 16:48:00 -04001196 * This function return true if Wait menu item can be shown
1197 * otherwise returns false. Assumes the passed string is non-empty
1198 * and the 0th index check is not required.
1199 */
1200 private boolean showWait(int start, int end, String digits) {
1201 if (start == end) {
1202 // visible false in this case
1203 if (start > digits.length()) return false;
1204
1205 // preceding char is ';', so visible should be false
1206 if (digits.charAt(start-1) == ';') return false;
1207
1208 // next char is ';', so visible should be false
1209 if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
1210 } else {
1211 // visible false in this case
1212 if (start > digits.length() || end > digits.length()) return false;
1213
1214 // In this case we need to just check for ';' preceding to start
1215 // or next to end
1216 if (digits.charAt(start-1) == ';') return false;
1217 }
1218 return true;
1219 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08001220}