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