blob: b93375c4aa6d0bf8d6753e70f43bdb32713ce497 [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
Dmitri Plotnikov8e86b752010-02-22 17:47:57 -080019import com.android.internal.telephony.ITelephony;
20import com.android.phone.CallLogAsync;
21import com.android.phone.HapticFeedback;
22
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080023import android.app.Activity;
24import android.content.ActivityNotFoundException;
25import android.content.Context;
26import android.content.Intent;
Bernd Holzheyd0bfafc2010-03-02 09:00:02 +010027import android.content.res.Configuration;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080028import android.content.res.Resources;
29import android.database.Cursor;
30import android.graphics.Bitmap;
31import android.graphics.BitmapFactory;
32import android.graphics.drawable.Drawable;
33import android.media.AudioManager;
34import android.media.ToneGenerator;
35import android.net.Uri;
36import android.os.Bundle;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080037import android.os.RemoteException;
38import android.os.ServiceManager;
39import android.os.SystemClock;
Dmitri Plotnikov8e86b752010-02-22 17:47:57 -080040import android.provider.Settings;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080041import android.provider.Contacts.People;
42import android.provider.Contacts.Phones;
43import android.provider.Contacts.PhonesColumns;
Dmitri Plotnikov8e86b752010-02-22 17:47:57 -080044import android.provider.Contacts.Intents.Insert;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080045import android.telephony.PhoneNumberFormattingTextWatcher;
46import android.telephony.PhoneNumberUtils;
47import android.telephony.PhoneStateListener;
48import android.telephony.TelephonyManager;
49import android.text.Editable;
50import android.text.TextUtils;
51import android.text.TextWatcher;
52import android.text.method.DialerKeyListener;
53import android.util.Log;
54import android.view.KeyEvent;
55import android.view.LayoutInflater;
56import android.view.Menu;
57import android.view.MenuItem;
58import android.view.View;
59import android.view.ViewConfiguration;
60import android.view.ViewGroup;
Bernd Holzheyd0bfafc2010-03-02 09:00:02 +010061import android.view.Window;
Karl Rosaenf46bc312009-03-24 18:20:48 -070062import android.view.inputmethod.InputMethodManager;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080063import android.widget.AdapterView;
64import android.widget.BaseAdapter;
65import android.widget.EditText;
66import android.widget.ImageView;
67import android.widget.ListView;
68import android.widget.TextView;
69
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080070/**
71 * Dialer activity that displays the typical twelve key interface.
72 */
Dmitri Plotnikov8e86b752010-02-22 17:47:57 -080073@SuppressWarnings("deprecation")
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080074public class TwelveKeyDialer extends Activity implements View.OnClickListener,
75 View.OnLongClickListener, View.OnKeyListener,
76 AdapterView.OnItemClickListener, TextWatcher {
Nicolas Cataniac3be69e2010-01-14 14:03:53 -080077 private static final String EMPTY_NUMBER = "";
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080078 private static final String TAG = "TwelveKeyDialer";
Eric Laurentd9efc872009-07-17 11:52:06 -070079
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080080 /** The length of DTMF tones in milliseconds */
81 private static final int TONE_LENGTH_MS = 150;
Eric Laurentd9efc872009-07-17 11:52:06 -070082
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080083 /** The DTMF tone volume relative to other sounds in the stream */
Jean-Michel Trividd44f8c2009-11-10 13:00:45 -080084 private static final int TONE_RELATIVE_VOLUME = 80;
85
86 /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
87 private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_MUSIC;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080088
89 private EditText mDigits;
90 private View mDelete;
91 private MenuItem mAddToContactMenuItem;
92 private ToneGenerator mToneGenerator;
93 private Object mToneGeneratorLock = new Object();
94 private Drawable mDigitsBackground;
95 private Drawable mDigitsEmptyBackground;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -080096 private View mDialpad;
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -070097 private View mVoicemailDialAndDeleteRow;
Nicolas Catania80bda0f2009-09-19 09:17:14 -070098 private View mVoicemailButton;
99 private View mDialButton;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800100 private ListView mDialpadChooser;
101 private DialpadChooserAdapter mDialpadChooserAdapter;
Reli Talc2a2a512009-06-10 16:48:00 -0400102 //Member variables for dialpad options
103 private MenuItem m2SecPauseMenuItem;
104 private MenuItem mWaitMenuItem;
105 private static final int MENU_ADD_CONTACTS = 1;
106 private static final int MENU_2S_PAUSE = 2;
107 private static final int MENU_WAIT = 3;
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800108
Nicolas Cataniac3be69e2010-01-14 14:03:53 -0800109 // Last number dialed, retrieved asynchronously from the call DB
110 // in onCreate. This number is displayed when the user hits the
111 // send key and cleared in onPause.
112 CallLogAsync mCallLog = new CallLogAsync();
113 private String mLastNumberDialed = EMPTY_NUMBER;
114
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800115 // determines if we want to playback local DTMF tones.
116 private boolean mDTMFToneEnabled;
David Brownc29c7ab2009-07-07 16:00:18 -0700117
118 // Vibration (haptic feedback) for dialer key presses.
Nicolas Catania905e7622009-12-01 08:51:20 -0800119 private HapticFeedback mHaptic = new HapticFeedback();
Eric Laurentd9efc872009-07-17 11:52:06 -0700120
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800121 /** Identifier for the "Add Call" intent extra. */
122 static final String ADD_CALL_MODE_KEY = "add_call_mode";
Paul Bermandbdcde22009-10-09 12:04:10 -0400123
124 /**
125 * Identifier for intent extra for sending an empty Flash message for
126 * CDMA networks. This message is used by the network to simulate a
127 * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
128 *
129 * TODO: Using an intent extra to tell the phone to send this flash is a
130 * temporary measure. To be replaced with an ITelephony call in the future.
131 * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java
132 * in Phone app until this is replaced with the ITelephony API.
133 */
134 static final String EXTRA_SEND_EMPTY_FLASH
135 = "com.android.phone.extra.SEND_EMPTY_FLASH";
136
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800137 /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
138 private boolean mIsAddCallMode;
139
Bai Tao09eb04f2010-09-01 15:34:16 +0800140 private String mCurrentCountryIso;
141
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800142 PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
143 /**
144 * Listen for phone state changes so that we can take down the
145 * "dialpad chooser" if the phone becomes idle while the
146 * chooser UI is visible.
147 */
148 @Override
149 public void onCallStateChanged(int state, String incomingNumber) {
150 // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
151 // + state + ", '" + incomingNumber + "'");
152 if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
153 // Log.i(TAG, "Call ended with dialpad chooser visible! Taking it down...");
154 // Note there's a race condition in the UI here: the
155 // dialpad chooser could conceivably disappear (on its
156 // own) at the exact moment the user was trying to select
157 // one of the choices, which would be confusing. (But at
158 // least that's better than leaving the dialpad chooser
159 // onscreen, but useless...)
160 showDialpadChooser(false);
161 }
162 }
163 };
164
165 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
166 // Do nothing
167 }
168
169 public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
170 // Do nothing
Eric Laurentd9efc872009-07-17 11:52:06 -0700171 // DTMF Tones do not need to be played here any longer -
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800172 // the DTMF dialer handles that functionality now.
173 }
174
175 public void afterTextChanged(Editable input) {
176 if (SpecialCharSequenceMgr.handleChars(this, input.toString(), mDigits)) {
177 // A special sequence was entered, clear the digits
178 mDigits.getText().clear();
179 }
180
Nicolas Cataniabe8821e2010-01-15 09:28:13 -0800181 if (!isDigitsEmpty()) {
Nicolas Catania75993762009-09-21 16:42:00 -0700182 mDigits.setBackgroundDrawable(mDigitsBackground);
183 } else {
Nicolas Catania3040fa32009-10-01 13:00:53 -0700184 mDigits.setCursorVisible(false);
Nicolas Catania75993762009-09-21 16:42:00 -0700185 mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
186 }
187
Nicolas Cataniac3be69e2010-01-14 14:03:53 -0800188 updateDialAndDeleteButtonEnabledState();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800189 }
190
191 @Override
192 protected void onCreate(Bundle icicle) {
193 super.onCreate(icicle);
194
Bai Tao09eb04f2010-09-01 15:34:16 +0800195 mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(this);
Bernd Holzheyd0bfafc2010-03-02 09:00:02 +0100196 Resources r = getResources();
197 // Do not show title in the case the device is in carmode.
198 if ((r.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK) ==
199 Configuration.UI_MODE_TYPE_CAR) {
200 requestWindowFeature(Window.FEATURE_NO_TITLE);
201 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800202 // Set the content view
203 setContentView(getContentViewResource());
204
Nicolas Catania75993762009-09-21 16:42:00 -0700205 // Load up the resources for the text field.
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800206 mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800207 mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800208
209 mDigits = (EditText) findViewById(R.id.digits);
210 mDigits.setKeyListener(DialerKeyListener.getInstance());
211 mDigits.setOnClickListener(this);
212 mDigits.setOnKeyListener(this);
Nicolas Catania3040fa32009-10-01 13:00:53 -0700213
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800214 maybeAddNumberFormatting();
215
216 // Check for the presence of the keypad
217 View view = findViewById(R.id.one);
218 if (view != null) {
219 setupKeypad();
220 }
221
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700222 mVoicemailDialAndDeleteRow = findViewById(R.id.voicemailAndDialAndDelete);
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700223
Nicolas Catania80bda0f2009-09-19 09:17:14 -0700224 initVoicemailButton();
225
David Brown3d07e6d2009-08-04 20:30:09 -0700226 // Check whether we should show the onscreen "Dial" button.
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700227 mDialButton = mVoicemailDialAndDeleteRow.findViewById(R.id.dialButton);
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700228
David Brown3d07e6d2009-08-04 20:30:09 -0700229 if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
David Brown3d07e6d2009-08-04 20:30:09 -0700230 mDialButton.setOnClickListener(this);
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700231 } else {
232 mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
233 mDialButton = null;
David Brown3d07e6d2009-08-04 20:30:09 -0700234 }
235
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700236 view = mVoicemailDialAndDeleteRow.findViewById(R.id.deleteButton);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800237 view.setOnClickListener(this);
238 view.setOnLongClickListener(this);
239 mDelete = view;
240
Nicolas Catania901f8562009-10-09 11:09:45 -0700241 mDialpad = findViewById(R.id.dialpad); // This is null in landscape mode.
242
243 // In landscape we put the keyboard in phone mode.
244 // In portrait we prevent the soft keyboard to show since the
245 // dialpad acts as one already.
246 if (null == mDialpad) {
247 mDigits.setInputType(android.text.InputType.TYPE_CLASS_PHONE);
248 } else {
249 mDigits.setInputType(android.text.InputType.TYPE_NULL);
250 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800251
252 // Set up the "dialpad chooser" UI; see showDialpadChooser().
253 mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
254 mDialpadChooser.setOnItemClickListener(this);
255
256 if (!resolveIntent() && icicle != null) {
257 super.onRestoreInstanceState(icicle);
258 }
259
Nicolas Catania905e7622009-12-01 08:51:20 -0800260 try {
261 mHaptic.init(this, r.getBoolean(R.bool.config_enable_dialer_key_vibration));
262 } catch (Resources.NotFoundException nfe) {
263 Log.e(TAG, "Vibrate control bool missing.", nfe);
264 }
265
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800266 }
267
268 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800269 protected void onRestoreInstanceState(Bundle icicle) {
270 // Do nothing, state is restored in onCreate() if needed
271 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700272
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800273 protected void maybeAddNumberFormatting() {
Bai Tao09eb04f2010-09-01 15:34:16 +0800274 mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher(mCurrentCountryIso));
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800275 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700276
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800277 /**
Eric Laurentd9efc872009-07-17 11:52:06 -0700278 * Overridden by subclasses to control the resource used by the content view.
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800279 */
280 protected int getContentViewResource() {
281 return R.layout.twelve_key_dialer;
282 }
283
284 private boolean resolveIntent() {
285 boolean ignoreState = false;
286
287 // Find the proper intent
288 final Intent intent;
289 if (isChild()) {
290 intent = getParent().getIntent();
291 ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
292 } else {
293 intent = getIntent();
294 }
295 // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
296
297 // by default we are not adding a call.
298 mIsAddCallMode = false;
299
300 // By default we don't show the "dialpad chooser" UI.
301 boolean needToShowDialpadChooser = false;
302
303 // Resolve the intent
304 final String action = intent.getAction();
305 if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
306 // see if we are "adding a call" from the InCallScreen; false by default.
307 mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
David Brown4dd082b2010-08-11 15:46:59 -0700308
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800309 Uri uri = intent.getData();
310 if (uri != null) {
311 if ("tel".equals(uri.getScheme())) {
312 // Put the requested number into the input area
Virgil Kingd8831122010-03-10 13:44:11 -0800313 String data = uri.getSchemeSpecificPart();
Bai Tao09eb04f2010-09-01 15:34:16 +0800314 setFormattedDigits(data, null);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800315 } else {
316 String type = intent.getType();
317 if (People.CONTENT_ITEM_TYPE.equals(type)
318 || Phones.CONTENT_ITEM_TYPE.equals(type)) {
319 // Query the phone number
320 Cursor c = getContentResolver().query(intent.getData(),
Bai Tao09eb04f2010-09-01 15:34:16 +0800321 new String[] {PhonesColumns.NUMBER, PhonesColumns.NUMBER_KEY},
322 null, null, null);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800323 if (c != null) {
324 if (c.moveToFirst()) {
325 // Put the number into the input area
Bai Tao09eb04f2010-09-01 15:34:16 +0800326 setFormattedDigits(c.getString(0), c.getString(1));
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800327 }
328 c.close();
329 }
330 }
331 }
Bernd Holzhey7ca5e4d2010-08-04 17:26:03 +0200332 } else {
David Brown4dd082b2010-08-11 15:46:59 -0700333 // ACTION_DIAL or ACTION_VIEW with no data.
334 // This behaves basically like ACTION_MAIN: If there's
335 // already an active call, bring up an intermediate UI to
336 // make the user confirm what they really want to do.
337 // Be sure *not* to show the dialpad chooser if this is an
338 // explicit "Add call" action, though.
339 if (!mIsAddCallMode && phoneIsInUse()) {
340 needToShowDialpadChooser = true;
341 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800342 }
343 } else if (Intent.ACTION_MAIN.equals(action)) {
344 // The MAIN action means we're bringing up a blank dialer
345 // (e.g. by selecting the Home shortcut, or tabbing over from
346 // Contacts or Call log.)
347 //
348 // At this point, IF there's already an active call, there's a
349 // good chance that the user got here accidentally (but really
350 // wanted the in-call dialpad instead). So we bring up an
351 // intermediate UI to make the user confirm what they really
352 // want to do.
353 if (phoneIsInUse()) {
354 // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
355 needToShowDialpadChooser = true;
356 }
357 }
358
359 // Bring up the "dialpad chooser" IFF we need to make the user
360 // confirm which dialpad they really want.
361 showDialpadChooser(needToShowDialpadChooser);
362
363 return ignoreState;
364 }
365
Bai Tao09eb04f2010-09-01 15:34:16 +0800366 protected void setFormattedDigits(String data, String normalizedNumber) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800367 // strip the non-dialable numbers out of the data string.
368 String dialString = PhoneNumberUtils.extractNetworkPortion(data);
Bai Tao09eb04f2010-09-01 15:34:16 +0800369 dialString =
370 PhoneNumberUtils.formatNumber(dialString, normalizedNumber, mCurrentCountryIso);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800371 if (!TextUtils.isEmpty(dialString)) {
372 Editable digits = mDigits.getText();
373 digits.replace(0, digits.length(), dialString);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700374 // for some reason this isn't getting called in the digits.replace call above..
375 // but in any case, this will make sure the background drawable looks right
376 afterTextChanged(digits);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800377 }
378 }
379
380 @Override
381 protected void onNewIntent(Intent newIntent) {
382 setIntent(newIntent);
383 resolveIntent();
384 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700385
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800386 @Override
387 protected void onPostCreate(Bundle savedInstanceState) {
388 super.onPostCreate(savedInstanceState);
389
390 // This can't be done in onCreate(), since the auto-restoring of the digits
391 // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
392 // is called. This method will be called every time the activity is created, and
393 // will always happen after onRestoreSavedInstanceState().
394 mDigits.addTextChangedListener(this);
395 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700396
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800397 private void setupKeypad() {
398 // Setup the listeners for the buttons
399 View view = findViewById(R.id.one);
400 view.setOnClickListener(this);
401 view.setOnLongClickListener(this);
402
403 findViewById(R.id.two).setOnClickListener(this);
404 findViewById(R.id.three).setOnClickListener(this);
405 findViewById(R.id.four).setOnClickListener(this);
406 findViewById(R.id.five).setOnClickListener(this);
407 findViewById(R.id.six).setOnClickListener(this);
408 findViewById(R.id.seven).setOnClickListener(this);
409 findViewById(R.id.eight).setOnClickListener(this);
410 findViewById(R.id.nine).setOnClickListener(this);
411 findViewById(R.id.star).setOnClickListener(this);
412
413 view = findViewById(R.id.zero);
414 view.setOnClickListener(this);
415 view.setOnLongClickListener(this);
416
417 findViewById(R.id.pound).setOnClickListener(this);
418 }
419
420 @Override
421 protected void onResume() {
422 super.onResume();
David Browndc1dfe22010-03-01 14:34:57 -0800423
Nicolas Cataniac3be69e2010-01-14 14:03:53 -0800424 // Query the last dialed number. Do it first because hitting
425 // the DB is 'slow'. This call is asynchronous.
426 queryLastOutgoingCall();
David Brownc29c7ab2009-07-07 16:00:18 -0700427
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800428 // retrieve the DTMF tone play back setting.
429 mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
430 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
431
Nicolas Catania905e7622009-12-01 08:51:20 -0800432 // Retrieve the haptic feedback setting.
433 mHaptic.checkSystemSetting();
434
Eric Laurentd9efc872009-07-17 11:52:06 -0700435 // if the mToneGenerator creation fails, just continue without it. It is
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800436 // a local audio signal, and is not as important as the dtmf tone itself.
437 synchronized(mToneGeneratorLock) {
438 if (mToneGenerator == null) {
439 try {
Jean-Michel Trividd44f8c2009-11-10 13:00:45 -0800440 // we want the user to be able to control the volume of the dial tones
441 // outside of a call, so we use the stream type that is also mapped to the
442 // volume control keys for this activity
443 mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
444 setVolumeControlStream(DIAL_TONE_STREAM_TYPE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800445 } catch (RuntimeException e) {
446 Log.w(TAG, "Exception caught while creating local tone generator: " + e);
447 mToneGenerator = null;
448 }
449 }
450 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700451
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800452 Activity parent = getParent();
453 // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
454 // digits in the dialer field.
455 if (parent != null && parent instanceof DialtactsActivity) {
456 Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
457 if (dialUri != null) {
458 resolveIntent();
459 }
460 }
461
462 // While we're in the foreground, listen for phone state changes,
463 // purely so that we can take down the "dialpad chooser" if the
464 // phone becomes idle while the chooser UI is visible.
465 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
466 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
467
468 // Potentially show hint text in the mDigits field when the user
469 // hasn't typed any digits yet. (If there's already an active call,
470 // this hint text will remind the user that he's about to add a new
471 // call.)
472 //
473 // TODO: consider adding better UI for the case where *both* lines
474 // are currently in use. (Right now we let the user try to add
475 // another call, but that call is guaranteed to fail. Perhaps the
476 // entire dialer UI should be disabled instead.)
477 if (phoneIsInUse()) {
478 mDigits.setHint(R.string.dialerDialpadHintText);
479 } else {
480 // Common case; no hint necessary.
481 mDigits.setHint(null);
482
483 // Also, a sanity-check: the "dialpad chooser" UI should NEVER
484 // be visible if the phone is idle!
485 showDialpadChooser(false);
486 }
Nicolas Cataniadea164e2009-09-18 06:26:16 -0700487
Nicolas Cataniac3be69e2010-01-14 14:03:53 -0800488 updateDialAndDeleteButtonEnabledState();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800489 }
490
491 @Override
Karl Rosaenf46bc312009-03-24 18:20:48 -0700492 public void onWindowFocusChanged(boolean hasFocus) {
493 if (hasFocus) {
494 // Hide soft keyboard, if visible (it's fugly over button dialer).
495 // The only known case where this will be true is when launching the dialer with
496 // ACTION_DIAL via a soft keyboard. we dismiss it here because we don't
497 // have a window token yet in onCreate / onNewIntent
498 InputMethodManager inputMethodManager = (InputMethodManager)
499 getSystemService(Context.INPUT_METHOD_SERVICE);
Eric Laurentd9efc872009-07-17 11:52:06 -0700500 inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
Karl Rosaenf46bc312009-03-24 18:20:48 -0700501 }
502 }
503
504 @Override
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800505 protected void onPause() {
506 super.onPause();
507
508 // Stop listening for phone state changes.
509 TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
510 telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
511
512 synchronized(mToneGeneratorLock) {
513 if (mToneGenerator != null) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800514 mToneGenerator.release();
515 mToneGenerator = null;
516 }
517 }
Nicolas Cataniac3be69e2010-01-14 14:03:53 -0800518 // TODO: I wonder if we should not check if the AsyncTask that
519 // lookup the last dialed number has completed.
520 mLastNumberDialed = EMPTY_NUMBER; // Since we are going to query again, free stale number.
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800521 }
522
523 @Override
524 public boolean onCreateOptionsMenu(Menu menu) {
Reli Talc2a2a512009-06-10 16:48:00 -0400525 mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800526 .setIcon(android.R.drawable.ic_menu_add);
Reli Talc2a2a512009-06-10 16:48:00 -0400527 m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
528 .setIcon(R.drawable.ic_menu_2sec_pause);
529 mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
530 .setIcon(R.drawable.ic_menu_wait);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800531 return true;
532 }
533
534 @Override
535 public boolean onPrepareOptionsMenu(Menu menu) {
536 // We never show a menu if the "choose dialpad" UI is up.
537 if (dialpadChooserVisible()) {
538 return false;
539 }
540
Nicolas Cataniabe8821e2010-01-15 09:28:13 -0800541 if (isDigitsEmpty()) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800542 mAddToContactMenuItem.setVisible(false);
Reli Talc2a2a512009-06-10 16:48:00 -0400543 m2SecPauseMenuItem.setVisible(false);
544 mWaitMenuItem.setVisible(false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800545 } else {
Nicolas Cataniabe8821e2010-01-15 09:28:13 -0800546 CharSequence digits = mDigits.getText();
547
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800548 // Put the current digits string into an intent
549 Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
Nicolas Cataniabe8821e2010-01-15 09:28:13 -0800550 intent.putExtra(Insert.PHONE, digits);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800551 intent.setType(People.CONTENT_ITEM_TYPE);
552 mAddToContactMenuItem.setIntent(intent);
553 mAddToContactMenuItem.setVisible(true);
Reli Talc2a2a512009-06-10 16:48:00 -0400554
555 // Check out whether to show Pause & Wait option menu items
556 int selectionStart;
557 int selectionEnd;
558 String strDigits = digits.toString();
559
560 selectionStart = mDigits.getSelectionStart();
561 selectionEnd = mDigits.getSelectionEnd();
562
563 if (selectionStart != -1) {
564 if (selectionStart > selectionEnd) {
565 // swap it as we want start to be less then end
566 int tmp = selectionStart;
567 selectionStart = selectionEnd;
568 selectionEnd = tmp;
569 }
570
571 if (selectionStart != 0) {
572 // Pause can be visible if cursor is not in the begining
573 m2SecPauseMenuItem.setVisible(true);
574
575 // For Wait to be visible set of condition to meet
576 mWaitMenuItem.setVisible(showWait(selectionStart,
577 selectionEnd, strDigits));
578 } else {
579 // cursor in the beginning both pause and wait to be invisible
580 m2SecPauseMenuItem.setVisible(false);
581 mWaitMenuItem.setVisible(false);
582 }
583 } else {
584 // cursor is not selected so assume new digit is added to the end
585 int strLength = strDigits.length();
586 mWaitMenuItem.setVisible(showWait(strLength,
587 strLength, strDigits));
588 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800589 }
590 return true;
591 }
592
593 @Override
594 public boolean onKeyDown(int keyCode, KeyEvent event) {
595 switch (keyCode) {
596 case KeyEvent.KEYCODE_CALL: {
597 long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
598 if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
599 // Launch voice dialer
600 Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
601 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
602 try {
603 startActivity(intent);
604 } catch (ActivityNotFoundException e) {
605 }
606 }
607 return true;
608 }
609 case KeyEvent.KEYCODE_1: {
Eric Laurentd9efc872009-07-17 11:52:06 -0700610 long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800611 if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
612 // Long press detected, call voice mail
613 callVoicemail();
614 }
615 return true;
616 }
617 }
618 return super.onKeyDown(keyCode, event);
619 }
620
621 @Override
622 public boolean onKeyUp(int keyCode, KeyEvent event) {
623 switch (keyCode) {
624 case KeyEvent.KEYCODE_CALL: {
Nicolas Catania998763d2010-01-14 14:03:53 -0800625 dialButtonPressed();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800626 return true;
627 }
628 }
629 return super.onKeyUp(keyCode, event);
630 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700631
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800632 private void keyPressed(int keyCode) {
Nicolas Catania905e7622009-12-01 08:51:20 -0800633 mHaptic.vibrate();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800634 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
635 mDigits.onKeyDown(keyCode, event);
636 }
637
638 public boolean onKey(View view, int keyCode, KeyEvent event) {
639 switch (view.getId()) {
640 case R.id.digits:
641 if (keyCode == KeyEvent.KEYCODE_ENTER) {
Nicolas Catania998763d2010-01-14 14:03:53 -0800642 dialButtonPressed();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800643 return true;
644 }
645 break;
646 }
647 return false;
648 }
649
650 public void onClick(View view) {
651 switch (view.getId()) {
652 case R.id.one: {
653 playTone(ToneGenerator.TONE_DTMF_1);
654 keyPressed(KeyEvent.KEYCODE_1);
655 return;
656 }
657 case R.id.two: {
658 playTone(ToneGenerator.TONE_DTMF_2);
659 keyPressed(KeyEvent.KEYCODE_2);
660 return;
661 }
662 case R.id.three: {
663 playTone(ToneGenerator.TONE_DTMF_3);
664 keyPressed(KeyEvent.KEYCODE_3);
665 return;
666 }
667 case R.id.four: {
668 playTone(ToneGenerator.TONE_DTMF_4);
669 keyPressed(KeyEvent.KEYCODE_4);
670 return;
671 }
672 case R.id.five: {
673 playTone(ToneGenerator.TONE_DTMF_5);
674 keyPressed(KeyEvent.KEYCODE_5);
675 return;
676 }
677 case R.id.six: {
678 playTone(ToneGenerator.TONE_DTMF_6);
679 keyPressed(KeyEvent.KEYCODE_6);
680 return;
681 }
682 case R.id.seven: {
683 playTone(ToneGenerator.TONE_DTMF_7);
684 keyPressed(KeyEvent.KEYCODE_7);
685 return;
686 }
687 case R.id.eight: {
688 playTone(ToneGenerator.TONE_DTMF_8);
689 keyPressed(KeyEvent.KEYCODE_8);
690 return;
691 }
692 case R.id.nine: {
693 playTone(ToneGenerator.TONE_DTMF_9);
694 keyPressed(KeyEvent.KEYCODE_9);
695 return;
696 }
697 case R.id.zero: {
698 playTone(ToneGenerator.TONE_DTMF_0);
699 keyPressed(KeyEvent.KEYCODE_0);
700 return;
701 }
702 case R.id.pound: {
703 playTone(ToneGenerator.TONE_DTMF_P);
704 keyPressed(KeyEvent.KEYCODE_POUND);
705 return;
706 }
707 case R.id.star: {
708 playTone(ToneGenerator.TONE_DTMF_S);
709 keyPressed(KeyEvent.KEYCODE_STAR);
710 return;
711 }
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700712 case R.id.deleteButton: {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800713 keyPressed(KeyEvent.KEYCODE_DEL);
714 return;
715 }
Nicolas Catania3040fa32009-10-01 13:00:53 -0700716 case R.id.dialButton: {
Nicolas Catania905e7622009-12-01 08:51:20 -0800717 mHaptic.vibrate(); // Vibrate here too, just like we do for the regular keys
Nicolas Catania998763d2010-01-14 14:03:53 -0800718 dialButtonPressed();
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800719 return;
720 }
Nicolas Catania80bda0f2009-09-19 09:17:14 -0700721 case R.id.voicemailButton: {
722 callVoicemail();
Nicolas Catania905e7622009-12-01 08:51:20 -0800723 mHaptic.vibrate();
Nicolas Catania80bda0f2009-09-19 09:17:14 -0700724 return;
725 }
Nicolas Catania3040fa32009-10-01 13:00:53 -0700726 case R.id.digits: {
Nicolas Cataniabe8821e2010-01-15 09:28:13 -0800727 if (!isDigitsEmpty()) {
Nicolas Catania3040fa32009-10-01 13:00:53 -0700728 mDigits.setCursorVisible(true);
729 }
730 return;
731 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800732 }
733 }
734
735 public boolean onLongClick(View view) {
736 final Editable digits = mDigits.getText();
737 int id = view.getId();
738 switch (id) {
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700739 case R.id.deleteButton: {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800740 digits.clear();
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700741 // TODO: The framework forgets to clear the pressed
742 // status of disabled button. Until this is fixed,
743 // clear manually the pressed status. b/2133127
744 mDelete.setPressed(false);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800745 return true;
746 }
747 case R.id.one: {
Nicolas Cataniabe8821e2010-01-15 09:28:13 -0800748 if (isDigitsEmpty()) {
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800749 callVoicemail();
750 return true;
751 }
752 return false;
753 }
754 case R.id.zero: {
755 keyPressed(KeyEvent.KEYCODE_PLUS);
756 return true;
757 }
758 }
759 return false;
760 }
761
762 void callVoicemail() {
Nicolas Cataniae504f6d2010-05-21 14:06:39 -0700763 startActivity(newVoicemailIntent());
764 mDigits.getText().clear(); // TODO: Fix bug 1745781
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800765 finish();
766 }
767
Nicolas Cataniae504f6d2010-05-21 14:06:39 -0700768 /**
769 * In most cases, when the dial button is pressed, there is a
770 * number in digits area. Pack it in the intent, start the
771 * outgoing call broadcast as a separate task and finish this
772 * activity.
773 *
774 * When there is no digit and the phone is CDMA and off hook,
775 * we're sending a blank flash for CDMA. CDMA networks use Flash
776 * messages when special processing needs to be done, mainly for
777 * 3-way or call waiting scenarios. Presumably, here we're in a
778 * special 3-way scenario where the network needs a blank flash
779 * before being able to add the new participant. (This is not the
780 * case with all 3-way calls, just certain CDMA infrastructures.)
781 *
782 * Otherwise, there is no digit, display the last dialed
783 * number. Don't finish since the user may want to edit it. The
784 * user needs to press the dial button again, to dial it (general
785 * case described above).
786 */
Nicolas Catania998763d2010-01-14 14:03:53 -0800787 void dialButtonPressed() {
Nicolas Cataniae504f6d2010-05-21 14:06:39 -0700788 if (isDigitsEmpty()) { // No number entered.
Paul Bermandbdcde22009-10-09 12:04:10 -0400789 if (phoneIsCdma() && phoneIsOffhook()) {
Nicolas Cataniae504f6d2010-05-21 14:06:39 -0700790 // This is really CDMA specific. On GSM is it possible
791 // to be off hook and wanted to add a 3rd party using
792 // the redial feature.
793 startActivity(newFlashIntent());
Paul Bermandbdcde22009-10-09 12:04:10 -0400794 } else {
Nicolas Cataniae504f6d2010-05-21 14:06:39 -0700795 if (!TextUtils.isEmpty(mLastNumberDialed)) {
796 mDigits.setText(mLastNumberDialed);
797 } else {
798 // There's no "last number dialed" or the
799 // background query is still running. There's
800 // nothing useful for the Dial button to do in
801 // this case. Note: with a soft dial button, this
802 // can never happens since the dial button is
803 // disabled under these conditons.
804 playTone(ToneGenerator.TONE_PROP_NACK);
805 }
Paul Bermandbdcde22009-10-09 12:04:10 -0400806 }
Nicolas Cataniae504f6d2010-05-21 14:06:39 -0700807 } else {
808 final String number = mDigits.getText().toString();
Nicolas Catania998763d2010-01-14 14:03:53 -0800809
Nicolas Cataniae504f6d2010-05-21 14:06:39 -0700810 startActivity(newDialNumberIntent(number));
811 mDigits.getText().clear(); // TODO: Fix bug 1745781
Paul Bermandbdcde22009-10-09 12:04:10 -0400812 finish();
813 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800814 }
815
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800816
817 /**
David Brown22f615f2009-06-25 16:19:19 -0700818 * Plays the specified tone for TONE_LENGTH_MS milliseconds.
819 *
820 * The tone is played locally, using the audio stream for phone calls.
821 * Tones are played only if the "Audible touch tones" user preference
822 * is checked, and are NOT played if the device is in silent mode.
823 *
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800824 * @param tone a tone code from {@link ToneGenerator}
825 */
826 void playTone(int tone) {
827 // if local tone playback is disabled, just return.
828 if (!mDTMFToneEnabled) {
829 return;
830 }
David Brown22f615f2009-06-25 16:19:19 -0700831
832 // Also do nothing if the phone is in silent mode.
833 // We need to re-check the ringer mode for *every* playTone()
834 // call, rather than keeping a local flag that's updated in
835 // onResume(), since it's possible to toggle silent mode without
836 // leaving the current activity (via the ENDCALL-longpress menu.)
837 AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
David Brownd5a15302009-07-20 16:39:47 -0700838 int ringerMode = audioManager.getRingerMode();
839 if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
840 || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
David Brown22f615f2009-06-25 16:19:19 -0700841 return;
842 }
843
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800844 synchronized(mToneGeneratorLock) {
845 if (mToneGenerator == null) {
846 Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
847 return;
848 }
Eric Laurentd9efc872009-07-17 11:52:06 -0700849
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800850 // Start the new tone (will stop any playing tone)
Eric Laurent8487fed2009-09-07 08:45:14 -0700851 mToneGenerator.startTone(tone, TONE_LENGTH_MS);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800852 }
853 }
854
855 /**
856 * Brings up the "dialpad chooser" UI in place of the usual Dialer
857 * elements (the textfield/button and the dialpad underneath).
858 *
859 * We show this UI if the user brings up the Dialer while a call is
860 * already in progress, since there's a good chance we got here
861 * accidentally (and the user really wanted the in-call dialpad instead).
862 * So in this situation we display an intermediate UI that lets the user
863 * explicitly choose between the in-call dialpad ("Use touch tone
864 * keypad") and the regular Dialer ("Add call"). (Or, the option "Return
865 * to call in progress" just goes back to the in-call UI with no dialpad
866 * at all.)
867 *
868 * @param enabled If true, show the "dialpad chooser" instead
869 * of the regular Dialer UI
870 */
871 private void showDialpadChooser(boolean enabled) {
872 if (enabled) {
873 // Log.i(TAG, "Showing dialpad chooser!");
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700874 mDigits.setVisibility(View.GONE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800875 if (mDialpad != null) mDialpad.setVisibility(View.GONE);
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700876 mVoicemailDialAndDeleteRow.setVisibility(View.GONE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800877 mDialpadChooser.setVisibility(View.VISIBLE);
878
879 // Instantiate the DialpadChooserAdapter and hook it up to the
880 // ListView. We do this only once.
881 if (mDialpadChooserAdapter == null) {
882 mDialpadChooserAdapter = new DialpadChooserAdapter(this);
883 mDialpadChooser.setAdapter(mDialpadChooserAdapter);
884 }
885 } else {
886 // Log.i(TAG, "Displaying normal Dialer UI.");
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700887 mDigits.setVisibility(View.VISIBLE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800888 if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -0700889 mVoicemailDialAndDeleteRow.setVisibility(View.VISIBLE);
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -0800890 mDialpadChooser.setVisibility(View.GONE);
891 }
892 }
893
894 /**
895 * @return true if we're currently showing the "dialpad chooser" UI.
896 */
897 private boolean dialpadChooserVisible() {
898 return mDialpadChooser.getVisibility() == View.VISIBLE;
899 }
900
901 /**
902 * Simple list adapter, binding to an icon + text label
903 * for each item in the "dialpad chooser" list.
904 */
905 private static class DialpadChooserAdapter extends BaseAdapter {
906 private LayoutInflater mInflater;
907
908 // Simple struct for a single "choice" item.
909 static class ChoiceItem {
910 String text;
911 Bitmap icon;
912 int id;
913
914 public ChoiceItem(String s, Bitmap b, int i) {
915 text = s;
916 icon = b;
917 id = i;
918 }
919 }
920
921 // IDs for the possible "choices":
922 static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
923 static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
924 static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
925
926 private static final int NUM_ITEMS = 3;
927 private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
928
929 public DialpadChooserAdapter(Context context) {
930 // Cache the LayoutInflate to avoid asking for a new one each time.
931 mInflater = LayoutInflater.from(context);
932
933 // Initialize the possible choices.
934 // TODO: could this be specified entirely in XML?
935
936 // - "Use touch tone keypad"
937 mChoiceItems[0] = new ChoiceItem(
938 context.getString(R.string.dialer_useDtmfDialpad),
939 BitmapFactory.decodeResource(context.getResources(),
940 R.drawable.ic_dialer_fork_tt_keypad),
941 DIALPAD_CHOICE_USE_DTMF_DIALPAD);
942
943 // - "Return to call in progress"
944 mChoiceItems[1] = new ChoiceItem(
945 context.getString(R.string.dialer_returnToInCallScreen),
946 BitmapFactory.decodeResource(context.getResources(),
947 R.drawable.ic_dialer_fork_current_call),
948 DIALPAD_CHOICE_RETURN_TO_CALL);
949
950 // - "Add call"
951 mChoiceItems[2] = new ChoiceItem(
952 context.getString(R.string.dialer_addAnotherCall),
953 BitmapFactory.decodeResource(context.getResources(),
954 R.drawable.ic_dialer_fork_add_call),
955 DIALPAD_CHOICE_ADD_NEW_CALL);
956 }
957
958 public int getCount() {
959 return NUM_ITEMS;
960 }
961
962 /**
963 * Return the ChoiceItem for a given position.
964 */
965 public Object getItem(int position) {
966 return mChoiceItems[position];
967 }
968
969 /**
970 * Return a unique ID for each possible choice.
971 */
972 public long getItemId(int position) {
973 return position;
974 }
975
976 /**
977 * Make a view for each row.
978 */
979 public View getView(int position, View convertView, ViewGroup parent) {
980 // When convertView is non-null, we can reuse it (there's no need
981 // to reinflate it.)
982 if (convertView == null) {
983 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
984 }
985
986 TextView text = (TextView) convertView.findViewById(R.id.text);
987 text.setText(mChoiceItems[position].text);
988
989 ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
990 icon.setImageBitmap(mChoiceItems[position].icon);
991
992 return convertView;
993 }
994 }
995
996 /**
997 * Handle clicks from the dialpad chooser.
998 */
999 public void onItemClick(AdapterView parent, View v, int position, long id) {
1000 DialpadChooserAdapter.ChoiceItem item =
1001 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
1002 int itemId = item.id;
1003 switch (itemId) {
1004 case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
1005 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
1006 // Fire off an intent to go back to the in-call UI
1007 // with the dialpad visible.
1008 returnToInCallScreen(true);
1009 break;
1010
1011 case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
1012 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
1013 // Fire off an intent to go back to the in-call UI
1014 // (with the dialpad hidden).
1015 returnToInCallScreen(false);
1016 break;
1017
1018 case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
1019 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
1020 // Ok, guess the user really did want to be here (in the
1021 // regular Dialer) after all. Bring back the normal Dialer UI.
1022 showDialpadChooser(false);
1023 break;
1024
1025 default:
1026 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
1027 break;
1028 }
1029 }
1030
1031 /**
1032 * Returns to the in-call UI (where there's presumably a call in
1033 * progress) in response to the user selecting "use touch tone keypad"
1034 * or "return to call" from the dialpad chooser.
1035 */
1036 private void returnToInCallScreen(boolean showDialpad) {
1037 try {
1038 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1039 if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
1040 } catch (RemoteException e) {
1041 Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
1042 }
1043
1044 // Finally, finish() ourselves so that we don't stay on the
1045 // activity stack.
1046 // Note that we do this whether or not the showCallScreenWithDialpad()
1047 // call above had any effect or not! (That call is a no-op if the
1048 // phone is idle, which can happen if the current call ends while
1049 // the dialpad chooser is up. In this case we can't show the
1050 // InCallScreen, and there's no point staying here in the Dialer,
1051 // so we just take the user back where he came from...)
1052 finish();
1053 }
1054
1055 /**
1056 * @return true if the phone is "in use", meaning that at least one line
1057 * is active (ie. off hook or ringing or dialing).
1058 */
1059 private boolean phoneIsInUse() {
1060 boolean phoneInUse = false;
1061 try {
1062 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1063 if (phone != null) phoneInUse = !phone.isIdle();
1064 } catch (RemoteException e) {
1065 Log.w(TAG, "phone.isIdle() failed", e);
1066 }
1067 return phoneInUse;
1068 }
David Brownc29c7ab2009-07-07 16:00:18 -07001069
1070 /**
Paul Bermandbdcde22009-10-09 12:04:10 -04001071 * @return true if the phone is a CDMA phone type
1072 */
1073 private boolean phoneIsCdma() {
1074 boolean isCdma = false;
1075 try {
1076 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1077 if (phone != null) {
1078 isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
1079 }
1080 } catch (RemoteException e) {
1081 Log.w(TAG, "phone.getActivePhoneType() failed", e);
1082 }
1083 return isCdma;
1084 }
1085
1086 /**
1087 * @return true if the phone state is OFFHOOK
1088 */
1089 private boolean phoneIsOffhook() {
1090 boolean phoneOffhook = false;
1091 try {
1092 ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
1093 if (phone != null) phoneOffhook = phone.isOffhook();
1094 } catch (RemoteException e) {
1095 Log.w(TAG, "phone.isOffhook() failed", e);
1096 }
1097 return phoneOffhook;
1098 }
1099
Reli Talc2a2a512009-06-10 16:48:00 -04001100
1101 /**
1102 * Returns true whenever any one of the options from the menu is selected.
1103 * Code changes to support dialpad options
1104 */
1105 @Override
1106 public boolean onOptionsItemSelected(MenuItem item) {
1107 switch (item.getItemId()) {
1108 case MENU_2S_PAUSE:
1109 updateDialString(",");
1110 return true;
1111 case MENU_WAIT:
1112 updateDialString(";");
1113 return true;
1114 }
1115 return false;
1116 }
1117
1118 /**
1119 * Updates the dial string (mDigits) after inserting a Pause character (,)
1120 * or Wait character (;).
1121 */
1122 private void updateDialString(String newDigits) {
1123 int selectionStart;
1124 int selectionEnd;
1125
1126 // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
Eric Fischer686782e2009-09-10 17:57:45 -07001127 int anchor = mDigits.getSelectionStart();
1128 int point = mDigits.getSelectionEnd();
1129
1130 selectionStart = Math.min(anchor, point);
1131 selectionEnd = Math.max(anchor, point);
Reli Talc2a2a512009-06-10 16:48:00 -04001132
1133 Editable digits = mDigits.getText();
1134 if (selectionStart != -1 ) {
1135 if (selectionStart == selectionEnd) {
1136 // then there is no selection. So insert the pause at this
1137 // position and update the mDigits.
1138 digits.replace(selectionStart, selectionStart, newDigits);
1139 } else {
Eric Fischer1e2d3a22009-09-17 10:53:10 -07001140 digits.replace(selectionStart, selectionEnd, newDigits);
Nicolas Catania7edbd0c2009-09-28 20:37:33 -07001141 // Unselect: back to a regular cursor, just pass the character inserted.
1142 mDigits.setSelection(selectionStart + 1);
Reli Talc2a2a512009-06-10 16:48:00 -04001143 }
1144 } else {
1145 int len = mDigits.length();
1146 digits.replace(len, len, newDigits);
1147 }
1148 }
1149
1150 /**
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -07001151 * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
Nicolas Cataniadea164e2009-09-18 06:26:16 -07001152 */
Nicolas Cataniac3be69e2010-01-14 14:03:53 -08001153 private void updateDialAndDeleteButtonEnabledState() {
1154 final boolean digitsNotEmpty = !isDigitsEmpty();
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -07001155
Nicolas Cataniac3be69e2010-01-14 14:03:53 -08001156 if (mDialButton != null) {
David Browndc1dfe22010-03-01 14:34:57 -08001157 // On CDMA phones, if we're already on a call, we *always*
1158 // enable the Dial button (since you can press it without
1159 // entering any digits to send an empty flash.)
1160 if (phoneIsCdma() && phoneIsOffhook()) {
1161 mDialButton.setEnabled(true);
Nicolas Cataniac3be69e2010-01-14 14:03:53 -08001162 } else {
David Browndc1dfe22010-03-01 14:34:57 -08001163 // Common case: GSM, or CDMA but not on a call.
1164 // Enable the Dial button if some digits have
1165 // been entered, or if there is a last dialed number
Nicolas Cataniac3be69e2010-01-14 14:03:53 -08001166 // that could be redialed.
1167 mDialButton.setEnabled(digitsNotEmpty ||
1168 !TextUtils.isEmpty(mLastNumberDialed));
Paul Bermandbdcde22009-10-09 12:04:10 -04001169 }
Nicolas Cataniadea164e2009-09-18 06:26:16 -07001170 }
Nicolas Cataniac3be69e2010-01-14 14:03:53 -08001171 mDelete.setEnabled(digitsNotEmpty);
Nicolas Cataniadea164e2009-09-18 06:26:16 -07001172 }
1173
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -07001174
Nicolas Cataniadea164e2009-09-18 06:26:16 -07001175 /**
Nicolas Catania80bda0f2009-09-19 09:17:14 -07001176 * Check if voicemail is enabled/accessible.
1177 */
1178 private void initVoicemailButton() {
1179 boolean hasVoicemail = false;
1180 try {
1181 hasVoicemail = TelephonyManager.getDefault().getVoiceMailNumber() != null;
1182 } catch (SecurityException se) {
1183 // Possibly no READ_PHONE_STATE privilege.
1184 }
1185
Nicolas Cataniaa7e5a5b2009-09-20 10:56:40 -07001186 mVoicemailButton = mVoicemailDialAndDeleteRow.findViewById(R.id.voicemailButton);
Nicolas Catania80bda0f2009-09-19 09:17:14 -07001187 if (hasVoicemail) {
1188 mVoicemailButton.setOnClickListener(this);
1189 } else {
1190 mVoicemailButton.setEnabled(false);
1191 }
1192 }
1193
1194 /**
Reli Talc2a2a512009-06-10 16:48:00 -04001195 * This function return true if Wait menu item can be shown
1196 * otherwise returns false. Assumes the passed string is non-empty
1197 * and the 0th index check is not required.
1198 */
1199 private boolean showWait(int start, int end, String digits) {
1200 if (start == end) {
1201 // visible false in this case
1202 if (start > digits.length()) return false;
1203
1204 // preceding char is ';', so visible should be false
1205 if (digits.charAt(start-1) == ';') return false;
1206
1207 // next char is ';', so visible should be false
1208 if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
1209 } else {
1210 // visible false in this case
1211 if (start > digits.length() || end > digits.length()) return false;
1212
1213 // In this case we need to just check for ';' preceding to start
1214 // or next to end
1215 if (digits.charAt(start-1) == ';') return false;
1216 }
1217 return true;
1218 }
Nicolas Cataniabe8821e2010-01-15 09:28:13 -08001219
1220 /**
1221 * @return true if the widget with the phone number digits is empty.
1222 */
1223 private boolean isDigitsEmpty() {
Nicolas Catania941b76f2010-01-19 14:09:40 -08001224 return mDigits.length() == 0;
Nicolas Cataniabe8821e2010-01-15 09:28:13 -08001225 }
Nicolas Cataniac3be69e2010-01-14 14:03:53 -08001226
1227 /**
1228 * Starts the asyn query to get the last dialed/outgoing
1229 * number. When the background query finishes, mLastNumberDialed
1230 * is set to the last dialed number or an empty string if none
1231 * exists yet.
1232 */
1233 private void queryLastOutgoingCall() {
1234 mLastNumberDialed = EMPTY_NUMBER;
1235 CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
1236 new CallLogAsync.GetLastOutgoingCallArgs(
1237 this,
1238 new CallLogAsync.OnLastOutgoingCallComplete() {
1239 public void lastOutgoingCall(String number) {
1240 // TODO: Filter out emergency numbers if
1241 // the carrier does not want redial for
1242 // these.
1243 mLastNumberDialed = number;
1244 updateDialAndDeleteButtonEnabledState();
1245 }
1246 });
1247 mCallLog.getLastOutgoingCall(lastCallArgs);
1248 }
Dmitri Plotnikov8e86b752010-02-22 17:47:57 -08001249
1250 @Override
1251 public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
1252 boolean globalSearch) {
1253 if (globalSearch) {
1254 super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
1255 } else {
1256 ContactsSearchManager.startSearch(this, initialQuery);
1257 }
1258 }
Nicolas Cataniae504f6d2010-05-21 14:06:39 -07001259
1260 // Helpers for the call intents.
1261 private Intent newVoicemailIntent() {
1262 final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1263 Uri.fromParts("voicemail", EMPTY_NUMBER, null));
1264 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1265 return intent;
1266 }
1267
1268 private Intent newFlashIntent() {
1269 final Intent intent = newDialNumberIntent(EMPTY_NUMBER);
1270 intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
1271 return intent;
1272 }
1273
1274 private Intent newDialNumberIntent(String number) {
1275 final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
1276 Uri.fromParts("tel", number, null));
1277 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1278 return intent;
1279 }
The Android Open Source Project7aa0e4c2009-03-03 19:32:21 -08001280}