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.dialer.dialpad;
     18 
     19 import android.app.Activity;
     20 import android.app.AlertDialog;
     21 import android.app.Dialog;
     22 import android.app.DialogFragment;
     23 import android.app.Fragment;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.DialogInterface;
     27 import android.content.Intent;
     28 import android.content.res.Resources;
     29 import android.database.Cursor;
     30 import android.graphics.Bitmap;
     31 import android.graphics.BitmapFactory;
     32 import android.media.AudioManager;
     33 import android.media.ToneGenerator;
     34 import android.net.Uri;
     35 import android.os.Bundle;
     36 import android.os.RemoteException;
     37 import android.os.ServiceManager;
     38 import android.os.SystemProperties;
     39 import android.provider.ContactsContract.Contacts;
     40 import android.provider.Contacts.People;
     41 import android.provider.Contacts.Phones;
     42 import android.provider.Contacts.PhonesColumns;
     43 import android.provider.ContactsContract.Intents;
     44 import android.provider.Settings;
     45 import android.telephony.PhoneNumberUtils;
     46 import android.telephony.PhoneStateListener;
     47 import android.telephony.TelephonyManager;
     48 import android.text.Editable;
     49 import android.text.SpannableString;
     50 import android.text.TextUtils;
     51 import android.text.TextWatcher;
     52 import android.text.style.RelativeSizeSpan;
     53 import android.util.AttributeSet;
     54 import android.util.DisplayMetrics;
     55 import android.util.Log;
     56 import android.util.TypedValue;
     57 import android.view.KeyEvent;
     58 import android.view.LayoutInflater;
     59 import android.view.Menu;
     60 import android.view.MenuInflater;
     61 import android.view.MenuItem;
     62 import android.view.MotionEvent;
     63 import android.view.View;
     64 import android.view.ViewConfiguration;
     65 import android.view.ViewGroup;
     66 import android.view.ViewTreeObserver;
     67 import android.view.ViewTreeObserver.OnPreDrawListener;
     68 import android.widget.AdapterView;
     69 import android.widget.BaseAdapter;
     70 import android.widget.EditText;
     71 import android.widget.ImageView;
     72 import android.widget.LinearLayout;
     73 import android.widget.ListView;
     74 import android.widget.PopupMenu;
     75 import android.widget.TableRow;
     76 import android.widget.TextView;
     77 
     78 import com.android.contacts.common.CallUtil;
     79 import com.android.contacts.common.GeoUtil;
     80 import com.android.contacts.common.activity.TransactionSafeActivity;
     81 import com.android.contacts.common.preference.ContactsPreferences;
     82 import com.android.contacts.common.util.PhoneNumberFormatter;
     83 import com.android.contacts.common.util.StopWatch;
     84 import com.android.dialer.NeededForReflection;
     85 import com.android.dialer.DialtactsActivity;
     86 import com.android.dialer.R;
     87 import com.android.dialer.SpecialCharSequenceMgr;
     88 import com.android.dialer.database.DialerDatabaseHelper;
     89 import com.android.dialer.interactions.PhoneNumberInteraction;
     90 import com.android.dialer.util.OrientationUtil;
     91 import com.android.internal.telephony.ITelephony;
     92 import com.android.phone.common.CallLogAsync;
     93 import com.android.phone.common.HapticFeedback;
     94 import com.google.common.annotations.VisibleForTesting;
     95 
     96 import java.util.HashSet;
     97 
     98 /**
     99  * Fragment that displays a twelve-key phone dialpad.
    100  */
    101 public class DialpadFragment extends Fragment
    102         implements View.OnClickListener,
    103         View.OnLongClickListener, View.OnKeyListener,
    104         AdapterView.OnItemClickListener, TextWatcher,
    105         PopupMenu.OnMenuItemClickListener,
    106         DialpadKeyButton.OnPressedListener {
    107     private static final String TAG = DialpadFragment.class.getSimpleName();
    108 
    109     public interface OnDialpadFragmentStartedListener {
    110         public void onDialpadFragmentStarted();
    111     }
    112 
    113     /**
    114      * LinearLayout with getter and setter methods for the translationY property using floats,
    115      * for animation purposes.
    116      */
    117     public static class DialpadSlidingLinearLayout extends LinearLayout {
    118 
    119         public DialpadSlidingLinearLayout(Context context) {
    120             super(context);
    121         }
    122 
    123         public DialpadSlidingLinearLayout(Context context, AttributeSet attrs) {
    124             super(context, attrs);
    125         }
    126 
    127         public DialpadSlidingLinearLayout(Context context, AttributeSet attrs, int defStyle) {
    128             super(context, attrs, defStyle);
    129         }
    130 
    131         @NeededForReflection
    132         public float getYFraction() {
    133             final int height = getHeight();
    134             if (height == 0) return 0;
    135             return getTranslationY() / height;
    136         }
    137 
    138         @NeededForReflection
    139         public void setYFraction(float yFraction) {
    140             setTranslationY(yFraction * getHeight());
    141         }
    142     }
    143 
    144     /**
    145      * LinearLayout that always returns true for onHoverEvent callbacks, to fix
    146      * problems with accessibility due to the dialpad overlaying other fragments.
    147      */
    148     public static class HoverIgnoringLinearLayout extends LinearLayout {
    149 
    150         public HoverIgnoringLinearLayout(Context context) {
    151             super(context);
    152         }
    153 
    154         public HoverIgnoringLinearLayout(Context context, AttributeSet attrs) {
    155             super(context, attrs);
    156         }
    157 
    158         public HoverIgnoringLinearLayout(Context context, AttributeSet attrs, int defStyle) {
    159             super(context, attrs, defStyle);
    160         }
    161 
    162         @Override
    163         public boolean onHoverEvent(MotionEvent event) {
    164             return true;
    165         }
    166     }
    167 
    168     public interface OnDialpadQueryChangedListener {
    169         void onDialpadQueryChanged(String query);
    170     }
    171 
    172     private static final boolean DEBUG = DialtactsActivity.DEBUG;
    173 
    174     // This is the amount of screen the dialpad fragment takes up when fully displayed
    175     private static final float DIALPAD_SLIDE_FRACTION = 0.67f;
    176 
    177     private static final String EMPTY_NUMBER = "";
    178     private static final char PAUSE = ',';
    179     private static final char WAIT = ';';
    180 
    181     /** The length of DTMF tones in milliseconds */
    182     private static final int TONE_LENGTH_MS = 150;
    183     private static final int TONE_LENGTH_INFINITE = -1;
    184 
    185     /** The DTMF tone volume relative to other sounds in the stream */
    186     private static final int TONE_RELATIVE_VOLUME = 80;
    187 
    188     /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
    189     private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF;
    190 
    191     private ContactsPreferences mContactsPrefs;
    192 
    193     private OnDialpadQueryChangedListener mDialpadQueryListener;
    194 
    195     /**
    196      * View (usually FrameLayout) containing mDigits field. This can be null, in which mDigits
    197      * isn't enclosed by the container.
    198      */
    199     private View mDigitsContainer;
    200     private EditText mDigits;
    201 
    202     /** Remembers if we need to clear digits field when the screen is completely gone. */
    203     private boolean mClearDigitsOnStop;
    204 
    205     private View mDelete;
    206     private ToneGenerator mToneGenerator;
    207     private final Object mToneGeneratorLock = new Object();
    208     private View mDialpad;
    209     private View mSpacer;
    210 
    211     /**
    212      * Set of dialpad keys that are currently being pressed
    213      */
    214     private final HashSet<View> mPressedDialpadKeys = new HashSet<View>(12);
    215 
    216     private View mDialButtonContainer;
    217     private View mDialButton;
    218     private ListView mDialpadChooser;
    219     private DialpadChooserAdapter mDialpadChooserAdapter;
    220 
    221     /**
    222      * Regular expression prohibiting manual phone call. Can be empty, which means "no rule".
    223      */
    224     private String mProhibitedPhoneNumberRegexp;
    225 
    226 
    227     // Last number dialed, retrieved asynchronously from the call DB
    228     // in onCreate. This number is displayed when the user hits the
    229     // send key and cleared in onPause.
    230     private final CallLogAsync mCallLog = new CallLogAsync();
    231     private String mLastNumberDialed = EMPTY_NUMBER;
    232 
    233     // determines if we want to playback local DTMF tones.
    234     private boolean mDTMFToneEnabled;
    235 
    236     // Vibration (haptic feedback) for dialer key presses.
    237     private final HapticFeedback mHaptic = new HapticFeedback();
    238 
    239     /** Identifier for the "Add Call" intent extra. */
    240     private static final String ADD_CALL_MODE_KEY = "add_call_mode";
    241 
    242     /**
    243      * Identifier for intent extra for sending an empty Flash message for
    244      * CDMA networks. This message is used by the network to simulate a
    245      * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
    246      *
    247      * TODO: Using an intent extra to tell the phone to send this flash is a
    248      * temporary measure. To be replaced with an ITelephony call in the future.
    249      * TODO: Keep in sync with the string defined in OutgoingCallBroadcaster.java
    250      * in Phone app until this is replaced with the ITelephony API.
    251      */
    252     private static final String EXTRA_SEND_EMPTY_FLASH
    253             = "com.android.phone.extra.SEND_EMPTY_FLASH";
    254 
    255     private String mCurrentCountryIso;
    256 
    257     private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
    258         /**
    259          * Listen for phone state changes so that we can take down the
    260          * "dialpad chooser" if the phone becomes idle while the
    261          * chooser UI is visible.
    262          */
    263         @Override
    264         public void onCallStateChanged(int state, String incomingNumber) {
    265             // Log.i(TAG, "PhoneStateListener.onCallStateChanged: "
    266             //       + state + ", '" + incomingNumber + "'");
    267             if ((state == TelephonyManager.CALL_STATE_IDLE) && dialpadChooserVisible()) {
    268                 // Log.i(TAG, "Call ended with dialpad chooser visible!  Taking it down...");
    269                 // Note there's a race condition in the UI here: the
    270                 // dialpad chooser could conceivably disappear (on its
    271                 // own) at the exact moment the user was trying to select
    272                 // one of the choices, which would be confusing.  (But at
    273                 // least that's better than leaving the dialpad chooser
    274                 // onscreen, but useless...)
    275                 showDialpadChooser(false);
    276             }
    277         }
    278     };
    279 
    280     private boolean mWasEmptyBeforeTextChange;
    281 
    282     /**
    283      * This field is set to true while processing an incoming DIAL intent, in order to make sure
    284      * that SpecialCharSequenceMgr actions can be triggered by user input but *not* by a
    285      * tel: URI passed by some other app.  It will be set to false when all digits are cleared.
    286      */
    287     private boolean mDigitsFilledByIntent;
    288 
    289     private boolean mStartedFromNewIntent = false;
    290     private boolean mFirstLaunch = false;
    291     private boolean mAdjustTranslationForAnimation = false;
    292 
    293     private static final String PREF_DIGITS_FILLED_BY_INTENT = "pref_digits_filled_by_intent";
    294 
    295     /**
    296      * Return an Intent for launching voicemail screen.
    297      */
    298     private static Intent getVoicemailIntent() {
    299         final Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
    300                 Uri.fromParts("voicemail", "", null));
    301         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    302         return intent;
    303     }
    304 
    305     private TelephonyManager getTelephonyManager() {
    306         return (TelephonyManager) getActivity().getSystemService(Context.TELEPHONY_SERVICE);
    307     }
    308 
    309     @Override
    310     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    311         mWasEmptyBeforeTextChange = TextUtils.isEmpty(s);
    312     }
    313 
    314     @Override
    315     public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
    316         if (mWasEmptyBeforeTextChange != TextUtils.isEmpty(input)) {
    317             final Activity activity = getActivity();
    318             if (activity != null) {
    319                 activity.invalidateOptionsMenu();
    320             }
    321         }
    322 
    323         // DTMF Tones do not need to be played here any longer -
    324         // the DTMF dialer handles that functionality now.
    325     }
    326 
    327     @Override
    328     public void afterTextChanged(Editable input) {
    329         // When DTMF dialpad buttons are being pressed, we delay SpecialCharSequencMgr sequence,
    330         // since some of SpecialCharSequenceMgr's behavior is too abrupt for the "touch-down"
    331         // behavior.
    332         if (!mDigitsFilledByIntent &&
    333                 SpecialCharSequenceMgr.handleChars(getActivity(), input.toString(), mDigits)) {
    334             // A special sequence was entered, clear the digits
    335             mDigits.getText().clear();
    336         }
    337 
    338         if (isDigitsEmpty()) {
    339             mDigitsFilledByIntent = false;
    340             mDigits.setCursorVisible(false);
    341         }
    342 
    343         if (mDialpadQueryListener != null) {
    344             mDialpadQueryListener.onDialpadQueryChanged(mDigits.getText().toString());
    345         }
    346         updateDialAndDeleteButtonEnabledState();
    347     }
    348 
    349     @Override
    350     public void onCreate(Bundle state) {
    351         super.onCreate(state);
    352         mFirstLaunch = true;
    353         mContactsPrefs = new ContactsPreferences(getActivity());
    354         mCurrentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
    355 
    356         try {
    357             mHaptic.init(getActivity(),
    358                          getResources().getBoolean(R.bool.config_enable_dialer_key_vibration));
    359         } catch (Resources.NotFoundException nfe) {
    360              Log.e(TAG, "Vibrate control bool missing.", nfe);
    361         }
    362 
    363         mProhibitedPhoneNumberRegexp = getResources().getString(
    364                 R.string.config_prohibited_phone_number_regexp);
    365 
    366         if (state != null) {
    367             mDigitsFilledByIntent = state.getBoolean(PREF_DIGITS_FILLED_BY_INTENT);
    368         }
    369     }
    370 
    371     @Override
    372     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
    373         final View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container,
    374                 false);
    375         fragmentView.buildLayer();
    376 
    377         final ViewTreeObserver vto = fragmentView.getViewTreeObserver();
    378         // Adjust the translation of the DialpadFragment in a preDrawListener instead of in
    379         // DialtactsActivity, because at the point in time when the DialpadFragment is added,
    380         // its views have not been laid out yet.
    381         final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
    382 
    383             @Override
    384             public boolean onPreDraw() {
    385 
    386                 if (isHidden()) return true;
    387                 if (mAdjustTranslationForAnimation && fragmentView.getTranslationY() == 0) {
    388                     ((DialpadSlidingLinearLayout) fragmentView).setYFraction(
    389                             DIALPAD_SLIDE_FRACTION);
    390                 }
    391                 final ViewTreeObserver vto = fragmentView.getViewTreeObserver();
    392                 vto.removeOnPreDrawListener(this);
    393                 return true;
    394             }
    395 
    396         };
    397 
    398         vto.addOnPreDrawListener(preDrawListener);
    399 
    400         // Load up the resources for the text field.
    401         Resources r = getResources();
    402 
    403         mDialButtonContainer = fragmentView.findViewById(R.id.dialButtonContainer);
    404         mDigitsContainer = fragmentView.findViewById(R.id.digits_container);
    405         mDigits = (EditText) fragmentView.findViewById(R.id.digits);
    406         mDigits.setKeyListener(UnicodeDialerKeyListener.INSTANCE);
    407         mDigits.setOnClickListener(this);
    408         mDigits.setOnKeyListener(this);
    409         mDigits.setOnLongClickListener(this);
    410         mDigits.addTextChangedListener(this);
    411         PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits);
    412         // Check for the presence of the keypad
    413         View oneButton = fragmentView.findViewById(R.id.one);
    414         if (oneButton != null) {
    415             setupKeypad(fragmentView);
    416         }
    417 
    418         mDialButton = fragmentView.findViewById(R.id.dialButton);
    419         if (r.getBoolean(R.bool.config_show_onscreen_dial_button)) {
    420             mDialButton.setOnClickListener(this);
    421             mDialButton.setOnLongClickListener(this);
    422         } else {
    423             mDialButton.setVisibility(View.GONE); // It's VISIBLE by default
    424             mDialButton = null;
    425         }
    426 
    427         mDelete = fragmentView.findViewById(R.id.deleteButton);
    428         if (mDelete != null) {
    429             mDelete.setOnClickListener(this);
    430             mDelete.setOnLongClickListener(this);
    431         }
    432 
    433         mSpacer = fragmentView.findViewById(R.id.spacer);
    434         mSpacer.setOnTouchListener(new View.OnTouchListener() {
    435             @Override
    436             public boolean onTouch(View v, MotionEvent event) {
    437                 if (isDigitsEmpty()) {
    438                     hideAndClearDialpad();
    439                     return true;
    440                 }
    441                 return false;
    442             }
    443         });
    444 
    445         mDialpad = fragmentView.findViewById(R.id.dialpad);  // This is null in landscape mode.
    446 
    447         // In landscape we put the keyboard in phone mode.
    448         if (null == mDialpad) {
    449             mDigits.setInputType(android.text.InputType.TYPE_CLASS_PHONE);
    450         } else {
    451             mDigits.setCursorVisible(false);
    452         }
    453 
    454         // Set up the "dialpad chooser" UI; see showDialpadChooser().
    455         mDialpadChooser = (ListView) fragmentView.findViewById(R.id.dialpadChooser);
    456         mDialpadChooser.setOnItemClickListener(this);
    457 
    458         return fragmentView;
    459     }
    460 
    461     @Override
    462     public void onStart() {
    463         super.onStart();
    464 
    465         final Activity activity = getActivity();
    466 
    467         try {
    468             ((OnDialpadFragmentStartedListener) activity).onDialpadFragmentStarted();
    469         } catch (ClassCastException e) {
    470             throw new ClassCastException(activity.toString()
    471                     + " must implement OnDialpadFragmentStartedListener");
    472         }
    473 
    474         final View overflowButton = getView().findViewById(R.id.overflow_menu_on_dialpad);
    475         overflowButton.setOnClickListener(this);
    476     }
    477 
    478     private boolean isLayoutReady() {
    479         return mDigits != null;
    480     }
    481 
    482     public EditText getDigitsWidget() {
    483         return mDigits;
    484     }
    485 
    486     /**
    487      * @return true when {@link #mDigits} is actually filled by the Intent.
    488      */
    489     private boolean fillDigitsIfNecessary(Intent intent) {
    490         // Only fills digits from an intent if it is a new intent.
    491         // Otherwise falls back to the previously used number.
    492         if (!mFirstLaunch && !mStartedFromNewIntent) {
    493             return false;
    494         }
    495 
    496         final String action = intent.getAction();
    497         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
    498             Uri uri = intent.getData();
    499             if (uri != null) {
    500                 if (CallUtil.SCHEME_TEL.equals(uri.getScheme())) {
    501                     // Put the requested number into the input area
    502                     String data = uri.getSchemeSpecificPart();
    503                     // Remember it is filled via Intent.
    504                     mDigitsFilledByIntent = true;
    505                     final String converted = PhoneNumberUtils.convertKeypadLettersToDigits(
    506                             PhoneNumberUtils.replaceUnicodeDigits(data));
    507                     setFormattedDigits(converted, null);
    508                     return true;
    509                 } else {
    510                     String type = intent.getType();
    511                     if (People.CONTENT_ITEM_TYPE.equals(type)
    512                             || Phones.CONTENT_ITEM_TYPE.equals(type)) {
    513                         // Query the phone number
    514                         Cursor c = getActivity().getContentResolver().query(intent.getData(),
    515                                 new String[] {PhonesColumns.NUMBER, PhonesColumns.NUMBER_KEY},
    516                                 null, null, null);
    517                         if (c != null) {
    518                             try {
    519                                 if (c.moveToFirst()) {
    520                                     // Remember it is filled via Intent.
    521                                     mDigitsFilledByIntent = true;
    522                                     // Put the number into the input area
    523                                     setFormattedDigits(c.getString(0), c.getString(1));
    524                                     return true;
    525                                 }
    526                             } finally {
    527                                 c.close();
    528                             }
    529                         }
    530                     }
    531                 }
    532             }
    533         }
    534         return false;
    535     }
    536 
    537     /**
    538      * Determines whether an add call operation is requested.
    539      *
    540      * @param intent The intent.
    541      * @return {@literal true} if add call operation was requested.  {@literal false} otherwise.
    542      */
    543     private static boolean isAddCallMode(Intent intent) {
    544         final String action = intent.getAction();
    545         if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)) {
    546             // see if we are "adding a call" from the InCallScreen; false by default.
    547             return intent.getBooleanExtra(ADD_CALL_MODE_KEY, false);
    548         } else {
    549             return false;
    550         }
    551     }
    552 
    553     /**
    554      * Checks the given Intent and changes dialpad's UI state. For example, if the Intent requires
    555      * the screen to enter "Add Call" mode, this method will show correct UI for the mode.
    556      */
    557     private void configureScreenFromIntent(Activity parent) {
    558         // If we were not invoked with a DIAL intent,
    559         if (!(parent instanceof DialtactsActivity)) {
    560             setStartedFromNewIntent(false);
    561             return;
    562         }
    563         // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
    564         // digits in the dialer field.
    565         Intent intent = parent.getIntent();
    566 
    567         if (!isLayoutReady()) {
    568             // This happens typically when parent's Activity#onNewIntent() is called while
    569             // Fragment#onCreateView() isn't called yet, and thus we cannot configure Views at
    570             // this point. onViewCreate() should call this method after preparing layouts, so
    571             // just ignore this call now.
    572             Log.i(TAG,
    573                     "Screen configuration is requested before onCreateView() is called. Ignored");
    574             return;
    575         }
    576 
    577         boolean needToShowDialpadChooser = false;
    578 
    579         // Be sure *not* to show the dialpad chooser if this is an
    580         // explicit "Add call" action, though.
    581         final boolean isAddCallMode = isAddCallMode(intent);
    582         if (!isAddCallMode) {
    583 
    584             // Don't show the chooser when called via onNewIntent() and phone number is present.
    585             // i.e. User clicks a telephone link from gmail for example.
    586             // In this case, we want to show the dialpad with the phone number.
    587             final boolean digitsFilled = fillDigitsIfNecessary(intent);
    588             if (!(mStartedFromNewIntent && digitsFilled)) {
    589 
    590                 final String action = intent.getAction();
    591                 if (Intent.ACTION_DIAL.equals(action) || Intent.ACTION_VIEW.equals(action)
    592                         || Intent.ACTION_MAIN.equals(action)) {
    593                     // If there's already an active call, bring up an intermediate UI to
    594                     // make the user confirm what they really want to do.
    595                     if (phoneIsInUse()) {
    596                         needToShowDialpadChooser = true;
    597                     }
    598                 }
    599 
    600             }
    601         }
    602         showDialpadChooser(needToShowDialpadChooser);
    603         setStartedFromNewIntent(false);
    604     }
    605 
    606     public void setStartedFromNewIntent(boolean value) {
    607         mStartedFromNewIntent = value;
    608     }
    609 
    610     /**
    611      * Sets formatted digits to digits field.
    612      */
    613     private void setFormattedDigits(String data, String normalizedNumber) {
    614         // strip the non-dialable numbers out of the data string.
    615         String dialString = PhoneNumberUtils.extractNetworkPortion(data);
    616         dialString =
    617                 PhoneNumberUtils.formatNumber(dialString, normalizedNumber, mCurrentCountryIso);
    618         if (!TextUtils.isEmpty(dialString)) {
    619             Editable digits = mDigits.getText();
    620             digits.replace(0, digits.length(), dialString);
    621             // for some reason this isn't getting called in the digits.replace call above..
    622             // but in any case, this will make sure the background drawable looks right
    623             afterTextChanged(digits);
    624         }
    625     }
    626 
    627     private void setupKeypad(View fragmentView) {
    628         final int[] buttonIds = new int[] {R.id.zero, R.id.one, R.id.two, R.id.three, R.id.four,
    629                 R.id.five, R.id.six, R.id.seven, R.id.eight, R.id.nine, R.id.star, R.id.pound};
    630 
    631         final int[] numberIds = new int[] {R.string.dialpad_0_number, R.string.dialpad_1_number,
    632                 R.string.dialpad_2_number, R.string.dialpad_3_number, R.string.dialpad_4_number,
    633                 R.string.dialpad_5_number, R.string.dialpad_6_number, R.string.dialpad_7_number,
    634                 R.string.dialpad_8_number, R.string.dialpad_9_number, R.string.dialpad_star_number,
    635                 R.string.dialpad_pound_number};
    636 
    637         final int[] letterIds = new int[] {R.string.dialpad_0_letters, R.string.dialpad_1_letters,
    638                 R.string.dialpad_2_letters, R.string.dialpad_3_letters, R.string.dialpad_4_letters,
    639                 R.string.dialpad_5_letters, R.string.dialpad_6_letters, R.string.dialpad_7_letters,
    640                 R.string.dialpad_8_letters, R.string.dialpad_9_letters,
    641                 R.string.dialpad_star_letters, R.string.dialpad_pound_letters};
    642 
    643         final Resources resources = getResources();
    644 
    645         DialpadKeyButton dialpadKey;
    646         TextView numberView;
    647         TextView lettersView;
    648 
    649         for (int i = 0; i < buttonIds.length; i++) {
    650             dialpadKey = (DialpadKeyButton) fragmentView.findViewById(buttonIds[i]);
    651             dialpadKey.setLayoutParams(new TableRow.LayoutParams(
    652                     TableRow.LayoutParams.MATCH_PARENT, TableRow.LayoutParams.MATCH_PARENT));
    653             dialpadKey.setOnPressedListener(this);
    654             numberView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_number);
    655             lettersView = (TextView) dialpadKey.findViewById(R.id.dialpad_key_letters);
    656             final String numberString = resources.getString(numberIds[i]);
    657             numberView.setText(numberString);
    658             dialpadKey.setContentDescription(numberString);
    659             if (lettersView != null) {
    660                 lettersView.setText(resources.getString(letterIds[i]));
    661                 if (buttonIds[i] == R.id.zero) {
    662                     lettersView.setTextSize(TypedValue.COMPLEX_UNIT_PX, resources.getDimension(
    663                             R.dimen.dialpad_key_plus_size));
    664                 }
    665             }
    666         }
    667 
    668         // Long-pressing one button will initiate Voicemail.
    669         fragmentView.findViewById(R.id.one).setOnLongClickListener(this);
    670 
    671         // Long-pressing zero button will enter '+' instead.
    672         fragmentView.findViewById(R.id.zero).setOnLongClickListener(this);
    673 
    674     }
    675 
    676     @Override
    677     public void onResume() {
    678         super.onResume();
    679 
    680         final DialtactsActivity activity = (DialtactsActivity) getActivity();
    681         mDialpadQueryListener = activity;
    682 
    683         final StopWatch stopWatch = StopWatch.start("Dialpad.onResume");
    684 
    685         // Query the last dialed number. Do it first because hitting
    686         // the DB is 'slow'. This call is asynchronous.
    687         queryLastOutgoingCall();
    688 
    689         stopWatch.lap("qloc");
    690 
    691         final ContentResolver contentResolver = activity.getContentResolver();
    692 
    693         // retrieve the DTMF tone play back setting.
    694         mDTMFToneEnabled = Settings.System.getInt(contentResolver,
    695                 Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
    696 
    697         stopWatch.lap("dtwd");
    698 
    699         // Retrieve the haptic feedback setting.
    700         mHaptic.checkSystemSetting();
    701 
    702         stopWatch.lap("hptc");
    703 
    704         // if the mToneGenerator creation fails, just continue without it.  It is
    705         // a local audio signal, and is not as important as the dtmf tone itself.
    706         synchronized (mToneGeneratorLock) {
    707             if (mToneGenerator == null) {
    708                 try {
    709                     mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
    710                 } catch (RuntimeException e) {
    711                     Log.w(TAG, "Exception caught while creating local tone generator: " + e);
    712                     mToneGenerator = null;
    713                 }
    714             }
    715         }
    716         stopWatch.lap("tg");
    717 
    718         mPressedDialpadKeys.clear();
    719 
    720         configureScreenFromIntent(getActivity());
    721 
    722         stopWatch.lap("fdin");
    723 
    724         // While we're in the foreground, listen for phone state changes,
    725         // purely so that we can take down the "dialpad chooser" if the
    726         // phone becomes idle while the chooser UI is visible.
    727         getTelephonyManager().listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    728 
    729         stopWatch.lap("tm");
    730 
    731         // Potentially show hint text in the mDigits field when the user
    732         // hasn't typed any digits yet.  (If there's already an active call,
    733         // this hint text will remind the user that he's about to add a new
    734         // call.)
    735         //
    736         // TODO: consider adding better UI for the case where *both* lines
    737         // are currently in use.  (Right now we let the user try to add
    738         // another call, but that call is guaranteed to fail.  Perhaps the
    739         // entire dialer UI should be disabled instead.)
    740         if (phoneIsInUse()) {
    741             final SpannableString hint = new SpannableString(
    742                     getActivity().getString(R.string.dialerDialpadHintText));
    743             hint.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), 0);
    744             mDigits.setHint(hint);
    745         } else {
    746             // Common case; no hint necessary.
    747             mDigits.setHint(null);
    748 
    749             // Also, a sanity-check: the "dialpad chooser" UI should NEVER
    750             // be visible if the phone is idle!
    751             showDialpadChooser(false);
    752         }
    753 
    754         mFirstLaunch = false;
    755 
    756         stopWatch.lap("hnt");
    757 
    758         updateDialAndDeleteButtonEnabledState();
    759 
    760         stopWatch.lap("bes");
    761 
    762         stopWatch.stopAndLog(TAG, 50);
    763     }
    764 
    765     @Override
    766     public void onPause() {
    767         super.onPause();
    768 
    769         // Stop listening for phone state changes.
    770         getTelephonyManager().listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
    771 
    772         // Make sure we don't leave this activity with a tone still playing.
    773         stopTone();
    774         mPressedDialpadKeys.clear();
    775 
    776         synchronized (mToneGeneratorLock) {
    777             if (mToneGenerator != null) {
    778                 mToneGenerator.release();
    779                 mToneGenerator = null;
    780             }
    781         }
    782         // TODO: I wonder if we should not check if the AsyncTask that
    783         // lookup the last dialed number has completed.
    784         mLastNumberDialed = EMPTY_NUMBER;  // Since we are going to query again, free stale number.
    785 
    786         SpecialCharSequenceMgr.cleanup();
    787     }
    788 
    789     @Override
    790     public void onStop() {
    791         super.onStop();
    792 
    793         if (mClearDigitsOnStop) {
    794             mClearDigitsOnStop = false;
    795             clearDialpad();
    796         }
    797     }
    798 
    799     @Override
    800     public void onSaveInstanceState(Bundle outState) {
    801         super.onSaveInstanceState(outState);
    802         outState.putBoolean(PREF_DIGITS_FILLED_BY_INTENT, mDigitsFilledByIntent);
    803     }
    804 
    805     private void setupMenuItems(Menu menu) {
    806         final MenuItem addToContactMenuItem = menu.findItem(R.id.menu_add_contacts);
    807 
    808         // We show "add to contacts" menu only when the user is
    809         // seeing usual dialpad and has typed at least one digit.
    810         // We never show a menu if the "choose dialpad" UI is up.
    811         if (dialpadChooserVisible() || isDigitsEmpty()) {
    812             addToContactMenuItem.setVisible(false);
    813         } else {
    814             final CharSequence digits = mDigits.getText();
    815             // Put the current digits string into an intent
    816             addToContactMenuItem.setIntent(DialtactsActivity.getAddNumberToContactIntent(digits));
    817             addToContactMenuItem.setVisible(true);
    818         }
    819     }
    820 
    821     private void keyPressed(int keyCode) {
    822         if (getView().getTranslationY() != 0) {
    823             return;
    824         }
    825         switch (keyCode) {
    826             case KeyEvent.KEYCODE_1:
    827                 playTone(ToneGenerator.TONE_DTMF_1, TONE_LENGTH_INFINITE);
    828                 break;
    829             case KeyEvent.KEYCODE_2:
    830                 playTone(ToneGenerator.TONE_DTMF_2, TONE_LENGTH_INFINITE);
    831                 break;
    832             case KeyEvent.KEYCODE_3:
    833                 playTone(ToneGenerator.TONE_DTMF_3, TONE_LENGTH_INFINITE);
    834                 break;
    835             case KeyEvent.KEYCODE_4:
    836                 playTone(ToneGenerator.TONE_DTMF_4, TONE_LENGTH_INFINITE);
    837                 break;
    838             case KeyEvent.KEYCODE_5:
    839                 playTone(ToneGenerator.TONE_DTMF_5, TONE_LENGTH_INFINITE);
    840                 break;
    841             case KeyEvent.KEYCODE_6:
    842                 playTone(ToneGenerator.TONE_DTMF_6, TONE_LENGTH_INFINITE);
    843                 break;
    844             case KeyEvent.KEYCODE_7:
    845                 playTone(ToneGenerator.TONE_DTMF_7, TONE_LENGTH_INFINITE);
    846                 break;
    847             case KeyEvent.KEYCODE_8:
    848                 playTone(ToneGenerator.TONE_DTMF_8, TONE_LENGTH_INFINITE);
    849                 break;
    850             case KeyEvent.KEYCODE_9:
    851                 playTone(ToneGenerator.TONE_DTMF_9, TONE_LENGTH_INFINITE);
    852                 break;
    853             case KeyEvent.KEYCODE_0:
    854                 playTone(ToneGenerator.TONE_DTMF_0, TONE_LENGTH_INFINITE);
    855                 break;
    856             case KeyEvent.KEYCODE_POUND:
    857                 playTone(ToneGenerator.TONE_DTMF_P, TONE_LENGTH_INFINITE);
    858                 break;
    859             case KeyEvent.KEYCODE_STAR:
    860                 playTone(ToneGenerator.TONE_DTMF_S, TONE_LENGTH_INFINITE);
    861                 break;
    862             default:
    863                 break;
    864         }
    865 
    866         mHaptic.vibrate();
    867         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
    868         mDigits.onKeyDown(keyCode, event);
    869 
    870         // If the cursor is at the end of the text we hide it.
    871         final int length = mDigits.length();
    872         if (length == mDigits.getSelectionStart() && length == mDigits.getSelectionEnd()) {
    873             mDigits.setCursorVisible(false);
    874         }
    875     }
    876 
    877     @Override
    878     public boolean onKey(View view, int keyCode, KeyEvent event) {
    879         switch (view.getId()) {
    880             case R.id.digits:
    881                 if (keyCode == KeyEvent.KEYCODE_ENTER) {
    882                     dialButtonPressed();
    883                     return true;
    884                 }
    885                 break;
    886         }
    887         return false;
    888     }
    889 
    890     /**
    891      * When a key is pressed, we start playing DTMF tone, do vibration, and enter the digit
    892      * immediately. When a key is released, we stop the tone. Note that the "key press" event will
    893      * be delivered by the system with certain amount of delay, it won't be synced with user's
    894      * actual "touch-down" behavior.
    895      */
    896     @Override
    897     public void onPressed(View view, boolean pressed) {
    898         if (DEBUG) Log.d(TAG, "onPressed(). view: " + view + ", pressed: " + pressed);
    899         if (pressed) {
    900             switch (view.getId()) {
    901                 case R.id.one: {
    902                     keyPressed(KeyEvent.KEYCODE_1);
    903                     break;
    904                 }
    905                 case R.id.two: {
    906                     keyPressed(KeyEvent.KEYCODE_2);
    907                     break;
    908                 }
    909                 case R.id.three: {
    910                     keyPressed(KeyEvent.KEYCODE_3);
    911                     break;
    912                 }
    913                 case R.id.four: {
    914                     keyPressed(KeyEvent.KEYCODE_4);
    915                     break;
    916                 }
    917                 case R.id.five: {
    918                     keyPressed(KeyEvent.KEYCODE_5);
    919                     break;
    920                 }
    921                 case R.id.six: {
    922                     keyPressed(KeyEvent.KEYCODE_6);
    923                     break;
    924                 }
    925                 case R.id.seven: {
    926                     keyPressed(KeyEvent.KEYCODE_7);
    927                     break;
    928                 }
    929                 case R.id.eight: {
    930                     keyPressed(KeyEvent.KEYCODE_8);
    931                     break;
    932                 }
    933                 case R.id.nine: {
    934                     keyPressed(KeyEvent.KEYCODE_9);
    935                     break;
    936                 }
    937                 case R.id.zero: {
    938                     keyPressed(KeyEvent.KEYCODE_0);
    939                     break;
    940                 }
    941                 case R.id.pound: {
    942                     keyPressed(KeyEvent.KEYCODE_POUND);
    943                     break;
    944                 }
    945                 case R.id.star: {
    946                     keyPressed(KeyEvent.KEYCODE_STAR);
    947                     break;
    948                 }
    949                 default: {
    950                     Log.wtf(TAG, "Unexpected onTouch(ACTION_DOWN) event from: " + view);
    951                     break;
    952                 }
    953             }
    954             mPressedDialpadKeys.add(view);
    955         } else {
    956             view.jumpDrawablesToCurrentState();
    957             mPressedDialpadKeys.remove(view);
    958             if (mPressedDialpadKeys.isEmpty()) {
    959                 stopTone();
    960             }
    961         }
    962     }
    963 
    964     @Override
    965     public void onClick(View view) {
    966         switch (view.getId()) {
    967             case R.id.overflow_menu_on_dialpad: {
    968                 final PopupMenu popupMenu = new PopupMenu(getActivity(), view);
    969                 final Menu menu = popupMenu.getMenu();
    970                 popupMenu.inflate(R.menu.dialpad_options);
    971                 popupMenu.setOnMenuItemClickListener(this);
    972                 setupMenuItems(menu);
    973                 popupMenu.show();
    974                 break;
    975             }
    976             case R.id.deleteButton: {
    977                 keyPressed(KeyEvent.KEYCODE_DEL);
    978                 return;
    979             }
    980             case R.id.dialButton: {
    981                 mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
    982                 dialButtonPressed();
    983                 return;
    984             }
    985             case R.id.digits: {
    986                 if (!isDigitsEmpty()) {
    987                     mDigits.setCursorVisible(true);
    988                 }
    989                 return;
    990             }
    991             default: {
    992                 Log.wtf(TAG, "Unexpected onClick() event from: " + view);
    993                 return;
    994             }
    995         }
    996     }
    997 
    998     @Override
    999     public boolean onLongClick(View view) {
   1000         final Editable digits = mDigits.getText();
   1001         final int id = view.getId();
   1002         switch (id) {
   1003             case R.id.deleteButton: {
   1004                 digits.clear();
   1005                 // TODO: The framework forgets to clear the pressed
   1006                 // status of disabled button. Until this is fixed,
   1007                 // clear manually the pressed status. b/2133127
   1008                 mDelete.setPressed(false);
   1009                 return true;
   1010             }
   1011             case R.id.one: {
   1012                 // '1' may be already entered since we rely on onTouch() event for numeric buttons.
   1013                 // Just for safety we also check if the digits field is empty or not.
   1014                 if (isDigitsEmpty() || TextUtils.equals(mDigits.getText(), "1")) {
   1015                     // We'll try to initiate voicemail and thus we want to remove irrelevant string.
   1016                     removePreviousDigitIfPossible();
   1017 
   1018                     if (isVoicemailAvailable()) {
   1019                         callVoicemail();
   1020                     } else if (getActivity() != null) {
   1021                         // Voicemail is unavailable maybe because Airplane mode is turned on.
   1022                         // Check the current status and show the most appropriate error message.
   1023                         final boolean isAirplaneModeOn =
   1024                                 Settings.System.getInt(getActivity().getContentResolver(),
   1025                                 Settings.System.AIRPLANE_MODE_ON, 0) != 0;
   1026                         if (isAirplaneModeOn) {
   1027                             DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
   1028                                     R.string.dialog_voicemail_airplane_mode_message);
   1029                             dialogFragment.show(getFragmentManager(),
   1030                                     "voicemail_request_during_airplane_mode");
   1031                         } else {
   1032                             DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
   1033                                     R.string.dialog_voicemail_not_ready_message);
   1034                             dialogFragment.show(getFragmentManager(), "voicemail_not_ready");
   1035                         }
   1036                     }
   1037                     return true;
   1038                 }
   1039                 return false;
   1040             }
   1041             case R.id.zero: {
   1042                 // Remove tentative input ('0') done by onTouch().
   1043                 removePreviousDigitIfPossible();
   1044                 keyPressed(KeyEvent.KEYCODE_PLUS);
   1045 
   1046                 // Stop tone immediately
   1047                 stopTone();
   1048                 mPressedDialpadKeys.remove(view);
   1049 
   1050                 return true;
   1051             }
   1052             case R.id.digits: {
   1053                 // Right now EditText does not show the "paste" option when cursor is not visible.
   1054                 // To show that, make the cursor visible, and return false, letting the EditText
   1055                 // show the option by itself.
   1056                 mDigits.setCursorVisible(true);
   1057                 return false;
   1058             }
   1059             case R.id.dialButton: {
   1060                 if (isDigitsEmpty()) {
   1061                     handleDialButtonClickWithEmptyDigits();
   1062                     // This event should be consumed so that onClick() won't do the exactly same
   1063                     // thing.
   1064                     return true;
   1065                 } else {
   1066                     return false;
   1067                 }
   1068             }
   1069         }
   1070         return false;
   1071     }
   1072 
   1073     /**
   1074      * Remove the digit just before the current position. This can be used if we want to replace
   1075      * the previous digit or cancel previously entered character.
   1076      */
   1077     private void removePreviousDigitIfPossible() {
   1078         final Editable editable = mDigits.getText();
   1079         final int currentPosition = mDigits.getSelectionStart();
   1080         if (currentPosition > 0) {
   1081             mDigits.setSelection(currentPosition);
   1082             mDigits.getText().delete(currentPosition - 1, currentPosition);
   1083         }
   1084     }
   1085 
   1086     public void callVoicemail() {
   1087         startActivity(getVoicemailIntent());
   1088         hideAndClearDialpad();
   1089     }
   1090 
   1091     private void hideAndClearDialpad() {
   1092         ((DialtactsActivity) getActivity()).hideDialpadFragment(false, true);
   1093     }
   1094 
   1095     public static class ErrorDialogFragment extends DialogFragment {
   1096         private int mTitleResId;
   1097         private int mMessageResId;
   1098 
   1099         private static final String ARG_TITLE_RES_ID = "argTitleResId";
   1100         private static final String ARG_MESSAGE_RES_ID = "argMessageResId";
   1101 
   1102         public static ErrorDialogFragment newInstance(int messageResId) {
   1103             return newInstance(0, messageResId);
   1104         }
   1105 
   1106         public static ErrorDialogFragment newInstance(int titleResId, int messageResId) {
   1107             final ErrorDialogFragment fragment = new ErrorDialogFragment();
   1108             final Bundle args = new Bundle();
   1109             args.putInt(ARG_TITLE_RES_ID, titleResId);
   1110             args.putInt(ARG_MESSAGE_RES_ID, messageResId);
   1111             fragment.setArguments(args);
   1112             return fragment;
   1113         }
   1114 
   1115         @Override
   1116         public void onCreate(Bundle savedInstanceState) {
   1117             super.onCreate(savedInstanceState);
   1118             mTitleResId = getArguments().getInt(ARG_TITLE_RES_ID);
   1119             mMessageResId = getArguments().getInt(ARG_MESSAGE_RES_ID);
   1120         }
   1121 
   1122         @Override
   1123         public Dialog onCreateDialog(Bundle savedInstanceState) {
   1124             AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
   1125             if (mTitleResId != 0) {
   1126                 builder.setTitle(mTitleResId);
   1127             }
   1128             if (mMessageResId != 0) {
   1129                 builder.setMessage(mMessageResId);
   1130             }
   1131             builder.setPositiveButton(android.R.string.ok,
   1132                     new DialogInterface.OnClickListener() {
   1133                             @Override
   1134                             public void onClick(DialogInterface dialog, int which) {
   1135                                 dismiss();
   1136                             }
   1137                     });
   1138             return builder.create();
   1139         }
   1140     }
   1141 
   1142     /**
   1143      * In most cases, when the dial button is pressed, there is a
   1144      * number in digits area. Pack it in the intent, start the
   1145      * outgoing call broadcast as a separate task and finish this
   1146      * activity.
   1147      *
   1148      * When there is no digit and the phone is CDMA and off hook,
   1149      * we're sending a blank flash for CDMA. CDMA networks use Flash
   1150      * messages when special processing needs to be done, mainly for
   1151      * 3-way or call waiting scenarios. Presumably, here we're in a
   1152      * special 3-way scenario where the network needs a blank flash
   1153      * before being able to add the new participant.  (This is not the
   1154      * case with all 3-way calls, just certain CDMA infrastructures.)
   1155      *
   1156      * Otherwise, there is no digit, display the last dialed
   1157      * number. Don't finish since the user may want to edit it. The
   1158      * user needs to press the dial button again, to dial it (general
   1159      * case described above).
   1160      */
   1161     public void dialButtonPressed() {
   1162         if (isDigitsEmpty()) { // No number entered.
   1163             handleDialButtonClickWithEmptyDigits();
   1164         } else {
   1165             final String number = mDigits.getText().toString();
   1166 
   1167             // "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
   1168             // test equipment.
   1169             // TODO: clean it up.
   1170             if (number != null
   1171                     && !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
   1172                     && number.matches(mProhibitedPhoneNumberRegexp)
   1173                     && (SystemProperties.getInt("persist.radio.otaspdial", 0) != 1)) {
   1174                 Log.i(TAG, "The phone number is prohibited explicitly by a rule.");
   1175                 if (getActivity() != null) {
   1176                     DialogFragment dialogFragment = ErrorDialogFragment.newInstance(
   1177                             R.string.dialog_phone_call_prohibited_message);
   1178                     dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
   1179                 }
   1180 
   1181                 // Clear the digits just in case.
   1182                 mDigits.getText().clear();
   1183             } else {
   1184                 final Intent intent = CallUtil.getCallIntent(number,
   1185                         (getActivity() instanceof DialtactsActivity ?
   1186                                 ((DialtactsActivity) getActivity()).getCallOrigin() : null));
   1187                 startActivity(intent);
   1188                 hideAndClearDialpad();
   1189             }
   1190         }
   1191     }
   1192 
   1193     public void clearDialpad() {
   1194         mDigits.getText().clear();
   1195     }
   1196 
   1197     private String getCallOrigin() {
   1198         return (getActivity() instanceof DialtactsActivity) ?
   1199                 ((DialtactsActivity) getActivity()).getCallOrigin() : null;
   1200     }
   1201 
   1202     private void handleDialButtonClickWithEmptyDigits() {
   1203         if (phoneIsCdma() && phoneIsOffhook()) {
   1204             // This is really CDMA specific. On GSM is it possible
   1205             // to be off hook and wanted to add a 3rd party using
   1206             // the redial feature.
   1207             startActivity(newFlashIntent());
   1208         } else {
   1209             if (!TextUtils.isEmpty(mLastNumberDialed)) {
   1210                 // Recall the last number dialed.
   1211                 mDigits.setText(mLastNumberDialed);
   1212 
   1213                 // ...and move the cursor to the end of the digits string,
   1214                 // so you'll be able to delete digits using the Delete
   1215                 // button (just as if you had typed the number manually.)
   1216                 //
   1217                 // Note we use mDigits.getText().length() here, not
   1218                 // mLastNumberDialed.length(), since the EditText widget now
   1219                 // contains a *formatted* version of mLastNumberDialed (due to
   1220                 // mTextWatcher) and its length may have changed.
   1221                 mDigits.setSelection(mDigits.getText().length());
   1222             } else {
   1223                 // There's no "last number dialed" or the
   1224                 // background query is still running. There's
   1225                 // nothing useful for the Dial button to do in
   1226                 // this case.  Note: with a soft dial button, this
   1227                 // can never happens since the dial button is
   1228                 // disabled under these conditons.
   1229                 playTone(ToneGenerator.TONE_PROP_NACK);
   1230             }
   1231         }
   1232     }
   1233 
   1234     /**
   1235      * Plays the specified tone for TONE_LENGTH_MS milliseconds.
   1236      */
   1237     private void playTone(int tone) {
   1238         playTone(tone, TONE_LENGTH_MS);
   1239     }
   1240 
   1241     /**
   1242      * Play the specified tone for the specified milliseconds
   1243      *
   1244      * The tone is played locally, using the audio stream for phone calls.
   1245      * Tones are played only if the "Audible touch tones" user preference
   1246      * is checked, and are NOT played if the device is in silent mode.
   1247      *
   1248      * The tone length can be -1, meaning "keep playing the tone." If the caller does so, it should
   1249      * call stopTone() afterward.
   1250      *
   1251      * @param tone a tone code from {@link ToneGenerator}
   1252      * @param durationMs tone length.
   1253      */
   1254     private void playTone(int tone, int durationMs) {
   1255         // if local tone playback is disabled, just return.
   1256         if (!mDTMFToneEnabled) {
   1257             return;
   1258         }
   1259 
   1260         // Also do nothing if the phone is in silent mode.
   1261         // We need to re-check the ringer mode for *every* playTone()
   1262         // call, rather than keeping a local flag that's updated in
   1263         // onResume(), since it's possible to toggle silent mode without
   1264         // leaving the current activity (via the ENDCALL-longpress menu.)
   1265         AudioManager audioManager =
   1266                 (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE);
   1267         int ringerMode = audioManager.getRingerMode();
   1268         if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
   1269             || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
   1270             return;
   1271         }
   1272 
   1273         synchronized (mToneGeneratorLock) {
   1274             if (mToneGenerator == null) {
   1275                 Log.w(TAG, "playTone: mToneGenerator == null, tone: " + tone);
   1276                 return;
   1277             }
   1278 
   1279             // Start the new tone (will stop any playing tone)
   1280             mToneGenerator.startTone(tone, durationMs);
   1281         }
   1282     }
   1283 
   1284     /**
   1285      * Stop the tone if it is played.
   1286      */
   1287     private void stopTone() {
   1288         // if local tone playback is disabled, just return.
   1289         if (!mDTMFToneEnabled) {
   1290             return;
   1291         }
   1292         synchronized (mToneGeneratorLock) {
   1293             if (mToneGenerator == null) {
   1294                 Log.w(TAG, "stopTone: mToneGenerator == null");
   1295                 return;
   1296             }
   1297             mToneGenerator.stopTone();
   1298         }
   1299     }
   1300 
   1301     /**
   1302      * Brings up the "dialpad chooser" UI in place of the usual Dialer
   1303      * elements (the textfield/button and the dialpad underneath).
   1304      *
   1305      * We show this UI if the user brings up the Dialer while a call is
   1306      * already in progress, since there's a good chance we got here
   1307      * accidentally (and the user really wanted the in-call dialpad instead).
   1308      * So in this situation we display an intermediate UI that lets the user
   1309      * explicitly choose between the in-call dialpad ("Use touch tone
   1310      * keypad") and the regular Dialer ("Add call").  (Or, the option "Return
   1311      * to call in progress" just goes back to the in-call UI with no dialpad
   1312      * at all.)
   1313      *
   1314      * @param enabled If true, show the "dialpad chooser" instead
   1315      *                of the regular Dialer UI
   1316      */
   1317     private void showDialpadChooser(boolean enabled) {
   1318         // Check if onCreateView() is already called by checking one of View objects.
   1319         if (!isLayoutReady()) {
   1320             return;
   1321         }
   1322 
   1323         if (enabled) {
   1324             // Log.i(TAG, "Showing dialpad chooser!");
   1325             if (mDigitsContainer != null) {
   1326                 mDigitsContainer.setVisibility(View.GONE);
   1327             } else {
   1328                 // mDigits is not enclosed by the container. Make the digits field itself gone.
   1329                 mDigits.setVisibility(View.GONE);
   1330             }
   1331             if (mDialpad != null) mDialpad.setVisibility(View.GONE);
   1332             if (mDialButtonContainer != null) mDialButtonContainer.setVisibility(View.GONE);
   1333 
   1334             mDialpadChooser.setVisibility(View.VISIBLE);
   1335 
   1336             // Instantiate the DialpadChooserAdapter and hook it up to the
   1337             // ListView.  We do this only once.
   1338             if (mDialpadChooserAdapter == null) {
   1339                 mDialpadChooserAdapter = new DialpadChooserAdapter(getActivity());
   1340             }
   1341             mDialpadChooser.setAdapter(mDialpadChooserAdapter);
   1342         } else {
   1343             // Log.i(TAG, "Displaying normal Dialer UI.");
   1344             if (mDigitsContainer != null) {
   1345                 mDigitsContainer.setVisibility(View.VISIBLE);
   1346             } else {
   1347                 mDigits.setVisibility(View.VISIBLE);
   1348             }
   1349             if (mDialpad != null) mDialpad.setVisibility(View.VISIBLE);
   1350             if (mDialButtonContainer != null) mDialButtonContainer.setVisibility(View.VISIBLE);
   1351             mDialpadChooser.setVisibility(View.GONE);
   1352         }
   1353     }
   1354 
   1355     /**
   1356      * @return true if we're currently showing the "dialpad chooser" UI.
   1357      */
   1358     private boolean dialpadChooserVisible() {
   1359         return mDialpadChooser.getVisibility() == View.VISIBLE;
   1360     }
   1361 
   1362     /**
   1363      * Simple list adapter, binding to an icon + text label
   1364      * for each item in the "dialpad chooser" list.
   1365      */
   1366     private static class DialpadChooserAdapter extends BaseAdapter {
   1367         private LayoutInflater mInflater;
   1368 
   1369         // Simple struct for a single "choice" item.
   1370         static class ChoiceItem {
   1371             String text;
   1372             Bitmap icon;
   1373             int id;
   1374 
   1375             public ChoiceItem(String s, Bitmap b, int i) {
   1376                 text = s;
   1377                 icon = b;
   1378                 id = i;
   1379             }
   1380         }
   1381 
   1382         // IDs for the possible "choices":
   1383         static final int DIALPAD_CHOICE_USE_DTMF_DIALPAD = 101;
   1384         static final int DIALPAD_CHOICE_RETURN_TO_CALL = 102;
   1385         static final int DIALPAD_CHOICE_ADD_NEW_CALL = 103;
   1386 
   1387         private static final int NUM_ITEMS = 3;
   1388         private ChoiceItem mChoiceItems[] = new ChoiceItem[NUM_ITEMS];
   1389 
   1390         public DialpadChooserAdapter(Context context) {
   1391             // Cache the LayoutInflate to avoid asking for a new one each time.
   1392             mInflater = LayoutInflater.from(context);
   1393 
   1394             // Initialize the possible choices.
   1395             // TODO: could this be specified entirely in XML?
   1396 
   1397             // - "Use touch tone keypad"
   1398             mChoiceItems[0] = new ChoiceItem(
   1399                     context.getString(R.string.dialer_useDtmfDialpad),
   1400                     BitmapFactory.decodeResource(context.getResources(),
   1401                                                  R.drawable.ic_dialer_fork_tt_keypad),
   1402                     DIALPAD_CHOICE_USE_DTMF_DIALPAD);
   1403 
   1404             // - "Return to call in progress"
   1405             mChoiceItems[1] = new ChoiceItem(
   1406                     context.getString(R.string.dialer_returnToInCallScreen),
   1407                     BitmapFactory.decodeResource(context.getResources(),
   1408                                                  R.drawable.ic_dialer_fork_current_call),
   1409                     DIALPAD_CHOICE_RETURN_TO_CALL);
   1410 
   1411             // - "Add call"
   1412             mChoiceItems[2] = new ChoiceItem(
   1413                     context.getString(R.string.dialer_addAnotherCall),
   1414                     BitmapFactory.decodeResource(context.getResources(),
   1415                                                  R.drawable.ic_dialer_fork_add_call),
   1416                     DIALPAD_CHOICE_ADD_NEW_CALL);
   1417         }
   1418 
   1419         @Override
   1420         public int getCount() {
   1421             return NUM_ITEMS;
   1422         }
   1423 
   1424         /**
   1425          * Return the ChoiceItem for a given position.
   1426          */
   1427         @Override
   1428         public Object getItem(int position) {
   1429             return mChoiceItems[position];
   1430         }
   1431 
   1432         /**
   1433          * Return a unique ID for each possible choice.
   1434          */
   1435         @Override
   1436         public long getItemId(int position) {
   1437             return position;
   1438         }
   1439 
   1440         /**
   1441          * Make a view for each row.
   1442          */
   1443         @Override
   1444         public View getView(int position, View convertView, ViewGroup parent) {
   1445             // When convertView is non-null, we can reuse it (there's no need
   1446             // to reinflate it.)
   1447             if (convertView == null) {
   1448                 convertView = mInflater.inflate(R.layout.dialpad_chooser_list_item, null);
   1449             }
   1450 
   1451             TextView text = (TextView) convertView.findViewById(R.id.text);
   1452             text.setText(mChoiceItems[position].text);
   1453 
   1454             ImageView icon = (ImageView) convertView.findViewById(R.id.icon);
   1455             icon.setImageBitmap(mChoiceItems[position].icon);
   1456 
   1457             return convertView;
   1458         }
   1459     }
   1460 
   1461     /**
   1462      * Handle clicks from the dialpad chooser.
   1463      */
   1464     @Override
   1465     public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
   1466         DialpadChooserAdapter.ChoiceItem item =
   1467                 (DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
   1468         int itemId = item.id;
   1469         switch (itemId) {
   1470             case DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD:
   1471                 // Log.i(TAG, "DIALPAD_CHOICE_USE_DTMF_DIALPAD");
   1472                 // Fire off an intent to go back to the in-call UI
   1473                 // with the dialpad visible.
   1474                 returnToInCallScreen(true);
   1475                 break;
   1476 
   1477             case DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL:
   1478                 // Log.i(TAG, "DIALPAD_CHOICE_RETURN_TO_CALL");
   1479                 // Fire off an intent to go back to the in-call UI
   1480                 // (with the dialpad hidden).
   1481                 returnToInCallScreen(false);
   1482                 break;
   1483 
   1484             case DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL:
   1485                 // Log.i(TAG, "DIALPAD_CHOICE_ADD_NEW_CALL");
   1486                 // Ok, guess the user really did want to be here (in the
   1487                 // regular Dialer) after all.  Bring back the normal Dialer UI.
   1488                 showDialpadChooser(false);
   1489                 break;
   1490 
   1491             default:
   1492                 Log.w(TAG, "onItemClick: unexpected itemId: " + itemId);
   1493                 break;
   1494         }
   1495     }
   1496 
   1497     /**
   1498      * Returns to the in-call UI (where there's presumably a call in
   1499      * progress) in response to the user selecting "use touch tone keypad"
   1500      * or "return to call" from the dialpad chooser.
   1501      */
   1502     private void returnToInCallScreen(boolean showDialpad) {
   1503         try {
   1504             ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.checkService("phone"));
   1505             if (phone != null) phone.showCallScreenWithDialpad(showDialpad);
   1506         } catch (RemoteException e) {
   1507             Log.w(TAG, "phone.showCallScreenWithDialpad() failed", e);
   1508         }
   1509 
   1510         // Finally, finish() ourselves so that we don't stay on the
   1511         // activity stack.
   1512         // Note that we do this whether or not the showCallScreenWithDialpad()
   1513         // call above had any effect or not!  (That call is a no-op if the
   1514         // phone is idle, which can happen if the current call ends while
   1515         // the dialpad chooser is up.  In this case we can't show the
   1516         // InCallScreen, and there's no point staying here in the Dialer,
   1517         // so we just take the user back where he came from...)
   1518         getActivity().finish();
   1519     }
   1520 
   1521     /**
   1522      * @return true if the phone is "in use", meaning that at least one line
   1523      *              is active (ie. off hook or ringing or dialing).
   1524      */
   1525     public boolean phoneIsInUse() {
   1526         return getTelephonyManager().getCallState() != TelephonyManager.CALL_STATE_IDLE;
   1527     }
   1528 
   1529     /**
   1530      * @return true if the phone is a CDMA phone type
   1531      */
   1532     private boolean phoneIsCdma() {
   1533         return getTelephonyManager().getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA;
   1534     }
   1535 
   1536     /**
   1537      * @return true if the phone state is OFFHOOK
   1538      */
   1539     private boolean phoneIsOffhook() {
   1540         return getTelephonyManager().getCallState() == TelephonyManager.CALL_STATE_OFFHOOK;
   1541     }
   1542 
   1543     @Override
   1544     public boolean onMenuItemClick(MenuItem item) {
   1545         // R.id.menu_add_contacts already has an add to contact intent populated by setupMenuItems
   1546         switch (item.getItemId()) {
   1547             case R.id.menu_2s_pause:
   1548                 updateDialString(PAUSE);
   1549                 return true;
   1550             case R.id.menu_add_wait:
   1551                 updateDialString(WAIT);
   1552                 return true;
   1553             default:
   1554                 return false;
   1555         }
   1556     }
   1557 
   1558     /**
   1559      * Updates the dial string (mDigits) after inserting a Pause character (,)
   1560      * or Wait character (;).
   1561      */
   1562     private void updateDialString(char newDigit) {
   1563         if(newDigit != WAIT && newDigit != PAUSE) {
   1564             Log.wtf(TAG, "Not expected for anything other than PAUSE & WAIT");
   1565             return;
   1566         }
   1567 
   1568         int selectionStart;
   1569         int selectionEnd;
   1570 
   1571         // SpannableStringBuilder editable_text = new SpannableStringBuilder(mDigits.getText());
   1572         int anchor = mDigits.getSelectionStart();
   1573         int point = mDigits.getSelectionEnd();
   1574 
   1575         selectionStart = Math.min(anchor, point);
   1576         selectionEnd = Math.max(anchor, point);
   1577 
   1578         if (selectionStart == -1) {
   1579             selectionStart = selectionEnd = mDigits.length();
   1580         }
   1581 
   1582         Editable digits = mDigits.getText();
   1583 
   1584         if (canAddDigit(digits, selectionStart, selectionEnd, newDigit)) {
   1585             digits.replace(selectionStart, selectionEnd, Character.toString(newDigit));
   1586 
   1587             if (selectionStart != selectionEnd) {
   1588               // Unselect: back to a regular cursor, just pass the character inserted.
   1589               mDigits.setSelection(selectionStart + 1);
   1590             }
   1591         }
   1592     }
   1593 
   1594     /**
   1595      * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
   1596      */
   1597     private void updateDialAndDeleteButtonEnabledState() {
   1598         final boolean digitsNotEmpty = !isDigitsEmpty();
   1599 
   1600         if (mDialButton != null) {
   1601             // On CDMA phones, if we're already on a call, we *always*
   1602             // enable the Dial button (since you can press it without
   1603             // entering any digits to send an empty flash.)
   1604             if (phoneIsCdma() && phoneIsOffhook()) {
   1605                 mDialButton.setEnabled(true);
   1606             } else {
   1607                 // Common case: GSM, or CDMA but not on a call.
   1608                 // Enable the Dial button if some digits have
   1609                 // been entered, or if there is a last dialed number
   1610                 // that could be redialed.
   1611                 mDialButton.setEnabled(digitsNotEmpty ||
   1612                         !TextUtils.isEmpty(mLastNumberDialed));
   1613             }
   1614         }
   1615         mDelete.setEnabled(digitsNotEmpty);
   1616     }
   1617 
   1618     /**
   1619      * Check if voicemail is enabled/accessible.
   1620      *
   1621      * @return true if voicemail is enabled and accessibly. Note that this can be false
   1622      * "temporarily" after the app boot.
   1623      * @see TelephonyManager#getVoiceMailNumber()
   1624      */
   1625     private boolean isVoicemailAvailable() {
   1626         try {
   1627             return getTelephonyManager().getVoiceMailNumber() != null;
   1628         } catch (SecurityException se) {
   1629             // Possibly no READ_PHONE_STATE privilege.
   1630             Log.w(TAG, "SecurityException is thrown. Maybe privilege isn't sufficient.");
   1631         }
   1632         return false;
   1633     }
   1634 
   1635     /**
   1636      * Returns true of the newDigit parameter can be added at the current selection
   1637      * point, otherwise returns false.
   1638      * Only prevents input of WAIT and PAUSE digits at an unsupported position.
   1639      * Fails early if start == -1 or start is larger than end.
   1640      */
   1641     @VisibleForTesting
   1642     /* package */ static boolean canAddDigit(CharSequence digits, int start, int end,
   1643                                              char newDigit) {
   1644         if(newDigit != WAIT && newDigit != PAUSE) {
   1645             Log.wtf(TAG, "Should not be called for anything other than PAUSE & WAIT");
   1646             return false;
   1647         }
   1648 
   1649         // False if no selection, or selection is reversed (end < start)
   1650         if (start == -1 || end < start) {
   1651             return false;
   1652         }
   1653 
   1654         // unsupported selection-out-of-bounds state
   1655         if (start > digits.length() || end > digits.length()) return false;
   1656 
   1657         // Special digit cannot be the first digit
   1658         if (start == 0) return false;
   1659 
   1660         if (newDigit == WAIT) {
   1661             // preceding char is ';' (WAIT)
   1662             if (digits.charAt(start - 1) == WAIT) return false;
   1663 
   1664             // next char is ';' (WAIT)
   1665             if ((digits.length() > end) && (digits.charAt(end) == WAIT)) return false;
   1666         }
   1667 
   1668         return true;
   1669     }
   1670 
   1671     /**
   1672      * @return true if the widget with the phone number digits is empty.
   1673      */
   1674     private boolean isDigitsEmpty() {
   1675         return mDigits.length() == 0;
   1676     }
   1677 
   1678     /**
   1679      * Starts the asyn query to get the last dialed/outgoing
   1680      * number. When the background query finishes, mLastNumberDialed
   1681      * is set to the last dialed number or an empty string if none
   1682      * exists yet.
   1683      */
   1684     private void queryLastOutgoingCall() {
   1685         mLastNumberDialed = EMPTY_NUMBER;
   1686         CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
   1687                 new CallLogAsync.GetLastOutgoingCallArgs(
   1688                     getActivity(),
   1689                     new CallLogAsync.OnLastOutgoingCallComplete() {
   1690                         @Override
   1691                         public void lastOutgoingCall(String number) {
   1692                             // TODO: Filter out emergency numbers if
   1693                             // the carrier does not want redial for
   1694                             // these.
   1695                             // If the fragment has already been detached since the last time
   1696                             // we called queryLastOutgoingCall in onResume there is no point
   1697                             // doing anything here.
   1698                             if (getActivity() == null) return;
   1699                             mLastNumberDialed = number;
   1700                             updateDialAndDeleteButtonEnabledState();
   1701                         }
   1702                     });
   1703         mCallLog.getLastOutgoingCall(lastCallArgs);
   1704     }
   1705 
   1706     private Intent newFlashIntent() {
   1707         final Intent intent = CallUtil.getCallIntent(EMPTY_NUMBER);
   1708         intent.putExtra(EXTRA_SEND_EMPTY_FLASH, true);
   1709         return intent;
   1710     }
   1711 
   1712     @Override
   1713     public void onHiddenChanged(boolean hidden) {
   1714         super.onHiddenChanged(hidden);
   1715         final DialtactsActivity activity = (DialtactsActivity) getActivity();
   1716         if (activity == null) return;
   1717         if (hidden) {
   1718             activity.showSearchBar();
   1719         } else {
   1720             activity.hideSearchBar();
   1721             mDigits.requestFocus();
   1722         }
   1723     }
   1724 
   1725     public void setAdjustTranslationForAnimation(boolean value) {
   1726         mAdjustTranslationForAnimation = value;
   1727     }
   1728 
   1729     public void setYFraction(float yFraction) {
   1730         ((DialpadSlidingLinearLayout) getView()).setYFraction(yFraction);
   1731     }
   1732 }
   1733