Home | History | Annotate | Download | only in dialpad
      1 /*
      2  * Copyright (C) 2011 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.dialpad;
     18 
     19 import com.android.contacts.ContactsUtils;
     20 import com.android.contacts.R;
     21 import com.android.contacts.SpecialCharSequenceMgr;
     22 import com.android.contacts.activities.DialtactsActivity;
     23 import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
     24 import com.android.contacts.util.PhoneNumberFormatter;
     25 import com.android.internal.telephony.ITelephony;
     26 import com.android.phone.CallLogAsync;
     27 import com.android.phone.HapticFeedback;
     28 
     29 import android.app.Activity;
     30 import android.app.AlertDialog;
     31 import android.app.Dialog;
     32 import android.app.DialogFragment;
     33 import android.app.Fragment;
     34 import android.content.Context;
     35 import android.content.DialogInterface;
     36 import android.content.Intent;
     37 import android.content.res.Resources;
     38 import android.database.Cursor;
     39 import android.graphics.Bitmap;
     40 import android.graphics.BitmapFactory;
     41 import android.media.AudioManager;
     42 import android.media.ToneGenerator;
     43 import android.net.Uri;
     44 import android.os.Bundle;
     45 import android.os.RemoteException;
     46 import android.os.ServiceManager;
     47 import android.os.SystemProperties;
     48 import android.provider.Contacts.Intents.Insert;
     49 import android.provider.Contacts.People;
     50 import android.provider.Contacts.Phones;
     51 import android.provider.Contacts.PhonesColumns;
     52 import android.provider.Settings;
     53 import android.telephony.PhoneNumberUtils;
     54 import android.telephony.PhoneStateListener;
     55 import android.telephony.TelephonyManager;
     56 import android.text.Editable;
     57 import android.text.TextUtils;
     58 import android.text.TextWatcher;
     59 import android.text.method.DialerKeyListener;
     60 import android.util.Log;
     61 import android.view.KeyEvent;
     62 import android.view.LayoutInflater;
     63 import android.view.Menu;
     64 import android.view.MenuInflater;
     65 import android.view.MenuItem;
     66 import android.view.View;
     67 import android.view.View.OnClickListener;
     68 import android.view.ViewConfiguration;
     69 import android.view.ViewGroup;
     70 import android.widget.AdapterView;
     71 import android.widget.BaseAdapter;
     72 import android.widget.Button;
     73 import android.widget.EditText;
     74 import android.widget.ImageView;
     75 import android.widget.ListView;
     76 import android.widget.PopupMenu;
     77 import android.widget.TextView;
     78 
     79 import java.util.HashSet;
     80 import java.util.Set;
     81 
     82 /**
     83  * Fragment that displays a twelve-key phone dialpad.
     84  */
     85 public class DialpadFragment extends Fragment
     86         implements View.OnClickListener,
     87         View.OnLongClickListener, View.OnKeyListener,
     88         AdapterView.OnItemClickListener, TextWatcher,
     89         PopupMenu.OnMenuItemClickListener,
     90         ViewPagerVisibilityListener {
     91     private static final String TAG = DialpadFragment.class.getSimpleName();
     92 
     93     private static final String EMPTY_NUMBER = "";
     94 
     95     /** The length of DTMF tones in milliseconds */
     96     private static final int TONE_LENGTH_MS = 150;
     97 
     98     /** The DTMF tone volume relative to other sounds in the stream */
     99     private static final int TONE_RELATIVE_VOLUME = 80;
    100 
    101     /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
    102     private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_MUSIC;
    103 
    104     public interface Listener {
    105         public void onSearchButtonPressed();
    106     }
    107 
    108     /**
    109      * View (usually FrameLayout) containing mDigits field. This can be null, in which mDigits
    110      * isn't enclosed by the container.
    111      */
    112     private View mDigitsContainer;
    113     private EditText mDigits;
    114 
    115     private View mDelete;
    116     private ToneGenerator mToneGenerator;
    117     private Object mToneGeneratorLock = new Object();
    118     private View mDialpad;
    119     private View mAdditionalButtonsRow;
    120 
    121     private View mSearchButton;
    122     private Listener mListener;
    123 
    124     private View mDialButton;
    125     private ListView mDialpadChooser;
    126     private DialpadChooserAdapter mDialpadChooserAdapter;
    127 
    128     /**
    129      * Regular expression prohibiting manual phone call. Can be empty, which means "no rule".
    130      */
    131     private String mProhibitedPhoneNumberRegexp;
    132 
    133     private boolean mShowOptionsMenu;
    134 
    135 
    136     // Last number dialed, retrieved asynchronously from the call DB
    137     // in onCreate. This number is displayed when the user hits the
    138     // send key and cleared in onPause.
    139     CallLogAsync mCallLog = new CallLogAsync();
    140     private String mLastNumberDialed = EMPTY_NUMBER;
    141 
    142     // determines if we want to playback local DTMF tones.
    143     private boolean mDTMFToneEnabled;
    144 
    145     // Vibration (haptic feedback) for dialer key presses.
    146     private HapticFeedback mHaptic = new HapticFeedback();
    147 
    148     /** Identifier for the "Add Call" intent extra. */
    149     static final String ADD_CALL_MODE_KEY = "add_call_mode";
    150 
    151     /**
    152      * Identifier for intent extra for sending an empty Flash message for
    153      * CDMA networks. This message is used by the network to simulate a
    154      * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
    155      *
    156      * TODO: Using an intent extra to tell the phone to send this flash is a
    157      * temporary measure. To be replaced with an ITelephony call in the future.
    158      * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java
    159      * in Phone app until this is replaced with the ITelephony API.
    160      */
    161     static final String EXTRA_SEND_EMPTY_FLASH
    162             = "com.android.phone.extra.SEND_EMPTY_FLASH";
    163 
    164     private String mCurrentCountryIso;
    165 
    166     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
    167         /**
    168          * Listen for phone state changes so that we can take down the
    169          * "dialpad chooser" if the phone becomes idle while the
    170          * chooser UI is visible.
    171          */
    172         @Override
    173         public void onCallStateChanged(int state, String incomingNumber) {
    174             // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
    175             //       + state + ", '" + incomingNumber + "'");
    176             if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
    177                 // Log.i(TAG, "Call ended with dialpad chooser visible!  Taking it down...");
    178                 // Note there's a race condition in the UI here: the
    179                 // dialpad chooser could conceivably disappear (on its
    180                 // own) at the exact moment the user was trying to select
    181                 // one of the choices, which would be confusing.  (But at
    182                 // least that's better than leaving the dialpad chooser
    183                 // onscreen, but useless...)
    184                 showDialpadChooser(false);
    185             }
    186         }
    187     };
    188 
    189     private boolean mWasEmptyBeforeTextChange;
    190 
    191     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    192         mWasEmptyBeforeTextChange = TextUtils.isEmpty(s);
    193     }
    194 
    195     public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
    196         if (mWasEmptyBeforeTextChange != TextUtils.isEmpty(input)) {
    197             final Activity activity = getActivity();
    198             if (activity != null) {
    199                 activity.invalidateOptionsMenu();
    200             }
    201         }
    202 
    203         // DTMF Tones do not need to be played here any longer -
    204         // the DTMF dialer handles that functionality now.
    205     }
    206 
    207     public void afterTextChanged(Editable input) {
    208         if (SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
    209             // A special sequence was entered, clear the digits
    210             mDigits.getText().clear();
    211         }
    212 
    213         if (isDigitsEmpty()) {
    214             mDigits.setCursorVisible(false);
    215         }
    216 
    217         updateDialAndDeleteButtonEnabledState();
    218     }
    219 
    220     @Override
    221     public void onCreate(Bundle state) {
    222         super.onCreate(state);
    223 
    224         mCurrentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
    225 
    226         try {
    227             mHaptic.init(getActivity(),
    228                          getResources().getBoolean(R.bool.config_enable_dialer_key_vibration));
    229         } catch (Resources.NotFoundException nfe) {
    230              Log.e(TAG, "Vibrate control bool missing.", nfe);
    231         }
    232 
    233         setHasOptionsMenu(true);
    234 
    235         mProhibitedPhoneNumberRegexp = getResources().getString(
    236                 R.string.config_prohibited_phone_number_regexp);
    237     }
    238 
    239     @Override
    240     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
    241         View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container, false);
    242 
    243         // Load up the resources for the text field.
    244         Resources r = getResources();
    245 
    246         mDigitsContainer = fragmentView.findViewById(R.id.digits_container);
    247         mDigits = (EditText) fragmentView.findViewById(R.id.digits);
    248         mDigits.setKeyListener(DialerKeyListener.getInstance());
    249         mDigits.setOnClickListener(this);
    250         mDigits.setOnKeyListener(this);
    251         mDigits.setOnLongClickListener(this);
    252         mDigits.addTextChangedListener(this);
    253 
    254         PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits);
    255 
    256         // Soft menu button should appear only when there's no hardware menu button.
    257         final View overflowMenuButton = fragmentView.findViewById(R.id.overflow_menu);
    258         if (overflowMenuButton != null) {
    259             if (ViewConfiguration.get(getActivity()).hasPermanentMenuKey()) {
    260                 overflowMenuButton.setVisibility(View.GONE);
    261             } else {
    262                 overflowMenuButton.setOnClickListener(this);
    263             }
    264         }
    265 
    266         // Check for the presence of the keypad
    267         View oneButton = fragmentView.findViewById(R.id.one);
    268         if (oneButton != null) {
    269             setupKeypad(fragmentView);
    270         }
    271 
    272         mAdditionalButtonsRow = fragmentView.findViewById(R.id.dialpadAdditionalButtons);
    273 
    274         mSearchButton = mAdditionalButtonsRow.findViewById(R.id.searchButton);
    275         if (mSearchButton != null) {
    276             mSearchButton.setOnClickListener(this);
    277         }
    278 
    279         // Check whether we should show the onscreen "Dial" button.
    280         mDialButton = mAdditionalButtonsRow.findViewById(R.id.dialButton);
    281 
    282         if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
    283             mDialButton.setOnClickListener(this);
    284         } else {
    285             mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
    286             mDialButton = null;
    287         }
    288 
    289         mDelete = mAdditionalButtonsRow.findViewById(R.id.deleteButton);
    290         mDelete.setOnClickListener(this);
    291         mDelete.setOnLongClickListener(this);
    292 
    293         mDialpad = fragmentView.findViewById(R.id.dialpad);  // This is null in landscape mode.
    294 
    295         // In landscape we put the keyboard in phone mode.
    296         if (null == mDialpad) {
    297             mDigits.setInputType(android.text.InputType.TYPE_CLASS_PHONE);
    298         } else {
    299             mDigits.setCursorVisible(false);
    300         }
    301 
    302         // Set up the "dialpad chooser" UI; see showDialpadChooser().
    303         mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser);
    304         mDialpadChooser.setOnItemClickListener(this);
    305 
    306         configureScreenFromIntent(getActivity().getIntent());
    307 
    308         return fragmentView;
    309     }
    310 
    311     private boolean isLayoutReady() {
    312         return mDigits != null;
    313     }
    314 
    315     public EditText getDigitsWidget() {
    316         return mDigits;
    317     }
    318 
    319     /**
    320      * @return true when {@link #mDigits} is actually filled by the Intent.
    321      */
    322     private boolean fillDigitsIfNecessary(Intent intent) {
    323         final String action = intent.getAction();
    324         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
    325             Uri uri = intent.getData();
    326             if (uri != null) {
    327                 if ("tel".equals(uri.getScheme())) {
    328                     // Put the requested number into the input area
    329                     String data = uri.getSchemeSpecificPart();
    330                     setFormattedDigits(data, null);
    331                     return true;
    332                 } else {
    333                     String type = intent.getType();
    334                     if (People.CONTENT_ITEM_TYPE.equals(type)
    335                             || Phones.CONTENT_ITEM_TYPE.equals(type)) {
    336                         // Query the phone number
    337                         Cursor c = getActivity().getContentResolver().query(intent.getData(),
    338                                 new String[] {PhonesColumns.NUMBER, PhonesColumns.NUMBER_KEY},
    339                                 null, null, null);
    340                         if (c != null) {
    341                             try {
    342                                 if (c.moveToFirst()) {
    343                                     // Put the number into the input area
    344                                     setFormattedDigits(c.getString(0), c.getString(1));
    345                                     return true;
    346                                 }
    347                             } finally {
    348                                 c.close();
    349                             }
    350                         }
    351                     }
    352                 }
    353             }
    354         }
    355 
    356         return false;
    357     }
    358 
    359     /**
    360      * @see #showDialpadChooser(boolean)
    361      */
    362     private static boolean needToShowDialpadChooser(Intent intent, boolean isAddCallMode) {
    363         final String action = intent.getAction();
    364 
    365         boolean needToShowDialpadChooser = false;
    366 
    367         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
    368             Uri uri = intent.getData();
    369             if (uri == null) {
    370                 // ACTION_DIAL or ACTION_VIEW with no data.
    371                 // This behaves basically like ACTION_MAIN: If there's
    372                 // already an active call, bring up an intermediate UI to
    373                 // make the user confirm what they really want to do.
    374                 // Be sure *not* to show the dialpad chooser if this is an
    375                 // explicit "Add call" action, though.
    376                 if (!isAddCallMode && phoneIsInUse()) {
    377                     needToShowDialpadChooser = true;
    378                 }
    379             }
    380         } else if (Intent.ACTION_MAIN.equals(action)) {
    381             // The MAIN action means we're bringing up a blank dialer
    382             // (e.g. by selecting the Home shortcut, or tabbing over from
    383             // Contacts or Call log.)
    384             //
    385             // At this point, IF there's already an active call, there's a
    386             // good chance that the user got here accidentally (but really
    387             // wanted the in-call dialpad instead).  So we bring up an
    388             // intermediate UI to make the user confirm what they really
    389             // want to do.
    390             if (phoneIsInUse()) {
    391                 // Log.i(TAG, "resolveIntent(): phone is in use; showing dialpad chooser!");
    392                 needToShowDialpadChooser = true;
    393             }
    394         }
    395 
    396         return needToShowDialpadChooser;
    397     }
    398 
    399     private static boolean isAddCallMode(Intent intent) {
    400         final String action = intent.getAction();
    401         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
    402             // see if we are "adding a call" from the InCallScreen; false by default.
    403             return intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
    404         } else {
    405             return false;
    406         }
    407     }
    408 
    409     /**
    410      * Checks the given Intent and changes dialpad's UI state. For example, if the Intent requires
    411      * the screen to enter "Add Call" mode, this method will show correct UI for the mode.
    412      */
    413     public void configureScreenFromIntent(Intent intent) {
    414         if (!isLayoutReady()) {
    415             // This happens typically when parent's Activity#onNewIntent() is called while
    416             // Fragment#onCreateView() isn't called yet, and thus we cannot configure Views at
    417             // this point. onViewCreate() should call this method after preparing layouts, so
    418             // just ignore this call now.
    419             Log.i(TAG,
    420                     "Screen configuration is requested before onCreateView() is called. Ignored");
    421             return;
    422         }
    423 
    424         boolean needToShowDialpadChooser = false;
    425 
    426         final boolean isAddCallMode = isAddCallMode(intent);
    427         if (!isAddCallMode) {
    428             final boolean digitsFilled = fillDigitsIfNecessary(intent);
    429             if (!digitsFilled) {
    430                 needToShowDialpadChooser = needToShowDialpadChooser(intent, isAddCallMode);
    431             }
    432         }
    433         showDialpadChooser(needToShowDialpadChooser);
    434     }
    435 
    436     private void setFormattedDigits(String data, String normalizedNumber) {
    437         // strip the non-dialable numbers out of the data string.
    438         String dialString = PhoneNumberUtils.extractNetworkPortion(data);
    439         dialString =
    440                 PhoneNumberUtils.formatNumber(dialString, normalizedNumber, mCurrentCountryIso);
    441         if (!TextUtils.isEmpty(dialString)) {
    442             Editable digits = mDigits.getText();
    443             digits.replace(0, digits.length(), dialString);
    444             // for some reason this isn't getting called in the digits.replace call above..
    445             // but in any case, this will make sure the background drawable looks right
    446             afterTextChanged(digits);
    447         }
    448     }
    449 
    450     private void setupKeypad(View fragmentView) {
    451         // Setup the listeners for the buttons
    452         View view = fragmentView.findViewById(R.id.one);
    453         view.setOnClickListener(this);
    454         view.setOnLongClickListener(this);
    455 
    456         fragmentView.findViewById(R.id.two).setOnClickListener(this);
    457         fragmentView.findViewById(R.id.three).setOnClickListener(this);
    458         fragmentView.findViewById(R.id.four).setOnClickListener(this);
    459         fragmentView.findViewById(R.id.five).setOnClickListener(this);
    460         fragmentView.findViewById(R.id.six).setOnClickListener(this);
    461         fragmentView.findViewById(R.id.seven).setOnClickListener(this);
    462         fragmentView.findViewById(R.id.eight).setOnClickListener(this);
    463         fragmentView.findViewById(R.id.nine).setOnClickListener(this);
    464         fragmentView.findViewById(R.id.star).setOnClickListener(this);
    465 
    466         view = fragmentView.findViewById(R.id.zero);
    467         view.setOnClickListener(this);
    468         view.setOnLongClickListener(this);
    469 
    470         fragmentView.findViewById(R.id.pound).setOnClickListener(this);
    471     }
    472 
    473     @Override
    474     public void onResume() {
    475         super.onResume();
    476 
    477         // Query the last dialed number. Do it first because hitting
    478         // the DB is 'slow'. This call is asynchronous.
    479         queryLastOutgoingCall();
    480 
    481         // retrieve the DTMF tone play back setting.
    482         mDTMFToneEnabled = Settings.System.getInt(getActivity().getContentResolver(),
    483                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
    484 
    485         // Retrieve the haptic feedback setting.
    486         mHaptic.checkSystemSetting();
    487 
    488         // if the mToneGenerator creation fails, just continue without it.  It is
    489         // a local audio signal, and is not as important as the dtmf tone itself.
    490         synchronized (mToneGeneratorLock) {
    491             if (mToneGenerator == null) {
    492                 try {
    493                     // we want the user to be able to control the volume of the dial tones
    494                     // outside of a call, so we use the stream type that is also mapped to the
    495                     // volume control keys for this activity
    496                     mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
    497                     getActivity().setVolumeControlStream(DIAL_TONE_STREAM_TYPE);
    498                 } catch (RuntimeException e) {
    499                     Log.w(TAG, "Exception caught while creating local tone generator: " + e);
    500                     mToneGenerator = null;
    501                 }
    502             }
    503         }
    504 
    505         Activity parent = getActivity();
    506         if (parent instanceof DialtactsActivity) {
    507             // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
    508             // digits in the dialer field.
    509             fillDigitsIfNecessary(parent.getIntent());
    510         }
    511 
    512         // While we're in the foreground, listen for phone state changes,
    513         // purely so that we can take down the "dialpad chooser" if the
    514         // phone becomes idle while the chooser UI is visible.
    515         TelephonyManager telephonyManager =
    516                 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
    517         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    518 
    519         // Potentially show hint text in the mDigits field when the user
    520         // hasn't typed any digits yet.  (If there's already an active call,
    521         // this hint text will remind the user that he's about to add a new
    522         // call.)
    523         //
    524         // TODO: consider adding better UI for the case where *both* lines
    525         // are currently in use.  (Right now we let the user try to add
    526         // another call, but that call is guaranteed to fail.  Perhaps the
    527         // entire dialer UI should be disabled instead.)
    528         if (phoneIsInUse()) {
    529             mDigits.setHint(R.string.dialerDialpadHintText);
    530         } else {
    531             // Common case; no hint necessary.
    532             mDigits.setHint(null);
    533 
    534             // Also, a sanity-check: the "dialpad chooser" UI should NEVER
    535             // be visible if the phone is idle!
    536             showDialpadChooser(false);
    537         }
    538 
    539         updateDialAndDeleteButtonEnabledState();
    540     }
    541 
    542     @Override
    543     public void onPause() {
    544         super.onPause();
    545 
    546         // Stop listening for phone state changes.
    547         TelephonyManager telephonyManager =
    548                 (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
    549         telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
    550 
    551         synchronized (mToneGeneratorLock) {
    552             if (mToneGenerator != null) {
    553                 mToneGenerator.release();
    554                 mToneGenerator = null;
    555             }
    556         }
    557         // TODO: I wonder if we should not check if the AsyncTask that
    558         // lookup the last dialed number has completed.
    559         mLastNumberDialed = EMPTY_NUMBER;  // Since we are going to query again, free stale number.
    560     }
    561 
    562     @Override
    563     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    564         super.onCreateOptionsMenu(menu, inflater);
    565         if (mShowOptionsMenu && ViewConfiguration.get(getActivity()).hasPermanentMenuKey() &&
    566                 isLayoutReady() && mDialpadChooser != null) {
    567             inflater.inflate(R.menu.dialpad_options, menu);
    568         }
    569     }
    570 
    571     @Override
    572     public void onPrepareOptionsMenu(Menu menu) {
    573         // Hardware menu key should be available and Views should already be ready.
    574         if (mShowOptionsMenu && ViewConfiguration.get(getActivity()).hasPermanentMenuKey() &&
    575                 isLayoutReady() && mDialpadChooser != null) {
    576              setupMenuItems(menu);
    577         }
    578     }
    579 
    580     private void setupMenuItems(Menu menu) {
    581         final MenuItem callSettingsMenuItem = menu.findItem(R.id.menu_call_settings_dialpad);
    582         final MenuItem addToContactMenuItem = menu.findItem(R.id.menu_add_contacts);
    583         final MenuItem twoSecPauseMenuItem = menu.findItem(R.id.menu_2s_pause);
    584         final MenuItem waitMenuItem = menu.findItem(R.id.menu_add_wait);
    585 
    586         final Activity activity = getActivity();
    587         if (activity != null && ViewConfiguration.get(activity).hasPermanentMenuKey()) {
    588             // Call settings should be available via its parent Activity.
    589             callSettingsMenuItem.setVisible(false);
    590         } else {
    591             callSettingsMenuItem.setVisible(true);
    592             callSettingsMenuItem.setIntent(DialtactsActivity.getCallSettingsIntent());
    593         }
    594 
    595         // We show "add to contacts", "2sec pause", and "add wait" menus only when the user is
    596         // seeing usual dialpads and has typed at least one digit.
    597         // We never show a menu if the "choose dialpad" UI is up.
    598         if (dialpadChooserVisible() || isDigitsEmpty()) {
    599             addToContactMenuItem.setVisible(false);
    600             twoSecPauseMenuItem.setVisible(false);
    601             waitMenuItem.setVisible(false);
    602         } else {
    603             final CharSequence digits = mDigits.getText();
    604 
    605             // Put the current digits string into an intent
    606             addToContactMenuItem.setIntent(getAddToContactIntent(digits));
    607             addToContactMenuItem.setVisible(true);
    608 
    609             // Check out whether to show Pause & Wait option menu items
    610             int selectionStart;
    611             int selectionEnd;
    612             String strDigits = digits.toString();
    613 
    614             selectionStart = mDigits.getSelectionStart();
    615             selectionEnd = mDigits.getSelectionEnd();
    616 
    617             if (selectionStart != -1) {
    618                 if (selectionStart > selectionEnd) {
    619                     // swap it as we want start to be less then end
    620                     int tmp = selectionStart;
    621                     selectionStart = selectionEnd;
    622                     selectionEnd = tmp;
    623                 }
    624 
    625                 if (selectionStart != 0) {
    626                     // Pause can be visible if cursor is not in the begining
    627                     twoSecPauseMenuItem.setVisible(true);
    628 
    629                     // For Wait to be visible set of condition to meet
    630                     waitMenuItem.setVisible(showWait(selectionStart, selectionEnd, strDigits));
    631                 } else {
    632                     // cursor in the beginning both pause and wait to be invisible
    633                     twoSecPauseMenuItem.setVisible(false);
    634                     waitMenuItem.setVisible(false);
    635                 }
    636             } else {
    637                 twoSecPauseMenuItem.setVisible(true);
    638 
    639                 // cursor is not selected so assume new digit is added to the end
    640                 int strLength = strDigits.length();
    641                 waitMenuItem.setVisible(showWait(strLength, strLength, strDigits));
    642             }
    643         }
    644     }
    645 
    646     private static Intent getAddToContactIntent(CharSequence digits) {
    647         final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
    648         intent.putExtra(Insert.PHONE, digits);
    649         intent.setType(People.CONTENT_ITEM_TYPE);
    650         return intent;
    651     }
    652 
    653     private void keyPressed(int keyCode) {
    654         mHaptic.vibrate();
    655         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
    656         mDigits.onKeyDown(keyCode, event);
    657 
    658         // If the cursor is at the end of the text we hide it.
    659         final int length = mDigits.length();
    660         if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {
    661             mDigits.setCursorVisible(false);
    662         }
    663     }
    664 
    665     public boolean onKey(View view, int keyCode, KeyEvent event) {
    666         switch (view.getId()) {
    667             case R.id.digits:
    668                 if (keyCode == KeyEvent.KEYCODE_ENTER) {
    669                     dialButtonPressed();
    670                     return true;
    671                 }
    672                 break;
    673         }
    674         return false;
    675     }
    676 
    677     @Override
    678     public void onClick(View view) {
    679         switch (view.getId()) {
    680             case R.id.one: {
    681                 playTone(ToneGenerator.TONE_DTMF_1);
    682                 keyPressed(KeyEvent.KEYCODE_1);
    683                 return;
    684             }
    685             case R.id.two: {
    686                 playTone(ToneGenerator.TONE_DTMF_2);
    687                 keyPressed(KeyEvent.KEYCODE_2);
    688                 return;
    689             }
    690             case R.id.three: {
    691                 playTone(ToneGenerator.TONE_DTMF_3);
    692                 keyPressed(KeyEvent.KEYCODE_3);
    693                 return;
    694             }
    695             case R.id.four: {
    696                 playTone(ToneGenerator.TONE_DTMF_4);
    697                 keyPressed(KeyEvent.KEYCODE_4);
    698                 return;
    699             }
    700             case R.id.five: {
    701                 playTone(ToneGenerator.TONE_DTMF_5);
    702                 keyPressed(KeyEvent.KEYCODE_5);
    703                 return;
    704             }
    705             case R.id.six: {
    706                 playTone(ToneGenerator.TONE_DTMF_6);
    707                 keyPressed(KeyEvent.KEYCODE_6);
    708                 return;
    709             }
    710             case R.id.seven: {
    711                 playTone(ToneGenerator.TONE_DTMF_7);
    712                 keyPressed(KeyEvent.KEYCODE_7);
    713                 return;
    714             }
    715             case R.id.eight: {
    716                 playTone(ToneGenerator.TONE_DTMF_8);
    717                 keyPressed(KeyEvent.KEYCODE_8);
    718                 return;
    719             }
    720             case R.id.nine: {
    721                 playTone(ToneGenerator.TONE_DTMF_9);
    722                 keyPressed(KeyEvent.KEYCODE_9);
    723                 return;
    724             }
    725             case R.id.zero: {
    726                 playTone(ToneGenerator.TONE_DTMF_0);
    727                 keyPressed(KeyEvent.KEYCODE_0);
    728                 return;
    729             }
    730             case R.id.pound: {
    731                 playTone(ToneGenerator.TONE_DTMF_P);
    732                 keyPressed(KeyEvent.KEYCODE_POUND);
    733                 return;
    734             }
    735             case R.id.star: {
    736                 playTone(ToneGenerator.TONE_DTMF_S);
    737                 keyPressed(KeyEvent.KEYCODE_STAR);
    738                 return;
    739             }
    740             case R.id.deleteButton: {
    741                 keyPressed(KeyEvent.KEYCODE_DEL);
    742                 return;
    743             }
    744             case R.id.dialButton: {
    745                 mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
    746                 dialButtonPressed();
    747                 return;
    748             }
    749             case R.id.searchButton: {
    750                 mHaptic.vibrate();
    751                 if (mListener != null) {
    752                     mListener.onSearchButtonPressed();
    753                 }
    754                 return;
    755             }
    756             case R.id.digits: {
    757                 if (!isDigitsEmpty()) {
    758                     mDigits.setCursorVisible(true);
    759                 }
    760                 return;
    761             }
    762             case R.id.overflow_menu: {
    763                 PopupMenu popup = constructPopupMenu(view);
    764                 if (popup != null) {
    765                     popup.show();
    766                 }
    767             }
    768         }
    769     }
    770 
    771     private PopupMenu constructPopupMenu(View anchorView) {
    772         final Context context = getActivity();
    773         if (context == null) {
    774             return null;
    775         }
    776         final PopupMenu popupMenu = new PopupMenu(context, anchorView);
    777         final Menu menu = popupMenu.getMenu();
    778         popupMenu.inflate(R.menu.dialpad_options);
    779         popupMenu.setOnMenuItemClickListener(this);
    780         setupMenuItems(menu);
    781         return popupMenu;
    782     }
    783 
    784     public boolean onLongClick(View view) {
    785         final Editable digits = mDigits.getText();
    786         int id = view.getId();
    787         switch (id) {
    788             case R.id.deleteButton: {
    789                 digits.clear();
    790                 // TODO: The framework forgets to clear the pressed
    791                 // status of disabled button. Until this is fixed,
    792                 // clear manually the pressed status. b/2133127
    793                 mDelete.setPressed(false);
    794                 return true;
    795             }
    796             case R.id.one: {
    797                 if (isDigitsEmpty()) {
    798                     if (isVoicemailAvailable()) {
    799                         callVoicemail();
    800                     } else if (getActivity() != null) {
    801                         DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
    802                                 R.string.dialog_voicemail_not_ready_title,
    803                                 R.string.dialog_voicemail_not_ready_message);
    804                         dialogFragment.show(getFragmentManager(), "voicemail_not_ready");
    805                     }
    806                     return true;
    807                 }
    808                 return false;
    809             }
    810             case R.id.zero: {
    811                 keyPressed(KeyEvent.KEYCODE_PLUS);
    812                 return true;
    813             }
    814             case R.id.digits: {
    815                 // Right now EditText does not show the "paste" option when cursor is not visible.
    816                 // To show that, make the cursor visible, and return false, letting the EditText
    817                 // show the option by itself.
    818                 mDigits.setCursorVisible(true);
    819                 return false;
    820             }
    821         }
    822         return false;
    823     }
    824 
    825     public void callVoicemail() {
    826         startActivity(newVoicemailIntent());
    827         mDigits.getText().clear(); // TODO: Fix bug 1745781
    828         getActivity().finish();
    829     }
    830 
    831     public static class ErrorDialogFragment extends DialogFragment {
    832         private int mTitleResId;
    833         private Integer mMessageResId;  // can be null
    834 
    835         private static final String ARG_TITLE_RES_ID = "argTitleResId";
    836         private static final String ARG_MESSAGE_RES_ID = "argMessageResId";
    837 
    838         public static ErrorDialogFragment newInstance(int titleResId) {
    839             return newInstanceInter(titleResId, null);
    840         }
    841 
    842         public static ErrorDialogFragment newInstance(int titleResId, int messageResId) {
    843             return newInstanceInter(titleResId, messageResId);
    844         }
    845 
    846         private static ErrorDialogFragment newInstanceInter(
    847                 int titleResId, Integer messageResId) {
    848             final ErrorDialogFragment fragment = new ErrorDialogFragment();
    849             final Bundle args = new Bundle();
    850             args.putInt(ARG_TITLE_RES_ID, titleResId);
    851             if (messageResId != null) {
    852                 args.putInt(ARG_MESSAGE_RES_ID, messageResId);
    853             }
    854             fragment.setArguments(args);
    855             return fragment;
    856         }
    857 
    858         @Override
    859         public void onCreate(Bundle savedInstanceState) {
    860             super.onCreate(savedInstanceState);
    861             mTitleResId = getArguments().getInt(ARG_TITLE_RES_ID);
    862             if (getArguments().containsKey(ARG_MESSAGE_RES_ID)) {
    863                 mMessageResId = getArguments().getInt(ARG_MESSAGE_RES_ID);
    864             }
    865         }
    866 
    867         @Override
    868         public Dialog onCreateDialog(Bundle savedInstanceState) {
    869             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
    870             builder.setTitle(mTitleResId)
    871                     .setPositiveButton(android.R.string.ok,
    872                             new DialogInterface.OnClickListener() {
    873                                 @Override
    874                                 public void onClick(DialogInterface dialog, int which) {
    875                                     dismiss();
    876                                 }
    877                             });
    878             if (mMessageResId != null) {
    879                 builder.setMessage(mMessageResId);
    880             }
    881             return builder.create();
    882         }
    883     }
    884 
    885     /**
    886      * In most cases, when the dial button is pressed, there is a
    887      * number in digits area. Pack it in the intent, start the
    888      * outgoing call broadcast as a separate task and finish this
    889      * activity.
    890      *
    891      * When there is no digit and the phone is CDMA and off hook,
    892      * we're sending a blank flash for CDMA. CDMA networks use Flash
    893      * messages when special processing needs to be done, mainly for
    894      * 3-way or call waiting scenarios. Presumably, here we're in a
    895      * special 3-way scenario where the network needs a blank flash
    896      * before being able to add the new participant.  (This is not the
    897      * case with all 3-way calls, just certain CDMA infrastructures.)
    898      *
    899      * Otherwise, there is no digit, display the last dialed
    900      * number. Don't finish since the user may want to edit it. The
    901      * user needs to press the dial button again, to dial it (general
    902      * case described above).
    903      */
    904     public void dialButtonPressed() {
    905         if (isDigitsEmpty()) { // No number entered.
    906             if (phoneIsCdma() && phoneIsOffhook()) {
    907                 // This is really CDMA specific. On GSM is it possible
    908                 // to be off hook and wanted to add a 3rd party using
    909                 // the redial feature.
    910                 startActivity(newFlashIntent());
    911             } else {
    912                 if (!TextUtils.isEmpty(mLastNumberDialed)) {
    913                     // Recall the last number dialed.
    914                     mDigits.setText(mLastNumberDialed);
    915 
    916                     // ...and move the cursor to the end of the digits string,
    917                     // so you'll be able to delete digits using the Delete
    918                     // button (just as if you had typed the number manually.)
    919                     //
    920                     // Note we use mDigits.getText().length() here, not
    921                     // mLastNumberDialed.length(), since the EditText widget now
    922                     // contains a *formatted* version of mLastNumberDialed (due to
    923                     // mTextWatcher) and its length may have changed.
    924                     mDigits.setSelection(mDigits.getText().length());
    925                 } else {
    926                     // There's no "last number dialed" or the
    927                     // background query is still running. There's
    928                     // nothing useful for the Dial button to do in
    929                     // this case.  Note: with a soft dial button, this
    930                     // can never happens since the dial button is
    931                     // disabled under these conditons.
    932                     playTone(ToneGenerator.TONE_PROP_NACK);
    933                 }
    934             }
    935         } else {
    936             final String number = mDigits.getText().toString();
    937 
    938             // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
    939             // test equipment.
    940             // TODO: clean it up.
    941             if (number != null
    942                     && !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
    943                     && number.matches(mProhibitedPhoneNumberRegexp)
    944                     && (SystemProperties.getInt("persist.radio.otaspdial", 0) != 1)) {
    945                 Log.i(TAG, "The phone number is prohibited explicitly by a rule.");
    946                 if (getActivity() != null) {
    947                     DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
    948                                     R.string.dialog_phone_call_prohibited_title);
    949                     dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
    950                 }
    951 
    952                 // Clear the digits just in case.
    953                 mDigits.getText().clear();
    954             } else {
    955                 final Intent intent = newDialNumberIntent(number);
    956                 if (getActivity() instanceof DialtactsActivity) {
    957                     intent.putExtra(DialtactsActivity.EXTRA_CALL_ORIGIN,
    958                             DialtactsActivity.CALL_ORIGIN_DIALTACTS);
    959                 }
    960                 startActivity(intent);
    961                 mDigits.getText().clear();  // TODO: Fix bug 1745781
    962                 getActivity().finish();
    963             }
    964         }
    965     }
    966 
    967     /**
    968      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
    969      *
    970      * The tone is played locally, using the audio stream for phone calls.
    971      * Tones are played only if the "Audible touch tones" user preference
    972      * is checked, and are NOT played if the device is in silent mode.
    973      *
    974      * @param tone a tone code from {@link ToneGenerator}
    975      */
    976     void playTone(int tone) {
    977         // if local tone playback is disabled, just return.
    978         if (!mDTMFToneEnabled) {
    979             return;
    980         }
    981 
    982         // Also do nothing if the phone is in silent mode.
    983         // We need to re-check the ringer mode for *every* playTone()
    984         // call, rather than keeping a local flag that's updated in
    985         // onResume(), since it's possible to toggle silent mode without
    986         // leaving the current activity (via the ENDCALL-longpress menu.)
    987         AudioManager audioManager =
    988                 (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
    989         int ringerMode = audioManager.getRingerMode();
    990         if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
    991             || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
    992             return;
    993         }
    994 
    995         synchronized (mToneGeneratorLock) {
    996             if (mToneGenerator == null) {
    997                 Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone);
    998                 return;
    999             }
   1000 
   1001             // Start the new tone (will stop any playing tone)
   1002             mToneGenerator.startTone(tone, TONE_LENGTH_MS);
   1003         }
   1004     }
   1005 
   1006     /**
   1007      * Brings up the "dialpad chooser" UI in place of the usual Dialer
   1008      * elements (the textfield/button and the dialpad underneath).
   1009      *
   1010      * We show this UI if the user brings up the Dialer while a call is
   1011      * already in progress, since there's a good chance we got here
   1012      * accidentally (and the user really wanted the in-call dialpad instead).
   1013      * So in this situation we display an intermediate UI that lets the user
   1014      * explicitly choose between the in-call dialpad ("Use touch tone
   1015      * keypad") and the regular Dialer ("Add call").  (Or, the option "Return
   1016      * to call in progress" just goes back to the in-call UI with no dialpad
   1017      * at all.)
   1018      *
   1019      * @param enabled If true, show the "dialpad chooser" instead
   1020      *                of the regular Dialer UI
   1021      */
   1022     private void showDialpadChooser(boolean enabled) {
   1023         // Check if onCreateView() is already called by checking one of View objects.
   1024         if (!isLayoutReady()) {
   1025             return;
   1026         }
   1027 
   1028         if (enabled) {
   1029             // Log.i(TAG, "Showing dialpad chooser!");
   1030             if (mDigitsContainer != null) {
   1031                 mDigitsContainer.setVisibility(View.GONE);
   1032             } else {
   1033                 // mDigits is not enclosed by the container. Make the digits field itself gone.
   1034                 mDigits.setVisibility(View.GONE);
   1035             }
   1036             if (mDialpad != null) mDialpad.setVisibility(View.GONE);
   1037             mAdditionalButtonsRow.setVisibility(View.GONE);
   1038             mDialpadChooser.setVisibility(View.VISIBLE);
   1039 
   1040             // Instantiate the DialpadChooserAdapter and hook it up to the
   1041             // ListView.  We do this only once.
   1042             if (mDialpadChooserAdapter == null) {
   1043                 mDialpadChooserAdapter = new DialpadChooserAdapter(getActivity());
   1044             }
   1045             mDialpadChooser.setAdapter(mDialpadChooserAdapter);
   1046         } else {
   1047             // Log.i(TAG, "Displaying normal Dialer UI.");
   1048             if (mDigitsContainer != null) {
   1049                 mDigitsContainer.setVisibility(View.VISIBLE);
   1050             } else {
   1051                 mDigits.setVisibility(View.VISIBLE);
   1052             }
   1053             if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
   1054             mAdditionalButtonsRow.setVisibility(View.VISIBLE);
   1055             mDialpadChooser.setVisibility(View.GONE);
   1056         }
   1057     }
   1058 
   1059     /**
   1060      * @return true if we're currently showing the "dialpad chooser" UI.
   1061      */
   1062     private boolean dialpadChooserVisible() {
   1063         return mDialpadChooser.getVisibility() == View.VISIBLE;
   1064     }
   1065 
   1066     /**
   1067      * Simple list adapter, binding to an icon + text label
   1068      * for each item in the "dialpad chooser" list.
   1069      */
   1070     private static class DialpadChooserAdapter extends BaseAdapter {
   1071         private LayoutInflater mInflater;
   1072 
   1073         // Simple struct for a single "choice" item.
   1074         static class ChoiceItem {
   1075             String text;
   1076             Bitmap icon;
   1077             int id;
   1078 
   1079             public ChoiceItem(String s, Bitmap b, int i) {
   1080                 text = s;
   1081                 icon = b;
   1082                 id = i;
   1083             }
   1084         }
   1085 
   1086         // IDs for the possible "choices":
   1087         static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
   1088         static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
   1089         static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
   1090 
   1091         private static final int NUM_ITEMS = 3;
   1092         private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
   1093 
   1094         public DialpadChooserAdapter(Context context) {
   1095             // Cache the LayoutInflate to avoid asking for a new one each time.
   1096             mInflater = LayoutInflater.from(context);
   1097 
   1098             // Initialize the possible choices.
   1099             // TODO: could this be specified entirely in XML?
   1100 
   1101             // - "Use touch tone keypad"
   1102             mChoiceItems[0] = new ChoiceItem(
   1103                     context.getString(R.string.dialer_useDtmfDialpad),
   1104                     BitmapFactory.decodeResource(context.getResources(),
   1105                                                  R.drawable.ic_dialer_fork_tt_keypad),
   1106                     DIALPAD_CHOICE_USE_DTMF_DIALPAD);
   1107 
   1108             // - "Return to call in progress"
   1109             mChoiceItems[1] = new ChoiceItem(
   1110                     context.getString(R.string.dialer_returnToInCallScreen),
   1111                     BitmapFactory.decodeResource(context.getResources(),
   1112                                                  R.drawable.ic_dialer_fork_current_call),
   1113                     DIALPAD_CHOICE_RETURN_TO_CALL);
   1114 
   1115             // - "Add call"
   1116             mChoiceItems[2] = new ChoiceItem(
   1117                     context.getString(R.string.dialer_addAnotherCall),
   1118                     BitmapFactory.decodeResource(context.getResources(),
   1119                                                  R.drawable.ic_dialer_fork_add_call),
   1120                     DIALPAD_CHOICE_ADD_NEW_CALL);
   1121         }
   1122 
   1123         public int getCount() {
   1124             return NUM_ITEMS;
   1125         }
   1126 
   1127         /**
   1128          * Return the ChoiceItem for a given position.
   1129          */
   1130         public Object getItem(int position) {
   1131             return mChoiceItems[position];
   1132         }
   1133 
   1134         /**
   1135          * Return a unique ID for each possible choice.
   1136          */
   1137         public long getItemId(int position) {
   1138             return position;
   1139         }
   1140 
   1141         /**
   1142          * Make a view for each row.
   1143          */
   1144         public View getView(int position, View convertView, ViewGroup parent) {
   1145             // When convertView is non-null, we can reuse it (there's no need
   1146             // to reinflate it.)
   1147             if (convertView == null) {
   1148                 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
   1149             }
   1150 
   1151             TextView text = (TextView) convertView.findViewById(R.id.text);
   1152             text.setText(mChoiceItems[position].text);
   1153 
   1154             ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
   1155             icon.setImageBitmap(mChoiceItems[position].icon);
   1156 
   1157             return convertView;
   1158         }
   1159     }
   1160 
   1161     /**
   1162      * Handle clicks from the dialpad chooser.
   1163      */
   1164     public void onItemClick(AdapterView parent, View v, int position, long id) {
   1165         DialpadChooserAdapter.ChoiceItem item =
   1166                 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
   1167         int itemId = item.id;
   1168         switch (itemId) {
   1169             case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
   1170                 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
   1171                 // Fire off an intent to go back to the in-call UI
   1172                 // with the dialpad visible.
   1173                 returnToInCallScreen(true);
   1174                 break;
   1175 
   1176             case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
   1177                 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
   1178                 // Fire off an intent to go back to the in-call UI
   1179                 // (with the dialpad hidden).
   1180                 returnToInCallScreen(false);
   1181                 break;
   1182 
   1183             case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
   1184                 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
   1185                 // Ok, guess the user really did want to be here (in the
   1186                 // regular Dialer) after all.  Bring back the normal Dialer UI.
   1187                 showDialpadChooser(false);
   1188                 break;
   1189 
   1190             default:
   1191                 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
   1192                 break;
   1193         }
   1194     }
   1195 
   1196     /**
   1197      * Returns to the in-call UI (where there's presumably a call in
   1198      * progress) in response to the user selecting "use touch tone keypad"
   1199      * or "return to call" from the dialpad chooser.
   1200      */
   1201     private void returnToInCallScreen(boolean showDialpad) {
   1202         try {
   1203             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
   1204             if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
   1205         } catch (RemoteException e) {
   1206             Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
   1207         }
   1208 
   1209         // Finally, finish() ourselves so that we don't stay on the
   1210         // activity stack.
   1211         // Note that we do this whether or not the showCallScreenWithDialpad()
   1212         // call above had any effect or not!  (That call is a no-op if the
   1213         // phone is idle, which can happen if the current call ends while
   1214         // the dialpad chooser is up.  In this case we can't show the
   1215         // InCallScreen, and there's no point staying here in the Dialer,
   1216         // so we just take the user back where he came from...)
   1217         getActivity().finish();
   1218     }
   1219 
   1220     /**
   1221      * @return true if the phone is "in use", meaning that at least one line
   1222      *              is active (ie. off hook or ringing or dialing).
   1223      */
   1224     public static boolean phoneIsInUse() {
   1225         boolean phoneInUse = false;
   1226         try {
   1227             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
   1228             if (phone != null) phoneInUse = !phone.isIdle();
   1229         } catch (RemoteException e) {
   1230             Log.w(TAG, "phone.isIdle() failed", e);
   1231         }
   1232         return phoneInUse;
   1233     }
   1234 
   1235     /**
   1236      * @return true if the phone is a CDMA phone type
   1237      */
   1238     private boolean phoneIsCdma() {
   1239         boolean isCdma = false;
   1240         try {
   1241             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
   1242             if (phone != null) {
   1243                 isCdma = (phone.getActivePhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
   1244             }
   1245         } catch (RemoteException e) {
   1246             Log.w(TAG, "phone.getActivePhoneType() failed", e);
   1247         }
   1248         return isCdma;
   1249     }
   1250 
   1251     /**
   1252      * @return true if the phone state is OFFHOOK
   1253      */
   1254     private boolean phoneIsOffhook() {
   1255         boolean phoneOffhook = false;
   1256         try {
   1257             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
   1258             if (phone != null) phoneOffhook = phone.isOffhook();
   1259         } catch (RemoteException e) {
   1260             Log.w(TAG, "phone.isOffhook() failed", e);
   1261         }
   1262         return phoneOffhook;
   1263     }
   1264 
   1265     /**
   1266      * Returns true whenever any one of the options from the menu is selected.
   1267      * Code changes to support dialpad options
   1268      */
   1269     @Override
   1270     public boolean onOptionsItemSelected(MenuItem item) {
   1271         switch (item.getItemId()) {
   1272             case R.id.menu_2s_pause:
   1273                 updateDialString(",");
   1274                 return true;
   1275             case R.id.menu_add_wait:
   1276                 updateDialString(";");
   1277                 return true;
   1278             default:
   1279                 return false;
   1280         }
   1281     }
   1282 
   1283     @Override
   1284     public boolean onMenuItemClick(MenuItem item) {
   1285         return onOptionsItemSelected(item);
   1286     }
   1287 
   1288     /**
   1289      * Updates the dial string (mDigits) after inserting a Pause character (,)
   1290      * or Wait character (;).
   1291      */
   1292     private void updateDialString(String newDigits) {
   1293         int selectionStart;
   1294         int selectionEnd;
   1295 
   1296         // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
   1297         int anchor = mDigits.getSelectionStart();
   1298         int point = mDigits.getSelectionEnd();
   1299 
   1300         selectionStart = Math.min(anchor, point);
   1301         selectionEnd = Math.max(anchor, point);
   1302 
   1303         Editable digits = mDigits.getText();
   1304         if (selectionStart != -1) {
   1305             if (selectionStart == selectionEnd) {
   1306                 // then there is no selection. So insert the pause at this
   1307                 // position and update the mDigits.
   1308                 digits.replace(selectionStart, selectionStart, newDigits);
   1309             } else {
   1310                 digits.replace(selectionStart, selectionEnd, newDigits);
   1311                 // Unselect: back to a regular cursor, just pass the character inserted.
   1312                 mDigits.setSelection(selectionStart + 1);
   1313             }
   1314         } else {
   1315             int len = mDigits.length();
   1316             digits.replace(len, len, newDigits);
   1317         }
   1318     }
   1319 
   1320     /**
   1321      * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
   1322      */
   1323     private void updateDialAndDeleteButtonEnabledState() {
   1324         final boolean digitsNotEmpty = !isDigitsEmpty();
   1325 
   1326         if (mDialButton != null) {
   1327             // On CDMA phones, if we're already on a call, we *always*
   1328             // enable the Dial button (since you can press it without
   1329             // entering any digits to send an empty flash.)
   1330             if (phoneIsCdma() && phoneIsOffhook()) {
   1331                 mDialButton.setEnabled(true);
   1332             } else {
   1333                 // Common case: GSM, or CDMA but not on a call.
   1334                 // Enable the Dial button if some digits have
   1335                 // been entered, or if there is a last dialed number
   1336                 // that could be redialed.
   1337                 mDialButton.setEnabled(digitsNotEmpty ||
   1338                         !TextUtils.isEmpty(mLastNumberDialed));
   1339             }
   1340         }
   1341         mDelete.setEnabled(digitsNotEmpty);
   1342     }
   1343 
   1344     /**
   1345      * Check if voicemail is enabled/accessible.
   1346      *
   1347      * @return true if voicemail is enabled and accessibly. Note that this can be false
   1348      * "temporarily" after the app boot.
   1349      * @see TelephonyManager#getVoiceMailNumber()
   1350      */
   1351     private boolean isVoicemailAvailable() {
   1352         try {
   1353             return (TelephonyManager.getDefault().getVoiceMailNumber() != null);
   1354         } catch (SecurityException se) {
   1355             // Possibly no READ_PHONE_STATE privilege.
   1356             Log.w(TAG, "SecurityException is thrown. Maybe privilege isn't sufficient.");
   1357         }
   1358         return false;
   1359     }
   1360 
   1361     /**
   1362      * This function return true if Wait menu item can be shown
   1363      * otherwise returns false. Assumes the passed string is non-empty
   1364      * and the 0th index check is not required.
   1365      */
   1366     private static boolean showWait(int start, int end, String digits) {
   1367         if (start == end) {
   1368             // visible false in this case
   1369             if (start > digits.length()) return false;
   1370 
   1371             // preceding char is ';', so visible should be false
   1372             if (digits.charAt(start - 1) == ';') return false;
   1373 
   1374             // next char is ';', so visible should be false
   1375             if ((digits.length() > start) && (digits.charAt(start) == ';')) return false;
   1376         } else {
   1377             // visible false in this case
   1378             if (start > digits.length() || end > digits.length()) return false;
   1379 
   1380             // In this case we need to just check for ';' preceding to start
   1381             // or next to end
   1382             if (digits.charAt(start - 1) == ';') return false;
   1383         }
   1384         return true;
   1385     }
   1386 
   1387     /**
   1388      * @return true if the widget with the phone number digits is empty.
   1389      */
   1390     private boolean isDigitsEmpty() {
   1391         return mDigits.length() == 0;
   1392     }
   1393 
   1394     /**
   1395      * Starts the asyn query to get the last dialed/outgoing
   1396      * number. When the background query finishes, mLastNumberDialed
   1397      * is set to the last dialed number or an empty string if none
   1398      * exists yet.
   1399      */
   1400     private void queryLastOutgoingCall() {
   1401         mLastNumberDialed = EMPTY_NUMBER;
   1402         CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
   1403                 new CallLogAsync.GetLastOutgoingCallArgs(
   1404                     getActivity(),
   1405                     new CallLogAsync.OnLastOutgoingCallComplete() {
   1406                         public void lastOutgoingCall(String number) {
   1407                             // TODO: Filter out emergency numbers if
   1408                             // the carrier does not want redial for
   1409                             // these.
   1410                             mLastNumberDialed = number;
   1411                             updateDialAndDeleteButtonEnabledState();
   1412                         }
   1413                     });
   1414         mCallLog.getLastOutgoingCall(lastCallArgs);
   1415     }
   1416 
   1417     // Helpers for the call intents.
   1418     private Intent newVoicemailIntent() {
   1419         final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
   1420                                          Uri.fromParts("voicemail", EMPTY_NUMBER, null));
   1421         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1422         return intent;
   1423     }
   1424 
   1425     private Intent newFlashIntent() {
   1426         final Intent intent = newDialNumberIntent(EMPTY_NUMBER);
   1427         intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
   1428         return intent;
   1429     }
   1430 
   1431     private Intent newDialNumberIntent(String number) {
   1432         final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
   1433                                          Uri.fromParts("tel", number, null));
   1434         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1435         return intent;
   1436     }
   1437 
   1438     public void setListener(Listener listener) {
   1439         mListener = listener;
   1440     }
   1441 
   1442     @Override
   1443     public void onVisibilityChanged(boolean visible) {
   1444         mShowOptionsMenu = visible;
   1445     }
   1446 }
   1447