Home | History | Annotate | Download | only in contacts
      1 /*
      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 
     17 package com.android.contacts;
     18 
     19 import com.android.internal.telephony.ITelephony;
     20 import com.android.phone.CallLogAsync;
     21 import com.android.phone.HapticFeedback;
     22 
     23 import android.app.Activity;
     24 import android.content.ActivityNotFoundException;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.res.Configuration;
     28 import android.content.res.Resources;
     29 import android.database.Cursor;
     30 import android.graphics.Bitmap;
     31 import android.graphics.BitmapFactory;
     32 import android.graphics.drawable.Drawable;
     33 import android.media.AudioManager;
     34 import android.media.ToneGenerator;
     35 import android.net.Uri;
     36 import android.os.Bundle;
     37 import android.os.RemoteException;
     38 import android.os.ServiceManager;
     39 import android.os.SystemClock;
     40 import android.provider.Settings;
     41 import android.provider.Contacts.People;
     42 import android.provider.Contacts.Phones;
     43 import android.provider.Contacts.PhonesColumns;
     44 import android.provider.Contacts.Intents.Insert;
     45 import android.telephony.PhoneNumberFormattingTextWatcher;
     46 import android.telephony.PhoneNumberUtils;
     47 import android.telephony.PhoneStateListener;
     48 import android.telephony.TelephonyManager;
     49 import android.text.Editable;
     50 import android.text.TextUtils;
     51 import android.text.TextWatcher;
     52 import android.text.method.DialerKeyListener;
     53 import android.util.Log;
     54 import android.view.KeyEvent;
     55 import android.view.LayoutInflater;
     56 import android.view.Menu;
     57 import android.view.MenuItem;
     58 import android.view.View;
     59 import android.view.ViewConfiguration;
     60 import android.view.ViewGroup;
     61 import android.view.Window;
     62 import android.view.inputmethod.InputMethodManager;
     63 import android.widget.AdapterView;
     64 import android.widget.BaseAdapter;
     65 import android.widget.EditText;
     66 import android.widget.ImageView;
     67 import android.widget.ListView;
     68 import android.widget.TextView;
     69 
     70 /**
     71  * Dialer activity that displays the typical twelve key interface.
     72  */
     73 @SuppressWarnings("deprecation")
     74 public class TwelveKeyDialer extends Activity implements View.OnClickListener,
     75         View.OnLongClickListener, View.OnKeyListener,
     76         AdapterView.OnItemClickListener, TextWatcher {
     77     private static final String EMPTY_NUMBER = "";
     78     private static final String TAG = "TwelveKeyDialer";
     79 
     80     /** The length of DTMF tones in milliseconds */
     81     private static final int TONE_LENGTH_MS = 150;
     82 
     83     /** The DTMF tone volume relative to other sounds in the stream */
     84     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;
     88 
     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;
     96     private View mDialpad;
     97     private View mVoicemailDialAndDeleteRow;
     98     private View mVoicemailButton;
     99     private View mDialButton;
    100     private ListView mDialpadChooser;
    101     private DialpadChooserAdapter mDialpadChooserAdapter;
    102     //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;
    108 
    109     // 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 
    115     // determines if we want to playback local DTMF tones.
    116     private boolean mDTMFToneEnabled;
    117 
    118     // Vibration (haptic feedback) for dialer key presses.
    119     private HapticFeedback mHaptic = new HapticFeedback();
    120 
    121     /** Identifier for the "Add Call" intent extra. */
    122     static final String ADD_CALL_MODE_KEY = "add_call_mode";
    123 
    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 
    137     /** Indicates if we are opening this dialer to add a call from the InCallScreen. */
    138     private boolean mIsAddCallMode;
    139 
    140     PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
    141             /**
    142              * Listen for phone state changes so that we can take down the
    143              * "dialpad chooser" if the phone becomes idle while the
    144              * chooser UI is visible.
    145              */
    146             @Override
    147             public void onCallStateChanged(int state, String incomingNumber) {
    148                 // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
    149                 //       + state + ", '" + incomingNumber + "'");
    150                 if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
    151                     // Log.i(TAG, "Call ended with dialpad chooser visible!  Taking it down...");
    152                     // Note there's a race condition in the UI here: the
    153                     // dialpad chooser could conceivably disappear (on its
    154                     // own) at the exact moment the user was trying to select
    155                     // one of the choices, which would be confusing.  (But at
    156                     // least that's better than leaving the dialpad chooser
    157                     // onscreen, but useless...)
    158                     showDialpadChooser(false);
    159                 }
    160             }
    161         };
    162 
    163     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    164         // Do nothing
    165     }
    166 
    167     public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
    168         // Do nothing
    169         // DTMF Tones do not need to be played here any longer -
    170         // the DTMF dialer handles that functionality now.
    171     }
    172 
    173     public void afterTextChanged(Editable input) {
    174         if (SpecialCharSequenceMgr.handleChars(this, input.toString(), mDigits)) {
    175             // A special sequence was entered, clear the digits
    176             mDigits.getText().clear();
    177         }
    178 
    179         if (!isDigitsEmpty()) {
    180             mDigits.setBackgroundDrawable(mDigitsBackground);
    181         } else {
    182             mDigits.setCursorVisible(false);
    183             mDigits.setBackgroundDrawable(mDigitsEmptyBackground);
    184         }
    185 
    186         updateDialAndDeleteButtonEnabledState();
    187     }
    188 
    189     @Override
    190     protected void onCreate(Bundle icicle) {
    191         super.onCreate(icicle);
    192 
    193         Resources r = getResources();
    194         // Do not show title in the case the device is in carmode.
    195         if ((r.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK) ==
    196                 Configuration.UI_MODE_TYPE_CAR) {
    197             requestWindowFeature(Window.FEATURE_NO_TITLE);
    198         }
    199         // Set the content view
    200         setContentView(getContentViewResource());
    201 
    202         // Load up the resources for the text field.
    203         mDigitsBackground = r.getDrawable(R.drawable.btn_dial_textfield_active);
    204         mDigitsEmptyBackground = r.getDrawable(R.drawable.btn_dial_textfield);
    205 
    206         mDigits = (EditText) findViewById(R.id.digits);
    207         mDigits.setKeyListener(DialerKeyListener.getInstance());
    208         mDigits.setOnClickListener(this);
    209         mDigits.setOnKeyListener(this);
    210 
    211         maybeAddNumberFormatting();
    212 
    213         // Check for the presence of the keypad
    214         View view = findViewById(R.id.one);
    215         if (view != null) {
    216             setupKeypad();
    217         }
    218 
    219         mVoicemailDialAndDeleteRow = findViewById(R.id.voicemailAndDialAndDelete);
    220 
    221         initVoicemailButton();
    222 
    223         // Check whether we should show the onscreen "Dial" button.
    224         mDialButton = mVoicemailDialAndDeleteRow.findViewById(R.id.dialButton);
    225 
    226         if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
    227             mDialButton.setOnClickListener(this);
    228         } else {
    229             mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
    230             mDialButton = null;
    231         }
    232 
    233         view = mVoicemailDialAndDeleteRow.findViewById(R.id.deleteButton);
    234         view.setOnClickListener(this);
    235         view.setOnLongClickListener(this);
    236         mDelete = view;
    237 
    238         mDialpad = findViewById(R.id.dialpad);  // This is null in landscape mode.
    239 
    240         // In landscape we put the keyboard in phone mode.
    241         // In portrait we prevent the soft keyboard to show since the
    242         // dialpad acts as one already.
    243         if (null == mDialpad) {
    244             mDigits.setInputType(android.text.InputType.TYPE_CLASS_PHONE);
    245         } else {
    246             mDigits.setInputType(android.text.InputType.TYPE_NULL);
    247         }
    248 
    249         // Set up the "dialpad chooser" UI; see showDialpadChooser().
    250         mDialpadChooser = (ListView) findViewById(R.id.dialpadChooser);
    251         mDialpadChooser.setOnItemClickListener(this);
    252 
    253         if (!resolveIntent() && icicle != null) {
    254             super.onRestoreInstanceState(icicle);
    255         }
    256 
    257         try {
    258             mHaptic.init(this, r.getBoolean(R.bool.config_enable_dialer_key_vibration));
    259         } catch (Resources.NotFoundException nfe) {
    260              Log.e(TAG, "Vibrate control bool missing.", nfe);
    261         }
    262 
    263     }
    264 
    265     @Override
    266     protected void onRestoreInstanceState(Bundle icicle) {
    267         // Do nothing, state is restored in onCreate() if needed
    268     }
    269 
    270     protected void maybeAddNumberFormatting() {
    271         mDigits.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
    272     }
    273 
    274     /**
    275      * Overridden by subclasses to control the resource used by the content view.
    276      */
    277     protected int getContentViewResource() {
    278         return R.layout.twelve_key_dialer;
    279     }
    280 
    281     private boolean resolveIntent() {
    282         boolean ignoreState = false;
    283 
    284         // Find the proper intent
    285         final Intent intent;
    286         if (isChild()) {
    287             intent = getParent().getIntent();
    288             ignoreState = intent.getBooleanExtra(DialtactsActivity.EXTRA_IGNORE_STATE, false);
    289         } else {
    290             intent = getIntent();
    291         }
    292         // Log.i(TAG, "==> resolveIntent(): intent: " + intent);
    293 
    294         // by default we are not adding a call.
    295         mIsAddCallMode = false;
    296 
    297         // By default we don't show the "dialpad chooser" UI.
    298         boolean needToShowDialpadChooser = false;
    299 
    300         // Resolve the intent
    301         final String action = intent.getAction();
    302         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
    303             // see if we are "adding a call" from the InCallScreen; false by default.
    304             mIsAddCallMode = intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
    305 
    306             Uri uri = intent.getData();
    307             if (uri != null) {
    308                 if ("tel".equals(uri.getScheme())) {
    309                     // Put the requested number into the input area
    310                     String data = uri.getSchemeSpecificPart();
    311                     setFormattedDigits(data);
    312                 } else {
    313                     String type = intent.getType();
    314                     if (People.CONTENT_ITEM_TYPE.equals(type)
    315                             || Phones.CONTENT_ITEM_TYPE.equals(type)) {
    316                         // Query the phone number
    317                         Cursor c = getContentResolver().query(intent.getData(),
    318                                 new String[] {PhonesColumns.NUMBER}, null, null, null);
    319                         if (c != null) {
    320                             if (c.moveToFirst()) {
    321                                 // Put the number into the input area
    322                                 setFormattedDigits(c.getString(0));
    323                             }
    324                             c.close();
    325                         }
    326                     }
    327                 }
    328             } else {
    329                 // ACTION_DIAL or ACTION_VIEW with no data.
    330                 // This behaves basically like ACTION_MAIN: If there's
    331                 // already an active call, bring up an intermediate UI to
    332                 // make the user confirm what they really want to do.
    333                 // Be sure *not* to show the dialpad chooser if this is an
    334                 // explicit "Add call" action, though.
    335                 if (!mIsAddCallMode && phoneIsInUse()) {
    336                     needToShowDialpadChooser = true;
    337                 }
    338             }
    339         } else if (Intent.ACTION_MAIN.equals(action)) {
    340             // The MAIN action means we're bringing up a blank dialer
    341             // (e.g. by selecting the Home shortcut, or tabbing over from
    342             // Contacts or Call log.)
    343             //
    344             // At this point, IF there's already an active call, there's a
    345             // good chance that the user got here accidentally (but really
    346             // wanted the in-call dialpad instead).  So we bring up an
    347             // intermediate UI to make the user confirm what they really
    348             // want to do.
    349             if (phoneIsInUse()) {
    350                 // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
    351                 needToShowDialpadChooser = true;
    352             }
    353         }
    354 
    355         // Bring up the "dialpad chooser" IFF we need to make the user
    356         // confirm which dialpad they really want.
    357         showDialpadChooser(needToShowDialpadChooser);
    358 
    359         return ignoreState;
    360     }
    361 
    362     protected void setFormattedDigits(String data) {
    363         // strip the non-dialable numbers out of the data string.
    364         String dialString = PhoneNumberUtils.extractNetworkPortion(data);
    365         dialString = PhoneNumberUtils.formatNumber(dialString);
    366         if (!TextUtils.isEmpty(dialString)) {
    367             Editable digits = mDigits.getText();
    368             digits.replace(0, digits.length(), dialString);
    369             // for some reason this isn't getting called in the digits.replace call above..
    370             // but in any case, this will make sure the background drawable looks right
    371             afterTextChanged(digits);
    372         }
    373     }
    374 
    375     @Override
    376     protected void onNewIntent(Intent newIntent) {
    377         setIntent(newIntent);
    378         resolveIntent();
    379     }
    380 
    381     @Override
    382     protected void onPostCreate(Bundle savedInstanceState) {
    383         super.onPostCreate(savedInstanceState);
    384 
    385         // This can't be done in onCreate(), since the auto-restoring of the digits
    386         // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
    387         // is called. This method will be called every time the activity is created, and
    388         // will always happen after onRestoreSavedInstanceState().
    389         mDigits.addTextChangedListener(this);
    390     }
    391 
    392     private void setupKeypad() {
    393         // Setup the listeners for the buttons
    394         View view = findViewById(R.id.one);
    395         view.setOnClickListener(this);
    396         view.setOnLongClickListener(this);
    397 
    398         findViewById(R.id.two).setOnClickListener(this);
    399         findViewById(R.id.three).setOnClickListener(this);
    400         findViewById(R.id.four).setOnClickListener(this);
    401         findViewById(R.id.five).setOnClickListener(this);
    402         findViewById(R.id.six).setOnClickListener(this);
    403         findViewById(R.id.seven).setOnClickListener(this);
    404         findViewById(R.id.eight).setOnClickListener(this);
    405         findViewById(R.id.nine).setOnClickListener(this);
    406         findViewById(R.id.star).setOnClickListener(this);
    407 
    408         view = findViewById(R.id.zero);
    409         view.setOnClickListener(this);
    410         view.setOnLongClickListener(this);
    411 
    412         findViewById(R.id.pound).setOnClickListener(this);
    413     }
    414 
    415     @Override
    416     protected void onResume() {
    417         super.onResume();
    418 
    419         // Query the last dialed number. Do it first because hitting
    420         // the DB is 'slow'. This call is asynchronous.
    421         queryLastOutgoingCall();
    422 
    423         // retrieve the DTMF tone play back setting.
    424         mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
    425                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
    426 
    427         // Retrieve the haptic feedback setting.
    428         mHaptic.checkSystemSetting();
    429 
    430         // if the mToneGenerator creation fails, just continue without it.  It is
    431         // a local audio signal, and is not as important as the dtmf tone itself.
    432         synchronized(mToneGeneratorLock) {
    433             if (mToneGenerator == null) {
    434                 try {
    435                     // we want the user to be able to control the volume of the dial tones
    436                     // outside of a call, so we use the stream type that is also mapped to the
    437                     // volume control keys for this activity
    438                     mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
    439                     setVolumeControlStream(DIAL_TONE_STREAM_TYPE);
    440                 } catch (RuntimeException e) {
    441                     Log.w(TAG, "Exception caught while creating local tone generator: " + e);
    442                     mToneGenerator = null;
    443                 }
    444             }
    445         }
    446 
    447         Activity parent = getParent();
    448         // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
    449         // digits in the dialer field.
    450         if (parent != null && parent instanceof DialtactsActivity) {
    451             Uri dialUri = ((DialtactsActivity) parent).getAndClearDialUri();
    452             if (dialUri != null) {
    453                 resolveIntent();
    454             }
    455         }
    456 
    457         // While we're in the foreground, listen for phone state changes,
    458         // purely so that we can take down the "dialpad chooser" if the
    459         // phone becomes idle while the chooser UI is visible.
    460         TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
    461         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    462 
    463         // Potentially show hint text in the mDigits field when the user
    464         // hasn't typed any digits yet.  (If there's already an active call,
    465         // this hint text will remind the user that he's about to add a new
    466         // call.)
    467         //
    468         // TODO: consider adding better UI for the case where *both* lines
    469         // are currently in use.  (Right now we let the user try to add
    470         // another call, but that call is guaranteed to fail.  Perhaps the
    471         // entire dialer UI should be disabled instead.)
    472         if (phoneIsInUse()) {
    473             mDigits.setHint(R.string.dialerDialpadHintText);
    474         } else {
    475             // Common case; no hint necessary.
    476             mDigits.setHint(null);
    477 
    478             // Also, a sanity-check: the "dialpad chooser" UI should NEVER
    479             // be visible if the phone is idle!
    480             showDialpadChooser(false);
    481         }
    482 
    483         updateDialAndDeleteButtonEnabledState();
    484     }
    485 
    486     @Override
    487     public void onWindowFocusChanged(boolean hasFocus) {
    488         if (hasFocus) {
    489             // Hide soft keyboard, if visible (it's fugly over button dialer).
    490             // The only known case where this will be true is when launching the dialer with
    491             // ACTION_DIAL via a soft keyboard.  we dismiss it here because we don't
    492             // have a window token yet in onCreate / onNewIntent
    493             InputMethodManager inputMethodManager = (InputMethodManager)
    494                     getSystemService(Context.INPUT_METHOD_SERVICE);
    495             inputMethodManager.hideSoftInputFromWindow(mDigits.getWindowToken(), 0);
    496         }
    497     }
    498 
    499     @Override
    500     protected void onPause() {
    501         super.onPause();
    502 
    503         // Stop listening for phone state changes.
    504         TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
    505         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
    506 
    507         synchronized(mToneGeneratorLock) {
    508             if (mToneGenerator != null) {
    509                 mToneGenerator.release();
    510                 mToneGenerator = null;
    511             }
    512         }
    513         // TODO: I wonder if we should not check if the AsyncTask that
    514         // lookup the last dialed number has completed.
    515         mLastNumberDialed = EMPTY_NUMBER;  // Since we are going to query again, free stale number.
    516     }
    517 
    518     @Override
    519     public boolean onCreateOptionsMenu(Menu menu) {
    520         mAddToContactMenuItem = menu.add(0, MENU_ADD_CONTACTS, 0, R.string.recentCalls_addToContact)
    521                 .setIcon(android.R.drawable.ic_menu_add);
    522         m2SecPauseMenuItem = menu.add(0, MENU_2S_PAUSE, 0, R.string.add_2sec_pause)
    523                 .setIcon(R.drawable.ic_menu_2sec_pause);
    524         mWaitMenuItem = menu.add(0, MENU_WAIT, 0, R.string.add_wait)
    525                 .setIcon(R.drawable.ic_menu_wait);
    526         return true;
    527     }
    528 
    529     @Override
    530     public boolean onPrepareOptionsMenu(Menu menu) {
    531         // We never show a menu if the "choose dialpad" UI is up.
    532         if (dialpadChooserVisible()) {
    533             return false;
    534         }
    535 
    536         if (isDigitsEmpty()) {
    537             mAddToContactMenuItem.setVisible(false);
    538             m2SecPauseMenuItem.setVisible(false);
    539             mWaitMenuItem.setVisible(false);
    540         } else {
    541             CharSequence digits = mDigits.getText();
    542 
    543             // Put the current digits string into an intent
    544             Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
    545             intent.putExtra(Insert.PHONE, digits);
    546             intent.setType(People.CONTENT_ITEM_TYPE);
    547             mAddToContactMenuItem.setIntent(intent);
    548             mAddToContactMenuItem.setVisible(true);
    549 
    550             // Check out whether to show Pause & Wait option menu items
    551             int selectionStart;
    552             int selectionEnd;
    553             String strDigits = digits.toString();
    554 
    555             selectionStart = mDigits.getSelectionStart();
    556             selectionEnd = mDigits.getSelectionEnd();
    557 
    558             if (selectionStart != -1) {
    559                 if (selectionStart > selectionEnd) {
    560                     // swap it as we want start to be less then end
    561                     int tmp = selectionStart;
    562                     selectionStart = selectionEnd;
    563                     selectionEnd = tmp;
    564                 }
    565 
    566                 if (selectionStart != 0) {
    567                     // Pause can be visible if cursor is not in the begining
    568                     m2SecPauseMenuItem.setVisible(true);
    569 
    570                     // For Wait to be visible set of condition to meet
    571                     mWaitMenuItem.setVisible(showWait(selectionStart,
    572                                                       selectionEnd, strDigits));
    573                 } else {
    574                     // cursor in the beginning both pause and wait to be invisible
    575                     m2SecPauseMenuItem.setVisible(false);
    576                     mWaitMenuItem.setVisible(false);
    577                 }
    578             } else {
    579                 // cursor is not selected so assume new digit is added to the end
    580                 int strLength = strDigits.length();
    581                 mWaitMenuItem.setVisible(showWait(strLength,
    582                                                       strLength, strDigits));
    583             }
    584         }
    585         return true;
    586     }
    587 
    588     @Override
    589     public boolean onKeyDown(int keyCode, KeyEvent event) {
    590         switch (keyCode) {
    591             case KeyEvent.KEYCODE_CALL: {
    592                 long callPressDiff = SystemClock.uptimeMillis() - event.getDownTime();
    593                 if (callPressDiff >= ViewConfiguration.getLongPressTimeout()) {
    594                     // Launch voice dialer
    595                     Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
    596                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    597                     try {
    598                         startActivity(intent);
    599                     } catch (ActivityNotFoundException e) {
    600                     }
    601                 }
    602                 return true;
    603             }
    604             case KeyEvent.KEYCODE_1: {
    605                 long timeDiff = SystemClock.uptimeMillis() - event.getDownTime();
    606                 if (timeDiff >= ViewConfiguration.getLongPressTimeout()) {
    607                     // Long press detected, call voice mail
    608                     callVoicemail();
    609                 }
    610                 return true;
    611             }
    612         }
    613         return super.onKeyDown(keyCode, event);
    614     }
    615 
    616     @Override
    617     public boolean onKeyUp(int keyCode, KeyEvent event) {
    618         switch (keyCode) {
    619             case KeyEvent.KEYCODE_CALL: {
    620                 // TODO: In dialButtonPressed we do some of these
    621                 // tests again. We should try to consolidate them in
    622                 // one place.
    623                 if (!phoneIsCdma() && mIsAddCallMode && isDigitsEmpty()) {
    624                     // For CDMA phones, we always call
    625                     // dialButtonPressed() because we may need to send
    626                     // an empty flash command to the network.
    627                     // Otherwise, if we are adding a call from the
    628                     // InCallScreen and the phone number entered is
    629                     // empty, we just close the dialer to expose the
    630                     // InCallScreen under it.
    631                     finish();
    632                 }
    633 
    634                 // If we're CDMA, regardless of where we are adding a call from (either
    635                 // InCallScreen or Dialtacts), the user may need to send an empty
    636                 // flash command to the network. So let's call dialButtonPressed() regardless
    637                 // and dialButtonPressed will handle this functionality for us.
    638                 // otherwise, we place the call.
    639                 dialButtonPressed();
    640                 return true;
    641             }
    642         }
    643         return super.onKeyUp(keyCode, event);
    644     }
    645 
    646     private void keyPressed(int keyCode) {
    647         mHaptic.vibrate();
    648         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
    649         mDigits.onKeyDown(keyCode, event);
    650     }
    651 
    652     public boolean onKey(View view, int keyCode, KeyEvent event) {
    653         switch (view.getId()) {
    654             case R.id.digits:
    655                 if (keyCode == KeyEvent.KEYCODE_ENTER) {
    656                     dialButtonPressed();
    657                     return true;
    658                 }
    659                 break;
    660         }
    661         return false;
    662     }
    663 
    664     public void onClick(View view) {
    665         switch (view.getId()) {
    666             case R.id.one: {
    667                 playTone(ToneGenerator.TONE_DTMF_1);
    668                 keyPressed(KeyEvent.KEYCODE_1);
    669                 return;
    670             }
    671             case R.id.two: {
    672                 playTone(ToneGenerator.TONE_DTMF_2);
    673                 keyPressed(KeyEvent.KEYCODE_2);
    674                 return;
    675             }
    676             case R.id.three: {
    677                 playTone(ToneGenerator.TONE_DTMF_3);
    678                 keyPressed(KeyEvent.KEYCODE_3);
    679                 return;
    680             }
    681             case R.id.four: {
    682                 playTone(ToneGenerator.TONE_DTMF_4);
    683                 keyPressed(KeyEvent.KEYCODE_4);
    684                 return;
    685             }
    686             case R.id.five: {
    687                 playTone(ToneGenerator.TONE_DTMF_5);
    688                 keyPressed(KeyEvent.KEYCODE_5);
    689                 return;
    690             }
    691             case R.id.six: {
    692                 playTone(ToneGenerator.TONE_DTMF_6);
    693                 keyPressed(KeyEvent.KEYCODE_6);
    694                 return;
    695             }
    696             case R.id.seven: {
    697                 playTone(ToneGenerator.TONE_DTMF_7);
    698                 keyPressed(KeyEvent.KEYCODE_7);
    699                 return;
    700             }
    701             case R.id.eight: {
    702                 playTone(ToneGenerator.TONE_DTMF_8);
    703                 keyPressed(KeyEvent.KEYCODE_8);
    704                 return;
    705             }
    706             case R.id.nine: {
    707                 playTone(ToneGenerator.TONE_DTMF_9);
    708                 keyPressed(KeyEvent.KEYCODE_9);
    709                 return;
    710             }
    711             case R.id.zero: {
    712                 playTone(ToneGenerator.TONE_DTMF_0);
    713                 keyPressed(KeyEvent.KEYCODE_0);
    714                 return;
    715             }
    716             case R.id.pound: {
    717                 playTone(ToneGenerator.TONE_DTMF_P);
    718                 keyPressed(KeyEvent.KEYCODE_POUND);
    719                 return;
    720             }
    721             case R.id.star: {
    722                 playTone(ToneGenerator.TONE_DTMF_S);
    723                 keyPressed(KeyEvent.KEYCODE_STAR);
    724                 return;
    725             }
    726             case R.id.deleteButton: {
    727                 keyPressed(KeyEvent.KEYCODE_DEL);
    728                 return;
    729             }
    730             case R.id.dialButton: {
    731                 mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
    732                 dialButtonPressed();
    733                 return;
    734             }
    735             case R.id.voicemailButton: {
    736                 callVoicemail();
    737                 mHaptic.vibrate();
    738                 return;
    739             }
    740             case R.id.digits: {
    741                 if (!isDigitsEmpty()) {
    742                     mDigits.setCursorVisible(true);
    743                 }
    744                 return;
    745             }
    746         }
    747     }
    748 
    749     public boolean onLongClick(View view) {
    750         final Editable digits = mDigits.getText();
    751         int id = view.getId();
    752         switch (id) {
    753             case R.id.deleteButton: {
    754                 digits.clear();
    755                 // TODO: The framework forgets to clear the pressed
    756                 // status of disabled button. Until this is fixed,
    757                 // clear manually the pressed status. b/2133127
    758                 mDelete.setPressed(false);
    759                 return true;
    760             }
    761             case R.id.one: {
    762                 if (isDigitsEmpty()) {
    763                     callVoicemail();
    764                     return true;
    765                 }
    766                 return false;
    767             }
    768             case R.id.zero: {
    769                 keyPressed(KeyEvent.KEYCODE_PLUS);
    770                 return true;
    771             }
    772         }
    773         return false;
    774     }
    775 
    776     void callVoicemail() {
    777         StickyTabs.saveTab(this, getIntent());
    778         Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
    779                 Uri.fromParts("voicemail", EMPTY_NUMBER, null));
    780         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    781         startActivity(intent);
    782         mDigits.getText().clear();
    783         finish();
    784     }
    785 
    786     void dialButtonPressed() {
    787         final String number = mDigits.getText().toString();
    788         boolean sendEmptyFlash = false;
    789         Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED);
    790 
    791         if (isDigitsEmpty()) { // There is no number entered.
    792             if (phoneIsCdma() && phoneIsOffhook()) {
    793                 // On CDMA phones, if we're already on a call, pressing
    794                 // the Dial button without entering any digits means "send
    795                 // an empty flash."
    796                 intent.setData(Uri.fromParts("tel", EMPTY_NUMBER, null));
    797                 intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
    798                 sendEmptyFlash = true;
    799             } else if (!TextUtils.isEmpty(mLastNumberDialed)) {
    800                 // Otherwise, pressing the Dial button without entering
    801                 // any digits means "recall the last number dialed".
    802                 mDigits.setText(mLastNumberDialed);
    803                 return;
    804             } else {
    805                 // Rare case: there's no "last number dialed".  There's
    806                 // nothing useful for the Dial button to do in this case.
    807                 playTone(ToneGenerator.TONE_PROP_NACK);
    808                 return;
    809             }
    810         } else {  // There is a number.
    811             intent.setData(Uri.fromParts("tel", number, null));
    812         }
    813 
    814         StickyTabs.saveTab(this, getIntent());
    815         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    816         startActivity(intent);
    817         mDigits.getText().clear();
    818 
    819         // Don't finish TwelveKeyDialer yet if we're sending a blank flash for CDMA. CDMA
    820         // networks use Flash messages when special processing needs to be done, mainly for
    821         // 3-way or call waiting scenarios. Presumably, here we're in a special 3-way scenario
    822         // where the network needs a blank flash before being able to add the new participant.
    823         // (This is not the case with all 3-way calls, just certain CDMA infrastructures.)
    824         if (!sendEmptyFlash) {
    825             finish();
    826         }
    827     }
    828 
    829 
    830     /**
    831      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
    832      *
    833      * The tone is played locally, using the audio stream for phone calls.
    834      * Tones are played only if the "Audible touch tones" user preference
    835      * is checked, and are NOT played if the device is in silent mode.
    836      *
    837      * @param tone a tone code from {@link ToneGenerator}
    838      */
    839     void playTone(int tone) {
    840         // if local tone playback is disabled, just return.
    841         if (!mDTMFToneEnabled) {
    842             return;
    843         }
    844 
    845         // Also do nothing if the phone is in silent mode.
    846         // We need to re-check the ringer mode for *every* playTone()
    847         // call, rather than keeping a local flag that's updated in
    848         // onResume(), since it's possible to toggle silent mode without
    849         // leaving the current activity (via the ENDCALL-longpress menu.)
    850         AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    851         int ringerMode = audioManager.getRingerMode();
    852         if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
    853             || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
    854             return;
    855         }
    856 
    857         synchronized(mToneGeneratorLock) {
    858             if (mToneGenerator == null) {
    859                 Log.w(TAG, "playTone: mToneGenerator == null, tone: "+tone);
    860                 return;
    861             }
    862 
    863             // Start the new tone (will stop any playing tone)
    864             mToneGenerator.startTone(tone, TONE_LENGTH_MS);
    865         }
    866     }
    867 
    868     /**
    869      * Brings up the "dialpad chooser" UI in place of the usual Dialer
    870      * elements (the textfield/button and the dialpad underneath).
    871      *
    872      * We show this UI if the user brings up the Dialer while a call is
    873      * already in progress, since there's a good chance we got here
    874      * accidentally (and the user really wanted the in-call dialpad instead).
    875      * So in this situation we display an intermediate UI that lets the user
    876      * explicitly choose between the in-call dialpad ("Use touch tone
    877      * keypad") and the regular Dialer ("Add call").  (Or, the option "Return
    878      * to call in progress" just goes back to the in-call UI with no dialpad
    879      * at all.)
    880      *
    881      * @param enabled If true, show the "dialpad chooser" instead
    882      *                of the regular Dialer UI
    883      */
    884     private void showDialpadChooser(boolean enabled) {
    885         if (enabled) {
    886             // Log.i(TAG, "Showing dialpad chooser!");
    887             mDigits.setVisibility(View.GONE);
    888             if (mDialpad != null) mDialpad.setVisibility(View.GONE);
    889             mVoicemailDialAndDeleteRow.setVisibility(View.GONE);
    890             mDialpadChooser.setVisibility(View.VISIBLE);
    891 
    892             // Instantiate the DialpadChooserAdapter and hook it up to the
    893             // ListView.  We do this only once.
    894             if (mDialpadChooserAdapter == null) {
    895                 mDialpadChooserAdapter = new DialpadChooserAdapter(this);
    896                 mDialpadChooser.setAdapter(mDialpadChooserAdapter);
    897             }
    898         } else {
    899             // Log.i(TAG, "Displaying normal Dialer UI.");
    900             mDigits.setVisibility(View.VISIBLE);
    901             if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
    902             mVoicemailDialAndDeleteRow.setVisibility(View.VISIBLE);
    903             mDialpadChooser.setVisibility(View.GONE);
    904         }
    905     }
    906 
    907     /**
    908      * @return true if we're currently showing the "dialpad chooser" UI.
    909      */
    910     private boolean dialpadChooserVisible() {
    911         return mDialpadChooser.getVisibility() == View.VISIBLE;
    912     }
    913 
    914     /**
    915      * Simple list adapter, binding to an icon + text label
    916      * for each item in the "dialpad chooser" list.
    917      */
    918     private static class DialpadChooserAdapter extends BaseAdapter {
    919         private LayoutInflater mInflater;
    920 
    921         // Simple struct for a single "choice" item.
    922         static class ChoiceItem {
    923             String text;
    924             Bitmap icon;
    925             int id;
    926 
    927             public ChoiceItem(String s, Bitmap b, int i) {
    928                 text = s;
    929                 icon = b;
    930                 id = i;
    931             }
    932         }
    933 
    934         // IDs for the possible "choices":
    935         static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
    936         static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
    937         static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
    938 
    939         private static final int NUM_ITEMS = 3;
    940         private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
    941 
    942         public DialpadChooserAdapter(Context context) {
    943             // Cache the LayoutInflate to avoid asking for a new one each time.
    944             mInflater = LayoutInflater.from(context);
    945 
    946             // Initialize the possible choices.
    947             // TODO: could this be specified entirely in XML?
    948 
    949             // - "Use touch tone keypad"
    950             mChoiceItems[0] = new ChoiceItem(
    951                     context.getString(R.string.dialer_useDtmfDialpad),
    952                     BitmapFactory.decodeResource(context.getResources(),
    953                                                  R.drawable.ic_dialer_fork_tt_keypad),
    954                     DIALPAD_CHOICE_USE_DTMF_DIALPAD);
    955 
    956             // - "Return to call in progress"
    957             mChoiceItems[1] = new ChoiceItem(
    958                     context.getString(R.string.dialer_returnToInCallScreen),
    959                     BitmapFactory.decodeResource(context.getResources(),
    960                                                  R.drawable.ic_dialer_fork_current_call),
    961                     DIALPAD_CHOICE_RETURN_TO_CALL);
    962 
    963             // - "Add call"
    964             mChoiceItems[2] = new ChoiceItem(
    965                     context.getString(R.string.dialer_addAnotherCall),
    966                     BitmapFactory.decodeResource(context.getResources(),
    967                                                  R.drawable.ic_dialer_fork_add_call),
    968                     DIALPAD_CHOICE_ADD_NEW_CALL);
    969         }
    970 
    971         public int getCount() {
    972             return NUM_ITEMS;
    973         }
    974 
    975         /**
    976          * Return the ChoiceItem for a given position.
    977          */
    978         public Object getItem(int position) {
    979             return mChoiceItems[position];
    980         }
    981 
    982         /**
    983          * Return a unique ID for each possible choice.
    984          */
    985         public long getItemId(int position) {
    986             return position;
    987         }
    988 
    989         /**
    990          * Make a view for each row.
    991          */
    992         public View getView(int position, View convertView, ViewGroup parent) {
    993             // When convertView is non-null, we can reuse it (there's no need
    994             // to reinflate it.)
    995             if (convertView == null) {
    996                 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
    997             }
    998 
    999             TextView text = (TextView) convertView.findViewById(R.id.text);
   1000             text.setText(mChoiceItems[position].text);
   1001 
   1002             ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
   1003             icon.setImageBitmap(mChoiceItems[position].icon);
   1004 
   1005             return convertView;
   1006         }
   1007     }
   1008 
   1009     /**
   1010      * Handle clicks from the dialpad chooser.
   1011      */
   1012     public void onItemClick(AdapterView parent, View v, int position, long id) {
   1013         DialpadChooserAdapter.ChoiceItem item =
   1014                 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
   1015         int itemId = item.id;
   1016         switch (itemId) {
   1017             case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
   1018                 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
   1019                 // Fire off an intent to go back to the in-call UI
   1020                 // with the dialpad visible.
   1021                 returnToInCallScreen(true);
   1022                 break;
   1023 
   1024             case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
   1025                 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
   1026                 // Fire off an intent to go back to the in-call UI
   1027                 // (with the dialpad hidden).
   1028                 returnToInCallScreen(false);
   1029                 break;
   1030 
   1031             case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
   1032                 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
   1033                 // Ok, guess the user really did want to be here (in the
   1034                 // regular Dialer) after all.  Bring back the normal Dialer UI.
   1035                 showDialpadChooser(false);
   1036                 break;
   1037 
   1038             default:
   1039                 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
   1040                 break;
   1041         }
   1042     }
   1043 
   1044     /**
   1045      * Returns to the in-call UI (where there's presumably a call in
   1046      * progress) in response to the user selecting "use touch tone keypad"
   1047      * or "return to call" from the dialpad chooser.
   1048      */
   1049     private void returnToInCallScreen(boolean showDialpad) {
   1050         try {
   1051             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
   1052             if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
   1053         } catch (RemoteException e) {
   1054             Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
   1055         }
   1056 
   1057         // Finally, finish() ourselves so that we don't stay on the
   1058         // activity stack.
   1059         // Note that we do this whether or not the showCallScreenWithDialpad()
   1060         // call above had any effect or not!  (That call is a no-op if the
   1061         // phone is idle, which can happen if the current call ends while
   1062         // the dialpad chooser is up.  In this case we can't show the
   1063         // InCallScreen, and there's no point staying here in the Dialer,
   1064         // so we just take the user back where he came from...)
   1065         finish();
   1066     }
   1067 
   1068     /**
   1069      * @return true if the phone is "in use", meaning that at least one line
   1070      *              is active (ie. off hook or ringing or dialing).
   1071      */
   1072     private boolean phoneIsInUse() {
   1073         boolean phoneInUse = false;
   1074         try {
   1075             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
   1076             if (phone != null) phoneInUse = !phone.isIdle();
   1077         } catch (RemoteException e) {
   1078             Log.w(TAG, "phone.isIdle() failed", e);
   1079         }
   1080         return phoneInUse;
   1081     }
   1082 
   1083     /**
   1084      * @return true if the phone is a CDMA phone type
   1085      */
   1086     private boolean phoneIsCdma() {
   1087         boolean isCdma = false;
   1088         try {
   1089             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
   1090             if (phone != null) {
   1091                 isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
   1092             }
   1093         } catch (RemoteException e) {
   1094             Log.w(TAG, "phone.getActivePhoneType() failed", e);
   1095         }
   1096         return isCdma;
   1097     }
   1098 
   1099     /**
   1100      * @return true if the phone state is OFFHOOK
   1101      */
   1102     private boolean phoneIsOffhook() {
   1103         boolean phoneOffhook = false;
   1104         try {
   1105             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
   1106             if (phone != null) phoneOffhook = phone.isOffhook();
   1107         } catch (RemoteException e) {
   1108             Log.w(TAG, "phone.isOffhook() failed", e);
   1109         }
   1110         return phoneOffhook;
   1111     }
   1112 
   1113 
   1114     /**
   1115      * Returns true whenever any one of the options from the menu is selected.
   1116      * Code changes to support dialpad options
   1117      */
   1118     @Override
   1119     public boolean onOptionsItemSelected(MenuItem item) {
   1120         switch (item.getItemId()) {
   1121             case MENU_2S_PAUSE:
   1122                 updateDialString(",");
   1123                 return true;
   1124             case MENU_WAIT:
   1125                 updateDialString(";");
   1126                 return true;
   1127         }
   1128         return false;
   1129     }
   1130 
   1131     /**
   1132      * Updates the dial string (mDigits) after inserting a Pause character (,)
   1133      * or Wait character (;).
   1134      */
   1135     private void updateDialString(String newDigits) {
   1136         int selectionStart;
   1137         int selectionEnd;
   1138 
   1139         // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
   1140         int anchor = mDigits.getSelectionStart();
   1141         int point = mDigits.getSelectionEnd();
   1142 
   1143         selectionStart = Math.min(anchor, point);
   1144         selectionEnd = Math.max(anchor, point);
   1145 
   1146         Editable digits = mDigits.getText();
   1147         if (selectionStart != -1 ) {
   1148             if (selectionStart == selectionEnd) {
   1149                 // then there is no selection. So insert the pause at this
   1150                 // position and update the mDigits.
   1151                 digits.replace(selectionStart, selectionStart, newDigits);
   1152             } else {
   1153                 digits.replace(selectionStart, selectionEnd, newDigits);
   1154                 // Unselect: back to a regular cursor, just pass the character inserted.
   1155                 mDigits.setSelection(selectionStart + 1);
   1156             }
   1157         } else {
   1158             int len = mDigits.length();
   1159             digits.replace(len, len, newDigits);
   1160         }
   1161     }
   1162 
   1163     /**
   1164      * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
   1165      */
   1166     private void updateDialAndDeleteButtonEnabledState() {
   1167         final boolean digitsNotEmpty = !isDigitsEmpty();
   1168 
   1169         if (mDialButton != null) {
   1170             // On CDMA phones, if we're already on a call, we *always*
   1171             // enable the Dial button (since you can press it without
   1172             // entering any digits to send an empty flash.)
   1173             if (phoneIsCdma() && phoneIsOffhook()) {
   1174                 mDialButton.setEnabled(true);
   1175             } else {
   1176                 // Common case: GSM, or CDMA but not on a call.
   1177                 // Enable the Dial button if some digits have
   1178                 // been entered, or if there is a last dialed number
   1179                 // that could be redialed.
   1180                 mDialButton.setEnabled(digitsNotEmpty ||
   1181                                        !TextUtils.isEmpty(mLastNumberDialed));
   1182             }
   1183         }
   1184         mDelete.setEnabled(digitsNotEmpty);
   1185     }
   1186 
   1187 
   1188     /**
   1189      * Check if voicemail is enabled/accessible.
   1190      */
   1191     private void initVoicemailButton() {
   1192         boolean hasVoicemail = false;
   1193         try {
   1194             hasVoicemail = TelephonyManager.getDefault().getVoiceMailNumber() != null;
   1195         } catch (SecurityException se) {
   1196             // Possibly no READ_PHONE_STATE privilege.
   1197         }
   1198 
   1199         mVoicemailButton = mVoicemailDialAndDeleteRow.findViewById(R.id.voicemailButton);
   1200         if (hasVoicemail) {
   1201             mVoicemailButton.setOnClickListener(this);
   1202         } else {
   1203             mVoicemailButton.setEnabled(false);
   1204         }
   1205     }
   1206 
   1207     /**
   1208      * This function return true if Wait menu item can be shown
   1209      * otherwise returns false. Assumes the passed string is non-empty
   1210      * and the 0th index check is not required.
   1211      */
   1212     private boolean showWait(int start, int end, String digits) {
   1213         if (start == end) {
   1214             // visible false in this case
   1215             if (start > digits.length()) return false;
   1216 
   1217             // preceding char is ';', so visible should be false
   1218             if (digits.charAt(start-1) == ';') return false;
   1219 
   1220             // next char is ';', so visible should be false
   1221             if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
   1222         } else {
   1223             // visible false in this case
   1224             if (start > digits.length() || end > digits.length()) return false;
   1225 
   1226             // In this case we need to just check for ';' preceding to start
   1227             // or next to end
   1228             if (digits.charAt(start-1) == ';') return false;
   1229         }
   1230         return true;
   1231     }
   1232 
   1233     /**
   1234      * @return true if the widget with the phone number digits is empty.
   1235      */
   1236     private boolean isDigitsEmpty() {
   1237         return mDigits.length() == 0;
   1238     }
   1239 
   1240     /**
   1241      * Starts the asyn query to get the last dialed/outgoing
   1242      * number. When the background query finishes, mLastNumberDialed
   1243      * is set to the last dialed number or an empty string if none
   1244      * exists yet.
   1245      */
   1246     private void queryLastOutgoingCall() {
   1247         mLastNumberDialed = EMPTY_NUMBER;
   1248         CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
   1249                 new CallLogAsync.GetLastOutgoingCallArgs(
   1250                     this,
   1251                     new CallLogAsync.OnLastOutgoingCallComplete() {
   1252                         public void lastOutgoingCall(String number) {
   1253                             // TODO: Filter out emergency numbers if
   1254                             // the carrier does not want redial for
   1255                             // these.
   1256                             mLastNumberDialed = number;
   1257                             updateDialAndDeleteButtonEnabledState();
   1258                         }
   1259                     });
   1260         mCallLog.getLastOutgoingCall(lastCallArgs);
   1261     }
   1262 
   1263     @Override
   1264     public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData,
   1265             boolean globalSearch) {
   1266         if (globalSearch) {
   1267             super.startSearch(initialQuery, selectInitialQuery, appSearchData, globalSearch);
   1268         } else {
   1269             ContactsSearchManager.startSearch(this, initialQuery);
   1270         }
   1271     }
   1272 }
   1273