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